1use crate::jira_doc_std_field;
2use clap::{ArgAction, Args, Parser, Subcommand, ValueEnum};
3use regex::Regex;
4use serde::{Deserialize, Serialize};
5use std::error::Error;
6
7#[derive(Parser, Debug)]
10#[command(author, version, about, long_about = None)]
11pub struct JirustCliArgs {
12 #[clap(subcommand)]
14 pub subcmd: Commands,
15}
16
17#[derive(Subcommand, Clone, Debug, Serialize, Deserialize)]
20pub enum Commands {
21 Config(ConfigArgs),
23 Issue(IssueArgs),
25 Link(LinkIssueArgs),
27 Project(ProjectArgs),
29 Transition(TransitionArgs),
31 Version(VersionArgs),
33}
34
35#[derive(Args, Clone, Debug, Serialize, Deserialize)]
40pub struct PaginationArgs {
41 #[clap(
43 long,
44 short = 'l',
45 value_name = "page_size",
46 help = "page size for lists"
47 )]
48 pub page_size: Option<i32>,
49 #[clap(
51 long,
52 short = 's',
53 value_name = "page_offset",
54 help = "page offset for list"
55 )]
56 pub page_offset: Option<i64>,
57}
58
59#[derive(ValueEnum, Debug, Clone, Copy, Serialize, Deserialize, PartialEq)]
65#[value(rename_all = "kebab-case")]
66pub enum OutputValues {
67 #[value(name = "table", help = "Print output in table format")]
69 Table,
70 #[value(name = "json", help = "Print output in json format")]
72 Json,
73}
74
75#[derive(ValueEnum, Debug, Clone, Copy, Serialize, Deserialize, PartialEq)]
82#[value(rename_all = "kebab-case")]
83pub enum OutputTypes {
84 #[value(name = "basic", help = "Print basic output")]
86 Basic,
87 #[value(name = "single", help = "Print single row output")]
89 Single,
90 #[value(name = "full", help = "Print full output")]
92 Full,
93}
94
95#[derive(Args, Clone, Debug, Serialize, Deserialize)]
99pub struct OutputArgs {
100 #[clap(long, short = 'o', value_name = "table|json", help = "Output format")]
102 pub output_format: Option<OutputValues>,
103 #[clap(
105 long,
106 short = 'z',
107 value_name = "basic|single|full",
108 help = "Output type"
109 )]
110 pub output_type: Option<OutputTypes>,
111}
112
113#[derive(Args, Clone, Debug, Serialize, Deserialize)]
117pub struct ConfigArgs {
118 #[arg(
120 value_name = "auth|jira|setup|show",
121 help_heading = "Configuration management"
122 )]
123 pub cfg_act: ConfigActionValues,
124}
125
126#[derive(ValueEnum, Debug, Clone, Copy, Serialize, Deserialize, PartialEq)]
134#[value(rename_all = "kebab-case")]
135pub enum ConfigActionValues {
136 #[value(name = "auth", help = "Set Jira API authentication (username, apikey)")]
138 Auth,
139 #[value(name = "jira", help = "Set Jira API base URL")]
141 Jira,
142 #[value(
144 name = "setup",
145 help = "Setup Jira API configuration (authentication data, jira base URL, etc.)"
146 )]
147 Setup,
148 #[value(name = "show", help = "Show current configuration")]
150 Show,
151}
152
153#[derive(Args, Clone, Debug, Serialize, Deserialize)]
172pub struct VersionArgs {
173 #[arg(
175 value_name = "archive|create|delete|list|release|update",
176 help_heading = "Jira Project version management"
177 )]
178 pub version_act: VersionActionValues,
179 #[clap(
181 long,
182 short = 'k',
183 value_name = "project_key",
184 required = true,
185 help = "Jira Project key"
186 )]
187 pub project_key: String,
188 #[clap(long, short = 'i', value_name = "project_id", help = "Jira Project ID")]
190 pub project_id: Option<i64>,
191 #[clap(
193 long,
194 short = 'v',
195 value_name = "version_id",
196 help = "Jira Project version ID"
197 )]
198 pub version_id: Option<String>,
199 #[clap(
201 long,
202 short = 'n',
203 value_name = "version_name",
204 help = "Jira Project version name"
205 )]
206 pub version_name: Option<String>,
207 #[clap(
209 long,
210 short = 'd',
211 value_name = "version_description",
212 help = "Jira Project version description"
213 )]
214 pub version_description: Option<String>,
215 #[clap(
217 long,
218 value_name = "version_start_date",
219 help = "Jira Project version start date"
220 )]
221 pub version_start_date: Option<String>,
222 #[clap(
224 long,
225 value_name = "version_release_date",
226 help = "Jira Project version release date"
227 )]
228 pub version_release_date: Option<String>,
229 #[clap(
231 long,
232 short = 'a',
233 value_name = "version_archived",
234 help = "Jira Project version archived"
235 )]
236 pub version_archived: Option<bool>,
237 #[clap(
239 long,
240 short = 'm',
241 action = ArgAction::SetTrue,
242 value_name = "version_released",
243 help = "Jira Project version released"
244 )]
245 pub version_released: Option<bool>,
246 #[clap(
248 long,
249 short = 'c',
250 value_name = "changelog_file",
251 help = "changelog file path to be used for automatic description generation (if set the script detects automatically the first tagged block in the changelog and use it as description)"
252 )]
253 pub changelog_file: Option<String>,
254 #[clap(
256 long,
257 short = 'r',
258 action = ArgAction::SetTrue,
259 value_name = "resolve_issues",
260 help = "if changelog is set and this flag is set, the script will transition all issues in the changelog of the current version release to the \"resolved\" status setting the version as \"fixVersion\""
261 )]
262 pub transition_issues: Option<bool>,
263 #[clap(
265 long,
266 short = 'u',
267 value_name = "transition_assignee",
268 help = "if changelog is set and the resolve_issues flag is set, the script will assigned all the resolved issue to the user specified in this field (if not set the assignee will not be changed)"
269 )]
270 pub transition_assignee: Option<String>,
271 #[clap(flatten)]
273 pub pagination: PaginationArgs,
274 #[clap(flatten)]
276 pub output: OutputArgs,
277}
278
279#[derive(ValueEnum, Debug, Clone, Copy, Serialize, Deserialize, PartialEq)]
290#[value(rename_all = "kebab-case")]
291pub enum VersionActionValues {
292 #[value(name = "archive", help = "Archive a Jira Project version")]
294 Archive,
295 #[value(name = "create", help = "Create a Jira Project version")]
297 Create,
298 #[value(name = "delete", help = "Delete a Jira Project version")]
300 Delete,
301 #[value(name = "list", help = "List Jira Project versions")]
303 List,
304 #[value(
306 name = "related-work-items",
307 help = "Get Project version related workitems"
308 )]
309 RelatedWorkItems,
310 #[value(name = "release", help = "Release a Jira Project version")]
312 Release,
313 #[value(name = "update", help = "Update a Jira Project version")]
315 Update,
316}
317
318#[derive(Args, Clone, Debug, Serialize, Deserialize)]
337pub struct ProjectArgs {
338 #[arg(
340 value_name = "create|get-issue-types|get-issue-type-fields|list",
341 help_heading = "Jira Project management",
342 required = true
343 )]
344 pub project_act: ProjectActionValues,
345 #[clap(
347 long,
348 short = 'k',
349 value_name = "project_key",
350 help = "Jira Project key"
351 )]
352 pub project_key: Option<String>,
353 #[clap(
355 long,
356 short = 'i',
357 value_name = "project_issue_type",
358 help = "Jira Project issue type ID"
359 )]
360 pub project_issue_type: Option<String>,
361 #[clap(long, value_name = "project_name", help = "Jira Project name")]
363 pub project_name: Option<String>,
364 #[clap(
366 long,
367 value_name = "project_description",
368 help = "Jira Project description"
369 )]
370 pub project_description: Option<String>,
371 #[clap(
373 long,
374 value_name = "project_field_configuration_id",
375 help = "Jira Project field configuration ID"
376 )]
377 pub project_field_configuration_id: Option<i64>,
378 #[clap(
380 long,
381 value_name = "project_issue_security_scheme_id",
382 help = "Jira Project issue security scheme ID"
383 )]
384 pub project_issue_security_scheme_id: Option<i64>,
385 #[clap(
387 long,
388 value_name = "project_issue_type_scheme_id",
389 help = "Jira Project issue type scheme ID"
390 )]
391 pub project_issue_type_scheme_id: Option<i64>,
392 #[clap(
394 long,
395 value_name = "project_issue_type_screen_scheme_id",
396 help = "Jira Project issue type screen scheme ID"
397 )]
398 pub project_issue_type_screen_scheme_id: Option<i64>,
399 #[clap(
401 long,
402 value_name = "project_notification_scheme_id",
403 help = "Jira Project notification scheme ID"
404 )]
405 pub project_notification_scheme_id: Option<i64>,
406 #[clap(
408 long,
409 value_name = "project_permission_scheme_id",
410 help = "Jira Project permission scheme ID"
411 )]
412 pub project_permission_scheme_id: Option<i64>,
413 #[clap(
415 long,
416 value_name = "project_workflow_scheme_id",
417 help = "Jira Project workflow scheme ID"
418 )]
419 pub project_workflow_scheme_id: Option<i64>,
420 #[clap(
422 long,
423 value_name = "project_lead_account_id",
424 help = "Jira Project lead account ID"
425 )]
426 pub project_lead_account_id: Option<String>,
427 #[clap(
429 long,
430 value_name = "project_assignee_type",
431 help = "Jira Project Assignee Type"
432 )]
433 pub project_assignee_type: Option<String>,
434 #[clap(flatten)]
436 pub pagination: PaginationArgs,
437 #[clap(flatten)]
439 pub output: OutputArgs,
440}
441
442#[derive(ValueEnum, Debug, Clone, Copy, Serialize, Deserialize, PartialEq)]
449#[value(rename_all = "kebab-case")]
450pub enum ProjectActionValues {
451 #[value(name = "create", help = "Create new Jira Project")]
453 Create,
454 #[value(
456 name = "get-issue-types",
457 help = "Get Jira Project issue types by Jira project key"
458 )]
459 GetIssueTypes,
460 #[value(
462 name = "get-issue-type-fields",
463 help = "Get Jira Project issue type fields by Jira project key and issue type ID"
464 )]
465 GetIssueTypeFields,
466 #[value(name = "list", help = "List Jira Projects")]
468 List,
469}
470
471#[derive(Args, Clone, Debug, Serialize, Deserialize)]
483pub struct IssueArgs {
484 #[arg(
486 value_name = "assign|create|delete|get|search|transition|update",
487 help_heading = "Jira Project issue management",
488 required = true
489 )]
490 pub issue_act: IssueActionValues,
491 #[clap(
493 long,
494 short = 'k',
495 value_name = "project_key",
496 help = "Jira Project key"
497 )]
498 pub project_key: Option<String>,
499 #[clap(
501 long,
502 short = 'i',
503 value_name = "issue_key",
504 help = "Jira Project issue key"
505 )]
506 pub issue_key: Option<String>,
507 #[clap(long,
509 short = 'f',
510 value_name = "issue_fields",
511 value_parser = parse_key_val::<String, String>,
512 help = "Jira Project issue fields (field_name=value)")]
513 pub issue_fields: Option<Vec<(String, String)>>,
514 #[clap(
516 long,
517 short = 't',
518 value_name = "transition_to",
519 help = "Jira Project issue transition to"
520 )]
521 pub transition_to: Option<String>,
522 #[clap(
524 long,
525 short = 'a',
526 value_name = "assignee",
527 help = "Jira Project issue assignee"
528 )]
529 pub assignee: Option<String>,
530 #[clap(
532 long,
533 short = 'q',
534 value_name = "query",
535 help = "Jira Project issue query"
536 )]
537 pub query: Option<String>,
538 #[clap(
540 long,
541 short = 'p',
542 value_name = "attachment_file_path",
543 help = "Jira Project issue attachment file path"
544 )]
545 pub attachment_file_path: Option<String>,
546 #[clap(flatten)]
548 pub pagination: PaginationArgs,
549 #[clap(flatten)]
551 pub output: OutputArgs,
552}
553
554#[derive(ValueEnum, Debug, Clone, Copy, Serialize, Deserialize, PartialEq)]
565#[value(rename_all = "kebab-case")]
566pub enum IssueActionValues {
567 #[value(name = "assign", help = "Assign a Jira Project issue")]
569 Assign,
570 #[value(name = "attach", help = "Attach a file to a Jira Project issue")]
572 Attach,
573 #[value(name = "create", help = "Create a Jira Project issue")]
575 Create,
576 #[value(name = "delete", help = "Delete a Jira Project issue")]
578 Delete,
579 #[value(name = "get", help = "Get a specific Jira Project issue")]
581 Get,
582 #[value(name = "search", help = "Search for Jira Project issues")]
584 Search,
585 #[value(name = "transition", help = "Transition a Jira Project issue")]
587 Transition,
588 #[value(name = "update", help = "Update a Jira Project issue")]
590 Update,
591}
592
593#[derive(Args, Clone, Debug, Serialize, Deserialize)]
602pub struct LinkIssueArgs {
603 #[arg(
605 value_name = "create",
606 help_heading = "Jira issues links management",
607 required = true
608 )]
609 pub link_act: LinkIssueActionValues,
610 #[clap(
612 long,
613 short = 'p',
614 value_name = "project_key",
615 help = "Jira Project key"
616 )]
617 pub project_key: Option<String>,
618 #[clap(
620 long,
621 short = 'i',
622 value_name = "issue_key",
623 help = "Jira issue link origin key",
624 required = true
625 )]
626 pub origin_issue_key: String,
627 #[clap(
629 long,
630 short = 'd',
631 value_name = "issue_key",
632 help = "Jira issue link destination key"
633 )]
634 pub destination_issue_key: Option<String>,
635 #[clap(
637 long,
638 short = 't',
639 value_name = "link_type",
640 help = "Jira issue link type",
641 required = true
642 )]
643 pub link_type: String,
644 #[clap(
646 long,
647 short = 'c',
648 value_name = "changelog_file",
649 help = "changelog file path to be used for automatic issues' links generation (if set the script detects automatically the first tagged block in the changelog and use it as description)"
650 )]
651 pub changelog_file: Option<String>,
652}
653
654#[derive(ValueEnum, Debug, Clone, Copy, Serialize, Deserialize, PartialEq)]
658#[value(rename_all = "kebab-case")]
659pub enum LinkIssueActionValues {
660 #[value(name = "create", help = "Create a Jira link between issues")]
662 Create,
663}
664
665#[derive(Args, Clone, Debug, Serialize, Deserialize)]
671pub struct TransitionArgs {
672 #[arg(value_name = "list", help_heading = "Jira issue transition list")]
674 pub transition_act: TransitionActionValues,
675 #[clap(
677 long,
678 short = 'i',
679 value_name = "issue_key",
680 help = "Jira Project issue key",
681 required = true
682 )]
683 pub issue_key: String,
684 #[clap(flatten)]
686 pub output: OutputArgs,
687}
688
689#[derive(ValueEnum, Debug, Clone, Copy, Serialize, Deserialize, PartialEq)]
693#[value(rename_all = "kebab-case")]
694pub enum TransitionActionValues {
695 #[value(name = "list", help = "List Jira issue available transitions")]
697 List,
698}
699
700fn parse_key_val<T, U>(s: &str) -> Result<(T, U), Box<dyn Error + Send + Sync + 'static>>
703where
704 T: std::str::FromStr,
705 T::Err: Error + Send + Sync + 'static,
706 U: std::str::FromStr,
707 U::Err: Error + Send + Sync + 'static,
708{
709 let pos = s
710 .find('=')
711 .ok_or_else(|| format!("invalid KEY=value: no `=` found in `{s}`"))?;
712 Ok((
713 s[..pos].parse()?,
714 manage_jira_document_field(s[pos + 1..].to_string()).parse()?,
715 ))
716}
717
718fn manage_jira_document_field(value: String) -> String {
721 let re = Regex::new(r"^jira_doc_field\[(.+)\]$").unwrap();
722 let captures = re.captures(&value);
723 if let Some(captures) = &captures {
724 if let Some(first_match) = captures.get(1) {
725 jira_doc_std_field![first_match.as_str()].to_string()
726 } else {
727 value.to_string()
728 }
729 } else {
730 value.to_string()
731 }
732}
733
734#[cfg(test)]
735mod tests {
736 use super::*;
737
738 #[test]
739 fn test_parse_key_val_success() {
740 let result: Result<(String, String), _> = parse_key_val("key=value");
741 assert!(result.is_ok());
742 let (key, value) = result.unwrap();
743 assert_eq!(key, "key");
744 assert_eq!(value, "value");
745 }
746
747 #[test]
748 fn test_parse_key_val_with_jira_doc_field() {
749 let result: Result<(String, String), _> = parse_key_val("key=normal_value");
750 assert!(result.is_ok());
751 let (key, value) = result.unwrap();
752 assert_eq!(key, "key");
753 assert_eq!(value, "normal_value");
754 }
755
756 #[test]
757 fn test_parse_key_val_missing_equals() {
758 let result: Result<(String, String), _> = parse_key_val("invalid");
759 assert!(result.is_err());
760 }
761
762 #[test]
763 fn test_manage_jira_document_field_no_match() {
764 let result = manage_jira_document_field("normal_value".to_string());
765 assert_eq!(result, "normal_value");
766 }
767
768 #[test]
769 fn test_manage_jira_document_field_empty_brackets() {
770 let input = "jira_doc_field[]".to_string();
771 let result = manage_jira_document_field(input.clone());
772 assert_eq!(result, input);
774 }
775}