refactor: move commands and misc out of main binary package

Signed-off-by: kjuulh <contact@kjuulh.io>
This commit is contained in:
Kasper Juul Hermansen 2023-08-01 15:34:24 +02:00
parent 8b83b9c14d
commit c7793f7422
Signed by: kjuulh
GPG Key ID: 9AA7BC13CE474394
24 changed files with 580 additions and 460 deletions

54
Cargo.lock generated
View File

@ -344,7 +344,36 @@ dependencies = [
"chrono", "chrono",
"clap", "clap",
"conventional_commit_parser", "conventional_commit_parser",
"cuddle-please-commands",
"cuddle-please-frontend", "cuddle-please-frontend",
"cuddle-please-misc",
"dotenv",
"git-cliff-core",
"lazy_static",
"parse-changelog",
"pretty_assertions",
"regex",
"reqwest",
"semver",
"serde",
"serde_yaml",
"tempdir",
"tracing",
"tracing-subscriber",
"tracing-test",
"url",
]
[[package]]
name = "cuddle-please-commands"
version = "0.1.0"
dependencies = [
"anyhow",
"chrono",
"clap",
"conventional_commit_parser",
"cuddle-please-frontend",
"cuddle-please-misc",
"dotenv", "dotenv",
"git-cliff-core", "git-cliff-core",
"lazy_static", "lazy_static",
@ -379,6 +408,31 @@ dependencies = [
"tracing-test", "tracing-test",
] ]
[[package]]
name = "cuddle-please-misc"
version = "0.1.0"
dependencies = [
"anyhow",
"chrono",
"clap",
"conventional_commit_parser",
"dotenv",
"git-cliff-core",
"lazy_static",
"parse-changelog",
"pretty_assertions",
"regex",
"reqwest",
"semver",
"serde",
"serde_yaml",
"tempdir",
"tracing",
"tracing-subscriber",
"tracing-test",
"url",
]
[[package]] [[package]]
name = "deunicode" name = "deunicode"
version = "0.4.3" version = "0.4.3"

View File

@ -1,10 +1,12 @@
[workspace] [workspace]
members = ["crates/cuddle-please", "crates/cuddle-please-frontend"] members = ["crates/cuddle-please", "crates/cuddle-please-frontend", "crates/cuddle-please-commands", "crates/cuddle-please-misc"]
resolver = "2" resolver = "2"
[workspace.dependencies] [workspace.dependencies]
cuddle-please = { path = "crates/cuddle-please" } cuddle-please = { path = "crates/cuddle-please" }
cuddle-please-frontend = { path = "crates/cuddle-please-frontend" } cuddle-please-frontend = { path = "crates/cuddle-please-frontend" }
cuddle-please-commands = { path = "crates/cuddle-please-commands" }
cuddle-please-misc = { path = "crates/cuddle-please-misc" }
anyhow = { version = "1.0.71" } anyhow = { version = "1.0.71" }
tracing = { version = "0.1", features = ["log"] } tracing = { version = "0.1", features = ["log"] }

View File

@ -0,0 +1,31 @@
[package]
name = "cuddle-please-commands"
version = "0.1.0"
edition = "2021"
[dependencies]
cuddle-please-frontend.workspace = true
cuddle-please-misc.workspace = true
anyhow.workspace = true
tracing.workspace = true
tracing-subscriber.workspace = true
clap.workspace = true
dotenv.workspace = true
serde_yaml.workspace = true
serde.workspace = true
reqwest = { workspace = true, features = ["blocking", "json"] }
url.workspace = true
semver.workspace = true
conventional_commit_parser.workspace = true
tempdir.workspace = true
git-cliff-core.workspace = true
regex.workspace = true
chrono.workspace = true
lazy_static.workspace = true
parse-changelog.workspace = true
[dev-dependencies]
tracing-test = { workspace = true, features = ["no-env-filter"] }
pretty_assertions.workspace = true

View File

