fix(issue)!: make subject mandatory and optional content

BREAKING CHANGE: When the editor contains only one line, it is now
treated as the subject instead of being used as the issue content.

Signed-off-by: Awiteb <a@4rs.nl>
This commit is contained in:
Awiteb
2025-08-18 13:25:36 +00:00
parent ec68e972f7
commit ed1bfaf161
3 changed files with 73 additions and 42 deletions

View File

@@ -5,6 +5,20 @@ All notable changes to this project will be documented in this file.
The format is based on [Keep a Changelog](https://keepachangelog.com/en/1.0.0/), The format is based on [Keep a Changelog](https://keepachangelog.com/en/1.0.0/),
and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0.html). and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0.html).
## Unreleased
### Added
- New command `repo state` - by Awiteb
### Breaking Change
- Make subject mandatory and optional content - by Awiteb
### Fixed
- Check the nip07 signer in `CliOptions::ensure_signer` - by Awiteb
## [0.4.0] - 2025-08-08 ## [0.4.0] - 2025-08-08
### Added ### Added

View File

@@ -37,14 +37,9 @@ use crate::{
/// Arguments for the `issue new` command /// Arguments for the `issue new` command
#[derive(Args, Debug)] #[derive(Args, Debug)]
#[clap( #[clap(
group(
ArgGroup::new("issue-content")
.args(["content", "editor"])
.required(true)
),
group( group(
ArgGroup::new("issue-subject") ArgGroup::new("issue-subject")
.args(["editor", "subject"]) .required(true)
) )
)] )]
pub struct NewArgs { pub struct NewArgs {
@@ -56,14 +51,14 @@ pub struct NewArgs {
naddrs: Option<Vec<NaddrOrSet>>, naddrs: Option<Vec<NaddrOrSet>>,
/// Markdown content for the issue. Cannot be used together with the /// Markdown content for the issue. Cannot be used together with the
/// `--editor` flag. /// `--editor` flag.
#[arg(short, long)] #[arg(short, long, group = "issue-content")]
content: Option<String>, content: Option<String>,
/// Opens the user's default editor to write issue content. The first line /// Opens the user's default editor to write issue content. The first line
/// will be used as the issue subject. /// will be used as the issue subject.
#[arg(short, long)] #[arg(short, long, group = "issue-subject", group = "issue-content")]
editor: bool, editor: bool,
/// The issue subject. Cannot be used together with the `--editor` flag. /// The issue subject. Cannot be used together with the `--editor` flag.
#[arg(long)] #[arg(long, group = "issue-subject")]
subject: Option<String>, subject: Option<String>,
/// Labels for the issue. Can be specified as arguments (-l bug) or hashtags /// Labels for the issue. Can be specified as arguments (-l bug) or hashtags
/// in content (#bug). /// in content (#bug).
@@ -71,29 +66,6 @@ pub struct NewArgs {
label: Vec<String>, label: Vec<String>,
} }
impl NewArgs {
/// Returns the subject and the content of the issue. (subject, content)
pub fn issue_content(&self) -> N34Result<(Option<String>, String)> {
if let Some(content) = self.content.as_ref() {
if let Some(subject) = self.subject.as_ref() {
return Ok((Some(subject.trim().to_owned()), content.trim().to_owned()));
}
return Ok((None, content.trim().to_owned()));
}
// If the `self.content` is `None` then the `self.editor` is `true`
let file_content = utils::read_editor(None, ".md")?;
if file_content.contains('\n') {
Ok(file_content
.split_once('\n')
.map(|(s, c)| (Some(s.trim().to_owned()), c.trim().to_owned()))
.expect("There is a new line"))
} else {
tracing::info!("File content contains only issue body without a subject line");
Ok((None, file_content))
}
}
}
impl CommandRunner for NewArgs { impl CommandRunner for NewArgs {
async fn run(self, options: CliOptions) -> N34Result<()> { async fn run(self, options: CliOptions) -> N34Result<()> {
let naddrs = utils::check_empty_naddrs(utils::naddrs_or_file( let naddrs = utils::check_empty_naddrs(utils::naddrs_or_file(
@@ -113,16 +85,30 @@ impl CommandRunner for NewArgs {
.add_relays(&utils::add_read_relays(relays_list.as_ref())) .add_relays(&utils::add_read_relays(relays_list.as_ref()))
.await; .await;
let (subject, content) = self.issue_content()?; let (subject, content) = utils::subject_and_body(self.subject, self.content, ".md")?;
let content_details = client.parse_content(&content).await;
let event = let content_details = if let Some(content) = &content {
EventBuilder::new_git_issue(coordinates.as_slice(), content, subject, self.label)? Some(client.parse_content(content).await)
.dedup_tags() } else {
.pow(options.pow.unwrap_or_default()) None
.tags(maintainers.iter().map(|p| Tag::public_key(*p))) };
.tags(content_details.clone().into_tags())
.build(user_pubk); let event = EventBuilder::new_git_issue(
coordinates.as_slice(),
content.unwrap_or_default(),
Some(subject),
self.label,
)?
.dedup_tags()
.pow(options.pow.unwrap_or_default())
.tags(maintainers.iter().map(|p| Tag::public_key(*p)))
.tags(
content_details
.clone()
.map(|c| c.into_tags())
.unwrap_or_default(),
)
.build(user_pubk);
let event_id = event.id.expect("There is an id"); let event_id = event.id.expect("There is an id");
let write_relays = [ let write_relays = [
@@ -135,7 +121,9 @@ impl CommandRunner for NewArgs {
.extract_relays(), .extract_relays(),
// Include read relays for each maintainer (if found) // Include read relays for each maintainer (if found)
client.read_relays_from_users(&maintainers).await, client.read_relays_from_users(&maintainers).await,
content_details.write_relays.clone().into_iter().collect(), content_details
.map(|c| c.write_relays.into_iter().collect())
.unwrap_or_default(),
] ]
.concat(); .concat();

View File

@@ -309,3 +309,32 @@ pub fn check_empty_naddrs(naddrs: Vec<Nip19Coordinate>) -> N34Result<Vec<Nip19Co
Ok(naddrs) Ok(naddrs)
} }
/// Extracts the subject and body from the provided inputs or an editor.
///
/// If no subject is provided, it opens an editor where the first line is used
/// as the subject and the remaining lines as the body. If the editor output
/// contains only one line, it is used as the subject.
pub fn subject_and_body(
subject: Option<String>,
body: Option<String>,
file_suffix: &str,
) -> N34Result<(String, Option<String>)> {
if let Some(subject) = subject {
return Ok((subject, body));
}
// There is no subject, so we need to get it from the editor
let file_content = read_editor(None, file_suffix)?;
// if there is a subject and body
if file_content.contains('\n') {
Ok(file_content
.split_once("\n")
.map(|(subject, body)| (subject.trim().to_owned(), Some(body.trim().to_owned())))
.expect("There is a newline"))
} else {
tracing::info!("File content contains only the subject");
Ok((file_content.trim().to_owned(), None))
}
}