@ -0,0 +1,396 @@
use std::{
io::Read,
ops::Deref,
path::{Path, PathBuf},
sync::{Arc, Mutex},
};
use ::semver::Version;
use anyhow::Context;
use clap::{Parser, Subcommand};
use cuddle_please_frontend::{gatheres::ConfigArgs, PleaseConfigBuilder};
use cuddle_please_misc::{
changelog_parser, get_most_significant_version, ChangeLogBuilder, ConsoleUi, DynUi,
GiteaClient, GlobalArgs, NextVersion, StdinFn, VcsClient,
};
#[derive(Parser)]
#[command(author, version, about, long_about = None)]
pub struct Command {
#[command(flatten)]
global: GlobalArgs,
#[command(flatten)]
config: ConfigArgs,
#[command(subcommand)]
commands: Option<Commands>,
#[clap(skip)]
ui: DynUi,
#[clap(skip)]
stdin: StdinFn,
}
impl Default for Command {
fn default() -> Self {
Self::new()
}
}
impl Command {
pub fn new() -> Self {
let args = std::env::args();
Self::new_from_args_with_stdin(Some(ConsoleUi::default()), args, || {
let mut input = String::new();
std::io::stdin().read_to_string(&mut input)?;
Ok(input)
})
}
pub fn new_from_args<I, T, UIF>(ui: Option<UIF>, i: I) -> Self
where
I: IntoIterator<Item = T>,
T: Into<std::ffi::OsString> + Clone,
UIF: Into<DynUi>,
{
let mut s = Self::parse_from(i);
if let Some(ui) = ui {
s.ui = ui.into();
}
s
}
pub fn new_from_args_with_stdin<I, T, F, UIF>(ui: Option<UIF>, i: I, input: F) -> Self
where
I: IntoIterator<Item = T>,
T: Into<std::ffi::OsString> + Clone,
F: Fn() -> anyhow::Result<String> + Send + Sync + 'static,
UIF: Into<DynUi>,
{
let mut s = Self::parse_from(i);
if let Some(ui) = ui {
s.ui = ui.into();
}
s.stdin = Some(Arc::new(Mutex::new(input)));
s
}
pub fn execute(self, current_dir: Option<&Path>) -> anyhow::Result<()> {
// 0. Get config
let mut builder = &mut PleaseConfigBuilder::new();
if self.global.config_stdin {
if let Some(stdin_fn) = self.stdin.clone() {
let output = (stdin_fn.lock().unwrap().deref())();
builder = builder.with_stdin(output?);
}
}
// 1. Parse the current directory
let current_dir = get_current_path(current_dir, self.config.source.clone())?;
let config = builder
.with_config_file(&current_dir)
.with_execution_env(std::env::vars())
.with_cli(self.config.clone())
.build()?;
match &self.commands {
Some(Commands::Release {}) => {
tracing::debug!("running bare command");
// 2. Parse the cuddle.please.yaml let cuddle.please.yaml take precedence
// 2a. if not existing use default.
// 2b. if not in a git repo abort. (unless --no-vcs is turned added)
let git_client = self.get_git(config.get_source())?;
// 3. Create gitea client and do a health check
let gitea_client = self.get_gitea_client();
gitea_client
.connect(config.get_owner(), config.get_repository())
.context("failed to connect to gitea repository")?;
// 4. Fetch git tags for the current repository
let tags = gitea_client.get_tags(config.get_owner(), config.get_repository())?;
let significant_tag = get_most_significant_version(tags.iter().collect());
// 5. Fetch git commits since last git tag
let commits = gitea_client.get_commits_since(
config.get_owner(),
config.get_repository(),
significant_tag.map(|st| st.commit.sha.clone()),
config.get_branch(),
)?;
// 7. Create a versioning client
let current_version = significant_tag
.map(|st| Version::try_from(st).unwrap())
.unwrap_or(Version::new(0, 1, 0));
// 8. Parse conventional commits and determine next version
let commit_strs = commits
.iter()
.map(|c| c.commit.message.as_str())
.collect::<Vec<&str>>();
if commit_strs.is_empty() {
tracing::info!("no commits to base release on");
return Ok(());
}
let next_version = current_version.next(&commit_strs);
// Compose changelog
let builder = ChangeLogBuilder::new(&commit_strs, next_version.to_string()).build();
let changelog_placement = config.get_source().join("CHANGELOG.md");
let changelog = match std::fs::read_to_string(&changelog_placement).ok() {
Some(existing_changelog) => builder.prepend(existing_changelog)?,
None => builder.generate()?,
};
let changelog_last_changes = changelog_parser::last_changes(&changelog)?;
// 9b. check for release commit and release, if release exists continue
// 10b. create release
if let Some(first_commit) = commit_strs.first() {
if first_commit.contains("chore(release): ") {
if !self.global.dry_run {
gitea_client.create_release(
config.get_owner(),
config.get_repository(),
next_version.to_string(),
changelog_last_changes.unwrap(),
!next_version.pre.is_empty(),
)?;
} else {
tracing::debug!("creating release (dry_run)");
}
return Ok(());
}
}
// 9a. Create / Update Pr
// Create or update branch
git_client.checkout_branch()?;
std::fs::write(changelog_placement, changelog.as_bytes())?;
git_client.commit_and_push(next_version.to_string(), self.global.dry_run)?;
let _pr_number = match gitea_client
.get_pull_request(config.get_owner(), config.get_repository())?
{
Some(existing_pr) => {
if !self.global.dry_run {
gitea_client.update_pull_request(
config.get_owner(),
config.get_repository(),
next_version.to_string(),
changelog_last_changes.unwrap(),
existing_pr,
)?
} else {
tracing::debug!("updating pull request (dry_run)");
1
}
}
None => {
if !self.global.dry_run {
gitea_client.create_pull_request(
config.get_owner(),
config.get_repository(),
next_version.to_string(),
changelog,
config.get_branch(),
)?
} else {
tracing::debug!("creating pull request (dry_run)");
1
}
}
};
}
Some(Commands::Config { command }) => match command {
ConfigCommand::List { .. } => {
tracing::debug!("running command: config list");
self.ui.write_str_ln("cuddle-config");
}
},
Some(Commands::Gitea { command }) => {
let git_url = url::Url::parse(config.get_api_url())?;
let mut url = String::new();
url.push_str(git_url.scheme());
url.push_str("://");
url.push_str(&git_url.host().unwrap().to_string());
if let Some(port) = git_url.port() {
url.push_str(format!(":{port}").as_str());
}
let client = GiteaClient::new(url, self.global.token);
match command {
GiteaCommand::Connect {} => {
client.connect(config.get_owner(), config.get_repository())?;
self.ui.write_str_ln("connected succesfully go gitea");
}
GiteaCommand::Tags { command } => match command {
Some(GiteaTagsCommand::MostSignificant {}) => {
let tags =
client.get_tags(config.get_owner(), config.get_repository())?;
match get_most_significant_version(tags.iter().collect()) {
Some(tag) => {
self.ui.write_str_ln(&format!(
"found most significant tags: {}",
tag.name
));
}
None => {
self.ui.write_str_ln("found no tags with versioning schema");
}
}
}
None => {
let tags =
client.get_tags(config.get_owner(), config.get_repository())?;
self.ui.write_str_ln("got tags from gitea");
for tag in tags {
self.ui.write_str_ln(&format!("- {}", tag.name))
}
}
},
GiteaCommand::SinceCommit { sha, branch } => {
let commits = client.get_commits_since(
config.get_owner(),
config.get_repository(),
Some(sha),
branch,
)?;
self.ui.write_str_ln("got commits from gitea");
for commit in commits {
self.ui.write_str_ln(&format!("- {}", commit.get_title()))
}
}
GiteaCommand::CheckPr {} => {
let pr =
client.get_pull_request(config.get_owner(), config.get_repository())?;
match pr {
Some(index) => {
self.ui.write_str_ln(&format!(
"found cuddle-please (index={}) pr from gitea",
index
));
}
None => {
self.ui.write_str_ln("found no cuddle-please pr from gitea");
}
}
}
}
}
Some(Commands::Doctor {}) => {
match std::process::Command::new("git").arg("-v").output() {
Ok(o) => {
let stdout = std::str::from_utf8(&o.stdout).unwrap_or("");
self.ui.write_str_ln(&format!("OK: {}", stdout));
}
Err(e) => {
self.ui
.write_str_ln(&format!("WARNING: git is not installed: {}", e));
}
}
}
None => {}
}
Ok(())
}
fn get_git(&self, current_dir: &Path) -> anyhow::Result<VcsClient> {
VcsClient::new_git(current_dir)
}
fn get_gitea_client(&self) -> GiteaClient {
GiteaClient::new(
self.config.api_url.clone().expect("api_url to be set"),
self.global.token.clone(),
)
}
}
#[derive(Debug, Clone, Subcommand)]
pub enum Commands {
/// Config is mostly used for debugging the final config output
Release {},
#[command(hide = true)]
Config {
#[command(subcommand)]
command: ConfigCommand,
},
#[command(hide = true)]
Gitea {
#[command(subcommand)]
command: GiteaCommand,
},
/// Helps you identify missing things from your execution environment for cuddle-please to function as intended
Doctor {},
}
#[derive(Subcommand, Debug, Clone)]
pub enum ConfigCommand {
/// List will list the final configuration
List {},
}
#[derive(Subcommand, Debug, Clone)]
pub enum GiteaCommand {
Connect {},
Tags {
#[command(subcommand)]
command: Option<GiteaTagsCommand>,
},
SinceCommit {
#[arg(long)]
sha: String,
#[arg(long)]
branch: String,
},
CheckPr {},
}
#[derive(Subcommand, Debug, Clone)]
pub enum GiteaTagsCommand {
MostSignificant {},
}
fn get_current_path(
optional_current_dir: Option<&Path>,
optional_source_path: Option<PathBuf>,
) -> anyhow::Result<PathBuf> {
let path = optional_source_path
.or_else(|| optional_current_dir.map(|p| p.to_path_buf())) // fall back on current env from environment
.filter(|v| v.to_string_lossy() != "") // make sure we don't get empty values
//.and_then(|p| p.canonicalize().ok()) // Make sure we get the absolute path
//.context("could not find current dir, pass --source as a replacement")?;
.unwrap_or(PathBuf::from("."));
if !path.exists() {
anyhow::bail!("path doesn't exist {}", path.display());
}
Ok(path)
}

View File

@ -0,0 +1,3 @@
mod command;
pub use command::Command as PleaseCommand;

View File

@ -0,0 +1,29 @@
[package]
name = "cuddle-please-misc"
version = "0.1.0"
edition = "2021"
# See more keys and their definitions at https://doc.rust-lang.org/cargo/reference/manifest.html
[dependencies]
anyhow.workspace = true
tracing.workspace = true
tracing-subscriber.workspace = true
clap.workspace = true
dotenv.workspace = true
serde_yaml.workspace = true
serde.workspace = true
reqwest = { workspace = true, features = ["blocking", "json"] }
url.workspace = true
semver.workspace = true
conventional_commit_parser.workspace = true
tempdir.workspace = true
git-cliff-core.workspace = true
regex.workspace = true
chrono.workspace = true
lazy_static.workspace = true
parse-changelog.workspace = true
[dev-dependencies]
tracing-test = { workspace = true, features = ["no-env-filter"] }
pretty_assertions.workspace = true

View File

@ -0,0 +1,38 @@
use std::sync::{Arc, Mutex};
use clap::Args;
pub type StdinFn = Option<Arc<Mutex<dyn Fn() -> anyhow::Result<String> + Send + Sync + 'static>>>;
#[derive(Args)]
pub struct GlobalArgs {
/// token is the personal access token from gitea.
#[arg(
env = "CUDDLE_PLEASE_TOKEN",
long,
long_help = "token is the personal access token from gitea. It requires at least repository write access, it isn't required by default, but for most usecases the flow will fail without it",
global = true,
help_heading = "Global"
)]
pub token: Option<String>,
/// whether to run in dry run mode (i.e. no pushes or releases)
#[arg(long, global = true, help_heading = "Global")]
pub dry_run: bool,
/// Inject configuration from stdin
#[arg(
env = "CUDDLE_PLEASE_CONFIG_STDIN",
long,
global = true,
help_heading = "Global",
long_help = "inject via stdin
cat <<EOF | cuddle-please --config-stdin
something
something
something
EOF
config-stdin will consume stdin until the channel is closed via. EOF"
)]
pub config_stdin: bool,
}

View File

@ -0,0 +1,13 @@
mod args;
mod cliff;
mod git_client;
mod gitea_client;
mod ui;
mod versioning;
pub use args::{GlobalArgs, StdinFn};
pub use cliff::{changelog_parser, ChangeLogBuilder};
pub use git_client::VcsClient;
pub use gitea_client::GiteaClient;
pub use ui::{ConsoleUi, DynUi, Ui};
pub use versioning::{next_version::NextVersion, semver::get_most_significant_version};

View File

@ -15,7 +15,7 @@ impl Default for DynUi {
} }
#[derive(Default)] #[derive(Default)]
pub(crate) struct ConsoleUi {} pub struct ConsoleUi {}
#[allow(dead_code)] #[allow(dead_code)]
impl ConsoleUi { impl ConsoleUi {

View File

@ -5,6 +5,8 @@ edition = "2021"
[dependencies] [dependencies]
cuddle-please-frontend.workspace = true cuddle-please-frontend.workspace = true
cuddle-please-commands.workspace = true
cuddle-please-misc.workspace = true
anyhow.workspace = true anyhow.workspace = true
tracing.workspace = true tracing.workspace = true

View File

@ -1,436 +1 @@
use std::{
io::Read,
ops::Deref,
path::{Path, PathBuf},
sync::{Arc, Mutex},
};
use ::semver::Version;
use anyhow::Context;
use clap::{Args, Parser, Subcommand};
use cuddle_please_frontend::{gatheres::ConfigArgs, PleaseConfigBuilder};
use crate::{
cliff::{self, changelog_parser},
git_client::VcsClient,
gitea_client::GiteaClient,
ui::{ConsoleUi, DynUi},
versioning::{next_version::NextVersion, semver::get_most_significant_version},
};
type StdinFn = Option<Arc<Mutex<dyn Fn() -> anyhow::Result<String> + Send + Sync + 'static>>>;
#[derive(Parser)]
#[command(author, version, about, long_about = None)]
pub struct Command {
#[command(flatten)]
global: GlobalArgs,
#[command(flatten)]
config: ConfigArgs,
#[command(subcommand)]
commands: Option<Commands>,
#[clap(skip)]
ui: DynUi,
#[clap(skip)]
stdin: StdinFn,
}
#[derive(Args)]
struct GlobalArgs {
/// token is the personal access token from gitea.
#[arg(
env = "CUDDLE_PLEASE_TOKEN",
long,
long_help = "token is the personal access token from gitea. It requires at least repository write access, it isn't required by default, but for most usecases the flow will fail without it",
global = true,
help_heading = "Global"
)]
token: Option<String>,
/// whether to run in dry run mode (i.e. no pushes or releases)
#[arg(long, global = true, help_heading = "Global")]
dry_run: bool,
/// Inject configuration from stdin
#[arg(
env = "CUDDLE_PLEASE_CONFIG_STDIN",
long,
global = true,
help_heading = "Global",
long_help = "inject via stdin
cat <<EOF | cuddle-please --config-stdin
something
something
something
EOF
config-stdin will consume stdin until the channel is closed via. EOF"
)]
config_stdin: bool,
}
impl Default for Command {
fn default() -> Self {
Self::new()
}
}
impl Command {
pub fn new() -> Self {
let args = std::env::args();
Self::new_from_args_with_stdin(Some(ConsoleUi::default()), args, || {
let mut input = String::new();
std::io::stdin().read_to_string(&mut input)?;
Ok(input)
})
}
pub fn new_from_args<I, T, UIF>(ui: Option<UIF>, i: I) -> Self
where
I: IntoIterator<Item = T>,
T: Into<std::ffi::OsString> + Clone,
UIF: Into<DynUi>,
{
let mut s = Self::parse_from(i);
if let Some(ui) = ui {
s.ui = ui.into();
}
s
}
pub fn new_from_args_with_stdin<I, T, F, UIF>(ui: Option<UIF>, i: I, input: F) -> Self
where
I: IntoIterator<Item = T>,
T: Into<std::ffi::OsString> + Clone,
F: Fn() -> anyhow::Result<String> + Send + Sync + 'static,
UIF: Into<DynUi>,
{
let mut s = Self::parse_from(i);
if let Some(ui) = ui {
s.ui = ui.into();
}
s.stdin = Some(Arc::new(Mutex::new(input)));
s
}
pub fn execute(self, current_dir: Option<&Path>) -> anyhow::Result<()> {
// 0. Get config
let mut builder = &mut PleaseConfigBuilder::new();
if self.global.config_stdin {
if let Some(stdin_fn) = self.stdin.clone() {
let output = (stdin_fn.lock().unwrap().deref())();
builder = builder.with_stdin(output?);
}
}
// 1. Parse the current directory
let current_dir = get_current_path(current_dir, self.config.source.clone())?;
let config = builder
.with_config_file(&current_dir)
.with_execution_env(std::env::vars())
.with_cli(self.config.clone())
.build()?;
match &self.commands {
Some(Commands::Release {}) => {
tracing::debug!("running bare command");
// 2. Parse the cuddle.please.yaml let cuddle.please.yaml take precedence
// 2a. if not existing use default.
// 2b. if not in a git repo abort. (unless --no-vcs is turned added)
let git_client = self.get_git(config.get_source())?;
// 3. Create gitea client and do a health check
let gitea_client = self.get_gitea_client();
gitea_client
.connect(config.get_owner(), config.get_repository())
.context("failed to connect to gitea repository")?;
// 4. Fetch git tags for the current repository
let tags = gitea_client.get_tags(config.get_owner(), config.get_repository())?;
let significant_tag = get_most_significant_version(tags.iter().collect());
// 5. Fetch git commits since last git tag
let commits = gitea_client.get_commits_since(
config.get_owner(),
config.get_repository(),
significant_tag.map(|st| st.commit.sha.clone()),
config.get_branch(),
)?;
// 7. Create a versioning client
let current_version = significant_tag
.map(|st| Version::try_from(st).unwrap())
.unwrap_or(Version::new(0, 1, 0));
// 8. Parse conventional commits and determine next version
let commit_strs = commits
.iter()
.map(|c| c.commit.message.as_str())
.collect::<Vec<&str>>();
if commit_strs.is_empty() {
tracing::info!("no commits to base release on");
return Ok(());
}
let next_version = current_version.next(&commit_strs);
// Compose changelog
let builder =
cliff::ChangeLogBuilder::new(&commit_strs, next_version.to_string()).build();
let changelog_placement = config.get_source().join("CHANGELOG.md");
let changelog = match std::fs::read_to_string(&changelog_placement).ok() {
Some(existing_changelog) => builder.prepend(existing_changelog)?,
None => builder.generate()?,
};
let changelog_last_changes = changelog_parser::last_changes(&changelog)?;
// 9b. check for release commit and release, if release exists continue
// 10b. create release
if let Some(first_commit) = commit_strs.first() {
if first_commit.contains("chore(release): ") {
if !self.global.dry_run {
gitea_client.create_release(
config.get_owner(),
config.get_repository(),
next_version.to_string(),
changelog_last_changes.unwrap(),
!next_version.pre.is_empty(),
)?;
} else {
tracing::debug!("creating release (dry_run)");
}
return Ok(());
}
}
// 9a. Create / Update Pr
// Create or update branch
git_client.checkout_branch()?;
std::fs::write(changelog_placement, changelog.as_bytes())?;
git_client.commit_and_push(next_version.to_string(), self.global.dry_run)?;
let _pr_number = match gitea_client
.get_pull_request(config.get_owner(), config.get_repository())?
{
Some(existing_pr) => {
if !self.global.dry_run {
gitea_client.update_pull_request(
config.get_owner(),
config.get_repository(),
next_version.to_string(),
changelog_last_changes.unwrap(),
existing_pr,
)?
} else {
tracing::debug!("updating pull request (dry_run)");
1
}
}
None => {
if !self.global.dry_run {
gitea_client.create_pull_request(
config.get_owner(),
config.get_repository(),
next_version.to_string(),
changelog,
config.get_branch(),
)?
} else {
tracing::debug!("creating pull request (dry_run)");
1
}
}
};
}
Some(Commands::Config { command }) => match command {
ConfigCommand::List { .. } => {
tracing::debug!("running command: config list");
self.ui.write_str_ln("cuddle-config");
}
},
Some(Commands::Gitea { command }) => {
let git_url = url::Url::parse(config.get_api_url())?;
let mut url = String::new();
url.push_str(git_url.scheme());
url.push_str("://");
url.push_str(&git_url.host().unwrap().to_string());
if let Some(port) = git_url.port() {
url.push_str(format!(":{port}").as_str());
}
let client = GiteaClient::new(url, self.global.token);
match command {
GiteaCommand::Connect {} => {
client.connect(config.get_owner(), config.get_repository())?;
self.ui.write_str_ln("connected succesfully go gitea");
}
GiteaCommand::Tags { command } => match command {
Some(GiteaTagsCommand::MostSignificant {}) => {
let tags =
client.get_tags(config.get_owner(), config.get_repository())?;
match get_most_significant_version(tags.iter().collect()) {
Some(tag) => {
self.ui.write_str_ln(&format!(
"found most significant tags: {}",
tag.name
));
}
None => {
self.ui.write_str_ln("found no tags with versioning schema");
}
}
}
None => {
let tags =
client.get_tags(config.get_owner(), config.get_repository())?;
self.ui.write_str_ln("got tags from gitea");
for tag in tags {
self.ui.write_str_ln(&format!("- {}", tag.name))
}
}
},
GiteaCommand::SinceCommit { sha, branch } => {
let commits = client.get_commits_since(
config.get_owner(),
config.get_repository(),
Some(sha),
branch,
)?;
self.ui.write_str_ln("got commits from gitea");
for commit in commits {
self.ui.write_str_ln(&format!("- {}", commit.get_title()))
}
}
GiteaCommand::CheckPr {} => {
let pr =
client.get_pull_request(config.get_owner(), config.get_repository())?;
match pr {
Some(index) => {
self.ui.write_str_ln(&format!(
"found cuddle-please (index={}) pr from gitea",
index
));
}
None => {
self.ui.write_str_ln("found no cuddle-please pr from gitea");
}
}
}
}
}
Some(Commands::Doctor {}) => {
match std::process::Command::new("git").arg("-v").output() {
Ok(o) => {
let stdout = std::str::from_utf8(&o.stdout).unwrap_or("");
self.ui.write_str_ln(&format!("OK: {}", stdout));
}
Err(e) => {
self.ui
.write_str_ln(&format!("WARNING: git is not installed: {}", e));
}
}
}
None => {}
}
Ok(())
}
fn get_git(&self, current_dir: &Path) -> anyhow::Result<VcsClient> {
VcsClient::new_git(current_dir)
}
fn get_gitea_client(&self) -> GiteaClient {
GiteaClient::new(
self.config.api_url.clone().expect("api_url to be set"),
self.global.token.clone(),
)
}
}
#[derive(Debug, Clone, Subcommand)]
enum Commands {
/// Config is mostly used for debugging the final config output
Release {},
#[command(hide = true)]
Config {
#[command(subcommand)]
command: ConfigCommand,
},
#[command(hide = true)]
Gitea {
#[command(subcommand)]
command: GiteaCommand,
},
/// Helps you identify missing things from your execution environment for cuddle-please to function as intended
Doctor {},
}
#[derive(Subcommand, Debug, Clone)]
enum ConfigCommand {
/// List will list the final configuration
List {},
}
#[derive(Subcommand, Debug, Clone)]
enum GiteaCommand {
Connect {},
Tags {
#[command(subcommand)]
command: Option<GiteaTagsCommand>,
},
SinceCommit {
#[arg(long)]
sha: String,
#[arg(long)]
branch: String,
},
CheckPr {},
}
#[derive(Subcommand, Debug, Clone)]
enum GiteaTagsCommand {
MostSignificant {},
}
fn get_current_path(
optional_current_dir: Option<&Path>,
optional_source_path: Option<PathBuf>,
) -> anyhow::Result<PathBuf> {
let path = optional_source_path
.or_else(|| optional_current_dir.map(|p| p.to_path_buf())) // fall back on current env from environment
.filter(|v| v.to_string_lossy() != "") // make sure we don't get empty values
//.and_then(|p| p.canonicalize().ok()) // Make sure we get the absolute path
//.context("could not find current dir, pass --source as a replacement")?;
.unwrap_or(PathBuf::from("."));
if !path.exists() {
anyhow::bail!("path doesn't exist {}", path.display());
}
Ok(path)
}

View File

@ -1,6 +0,0 @@
pub mod cliff;
pub mod command;
pub mod git_client;
pub mod gitea_client;
pub mod ui;
pub mod versioning;

View File

@ -1,11 +1,4 @@
pub mod cliff; use cuddle_please_commands::PleaseCommand;
pub mod command;
pub mod git_client;
pub mod gitea_client;
pub mod ui;
pub mod versioning;
use command::Command;
fn main() -> anyhow::Result<()> { fn main() -> anyhow::Result<()> {
dotenv::dotenv().ok(); dotenv::dotenv().ok();
@ -14,7 +7,7 @@ fn main() -> anyhow::Result<()> {
let current_dir = std::env::current_dir().ok(); let current_dir = std::env::current_dir().ok();
let current_dir = current_dir.as_deref(); let current_dir = current_dir.as_deref();
Command::new().execute(current_dir)?; PleaseCommand::new().execute(current_dir)?;
Ok(()) Ok(())
} }

View File

@ -1,4 +1,4 @@
use cuddle_please::ui::{DynUi, Ui}; use cuddle_please_misc::{DynUi, Ui};
use std::{ use std::{
io::Write, io::Write,

View File

@ -1,7 +1,7 @@
pub mod common; pub mod common;
use common::BufferUi; use common::BufferUi;
use cuddle_please::command::Command; use cuddle_please_commands::PleaseCommand;
use tracing_test::traced_test; use tracing_test::traced_test;
use crate::common::{assert_output, get_test_data_path}; use crate::common::{assert_output, get_test_data_path};
@ -17,7 +17,7 @@ fn test_config_from_current_dir() {
let ui = &BufferUi::default(); let ui = &BufferUi::default();
let current_dir = get_test_data_path("cuddle-embed"); let current_dir = get_test_data_path("cuddle-embed");
Command::new_from_args(Some(ui), args) PleaseCommand::new_from_args(Some(ui), args)
.execute(Some(&current_dir)) .execute(Some(&current_dir))
.unwrap(); .unwrap();
@ -33,7 +33,7 @@ fn test_config_from_source_dir() {
args.push("--source"); args.push("--source");
args.push(current_dir.to_str().unwrap()); args.push(current_dir.to_str().unwrap());
Command::new_from_args(Some(ui), args) PleaseCommand::new_from_args(Some(ui), args)
.execute(None) .execute(None)
.unwrap(); .unwrap();
@ -55,7 +55,7 @@ settings:
"#; "#;
args.push("--config-stdin"); args.push("--config-stdin");
Command::new_from_args_with_stdin(Some(ui), args, || Ok(config.into())) PleaseCommand::new_from_args_with_stdin(Some(ui), args, || Ok(config.into()))
.execute(None) .execute(None)
.unwrap(); .unwrap();
assert_output(ui, "cuddle-config\n", ""); assert_output(ui, "cuddle-config\n", "");
@ -67,7 +67,7 @@ fn test_config_fails_when_not_path_is_set() {
let args = get_base_args(); let args = get_base_args();
let ui = &BufferUi::default(); let ui = &BufferUi::default();
let res = Command::new_from_args(Some(ui), args).execute(None); let res = PleaseCommand::new_from_args(Some(ui), args).execute(None);
assert!(res.is_err()) assert!(res.is_err())
} }

View File

@ -1,6 +1,6 @@
use std::path::Path; use std::path::Path;
use cuddle_please::git_client::VcsClient; use cuddle_please_misc::VcsClient;
use pretty_assertions::assert_eq; use pretty_assertions::assert_eq;
use tracing_test::traced_test; use tracing_test::traced_test;

View File

@ -1,6 +1,6 @@
pub mod common; pub mod common;
use cuddle_please::git_client::VcsClient; use cuddle_please_misc::VcsClient;
use tracing_test::traced_test; use tracing_test::traced_test;
use crate::common::get_test_data_path; use crate::common::get_test_data_path;