Compare commits
5 commits
86fe96f0cd
...
0c459e2770
Author | SHA1 | Date | |
---|---|---|---|
0c459e2770 | |||
8423cc1240 | |||
38c571362b | |||
8826103da7 | |||
4fbab7102a |
11 changed files with 325 additions and 143 deletions
87
Cargo.lock
generated
87
Cargo.lock
generated
|
@ -256,16 +256,6 @@ version = "1.0.3"
|
|||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "5b63caa9aa9397e2d9480a9b13673856c78d8ac123288526c37d7839f2a86990"
|
||||
|
||||
[[package]]
|
||||
name = "colored"
|
||||
version = "2.2.0"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "117725a109d387c937a1533ce01b450cbde6b88abceea8473c4d7a85853cda3c"
|
||||
dependencies = [
|
||||
"lazy_static",
|
||||
"windows-sys 0.59.0",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "config"
|
||||
version = "0.15.6"
|
||||
|
@ -339,6 +329,31 @@ dependencies = [
|
|||
"libc",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "crossterm"
|
||||
version = "0.28.1"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "829d955a0bb380ef178a640b91779e3987da38c9aea133b20614cfed8cdea9c6"
|
||||
dependencies = [
|
||||
"bitflags",
|
||||
"crossterm_winapi",
|
||||
"mio",
|
||||
"parking_lot",
|
||||
"rustix",
|
||||
"signal-hook",
|
||||
"signal-hook-mio",
|
||||
"winapi",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "crossterm_winapi"
|
||||
version = "0.9.1"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "acdd7c62a3665c7f6830a51635d9ac9b23ed385797f70a83bb8bafe9c572ab2b"
|
||||
dependencies = [
|
||||
"winapi",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "crunchy"
|
||||
version = "0.2.2"
|
||||
|
@ -920,8 +935,8 @@ version = "0.1.0"
|
|||
dependencies = [
|
||||
"chrono",
|
||||
"clap",
|
||||
"colored",
|
||||
"config",
|
||||
"crossterm",
|
||||
"directories",
|
||||
"gray_matter",
|
||||
"open",
|
||||
|
@ -955,12 +970,6 @@ dependencies = [
|
|||
"serde",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "lazy_static"
|
||||
version = "1.5.0"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "bbd2bcb4c963f2ddae06a2efc7e9f3591312473c50c6685e1f298068316e66fe"
|
||||
|
||||
[[package]]
|
||||
name = "libc"
|
||||
version = "0.2.169"
|
||||
|
@ -1033,6 +1042,7 @@ source = "registry+https://github.com/rust-lang/crates.io-index"
|
|||
checksum = "2886843bf800fba2e3377cff24abf6379b4c4d5c6681eaf9ea5b0d15090450bd"
|
||||
dependencies = [
|
||||
"libc",
|
||||
"log",
|
||||
"wasi",
|
||||
"windows-sys 0.52.0",
|
||||
]
|
||||
|
@ -1545,6 +1555,27 @@ version = "1.3.0"
|
|||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "0fda2ff0d084019ba4d7c6f371c95d8fd75ce3524c3cb8fb653a3023f6323e64"
|
||||
|
||||
[[package]]
|
||||
name = "signal-hook"
|
||||
version = "0.3.17"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "8621587d4798caf8eb44879d42e56b9a93ea5dcd315a6487c357130095b62801"
|
||||
dependencies = [
|
||||
"libc",
|
||||
"signal-hook-registry",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "signal-hook-mio"
|
||||
version = "0.2.4"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "34db1a06d485c9142248b7a054f034b349b212551f3dfd19c94d45a754a217cd"
|
||||
dependencies = [
|
||||
"libc",
|
||||
"mio",
|
||||
"signal-hook",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "signal-hook-registry"
|
||||
version = "1.4.2"
|
||||
|
@ -2053,6 +2084,28 @@ dependencies = [
|
|||
"wasm-bindgen",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "winapi"
|
||||
version = "0.3.9"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "5c839a674fcd7a98952e593242ea400abe93992746761e38641405d28b00f419"
|
||||
dependencies = [
|
||||
"winapi-i686-pc-windows-gnu",
|
||||
"winapi-x86_64-pc-windows-gnu",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "winapi-i686-pc-windows-gnu"
|
||||
version = "0.4.0"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "ac3b87c63620426dd9b991e5ce0329eff545bccbbb34f3be09ff6fb6ab51b7b6"
|
||||
|
||||
[[package]]
|
||||
name = "winapi-x86_64-pc-windows-gnu"
|
||||
version = "0.4.0"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "712e227841d057c1ee1cd2fb22fa7e5a5461ae8e48fa2ca79ec42cfc1931183f"
|
||||
|
||||
[[package]]
|
||||
name = "windows-core"
|
||||
version = "0.52.0"
|
||||
|
|
|
@ -9,7 +9,6 @@ reqwest = { version = "0.12", features = ["json"] }
|
|||
tokio = { version = "1.0", features = ["full"] }
|
||||
serde = { version = "1.0", features = ["derive"] }
|
||||
serde_json = "1.0"
|
||||
colored = "2.0"
|
||||
toml = "0.8"
|
||||
config = "0.15"
|
||||
directories = "6.0"
|
||||
|
@ -18,3 +17,4 @@ gray_matter = { version = "0.2", default-features = false, features = ["toml"] }
|
|||
open = "5.2"
|
||||
chrono = { version = "0.4", features = ["serde"] }
|
||||
tabwriter = "1.4"
|
||||
crossterm = "0.28"
|
34
readme.md
34
readme.md
|
@ -24,9 +24,10 @@ cargo install jirac
|
|||
Usage: jirac <COMMAND>
|
||||
|
||||
Commands:
|
||||
create Create a ticket
|
||||
list Find tickets currently assigned to you
|
||||
init Setup the configuration
|
||||
create Create an issue
|
||||
list Find issues currently assigned to you
|
||||
search Search for issues
|
||||
init Set up the configuration
|
||||
help Print this message or the help of the given subcommand(s)
|
||||
|
||||
Options:
|
||||
|
@ -68,7 +69,7 @@ jirac create ticket.md
|
|||
jirac create --project KEY
|
||||
```
|
||||
|
||||
## Listing tickets
|
||||
### Listing tickets
|
||||
|
||||
```
|
||||
Find tickets currently assigned to you
|
||||
|
@ -80,6 +81,29 @@ Options:
|
|||
-h, --help Print help
|
||||
```
|
||||
|
||||
### Search for tickets
|
||||
|
||||
```
|
||||
Search for issues
|
||||
|
||||
Usage: jirac search [OPTIONS] <JQL>
|
||||
|
||||
Arguments:
|
||||
<JQL> A JQL string
|
||||
|
||||
Options:
|
||||
--json Print JSON rather than pretty print
|
||||
-h, --help Print help
|
||||
```
|
||||
|
||||
Use [JQL](https://support.atlassian.com/jira-software-cloud/docs/use-advanced-search-with-jira-query-language-jql/) to search for Issues.
|
||||
|
||||
*Find all in-progress tickets in a project*
|
||||
|
||||
```
|
||||
jirac search 'project = KEY AND status = "In Progress" ORDER BY created DESC'
|
||||
```
|
||||
|
||||
## Configuration
|
||||
|
||||
Get the following information:
|
||||
|
@ -88,7 +112,7 @@ Get the following information:
|
|||
* You email
|
||||
* [An API key](https://id.atlassian.com/manage-profile/security/api-tokens)
|
||||
|
||||
Then run the the `jirac init` command
|
||||
Then run the `jirac init` command
|
||||
|
||||
```
|
||||
Setup the configuration
|
||||
|
|
24
src/cli.rs
24
src/cli.rs
|
@ -10,27 +10,37 @@ pub struct Cli {
|
|||
|
||||
#[derive(Subcommand)]
|
||||
pub enum Commands {
|
||||
/// Create a ticket
|
||||
/// Create an issue
|
||||
Create {
|
||||
/// The project key in which to create the ticket
|
||||
/// The project key in which to create the issue
|
||||
#[arg(long)]
|
||||
project: Option<String>,
|
||||
|
||||
/// Open the new ticket in a browser
|
||||
/// Open the new issue in a browser
|
||||
#[arg(long)]
|
||||
open: bool,
|
||||
|
||||
/// A markdown file
|
||||
/// A Markdown file
|
||||
#[arg(value_name = "MARKDOWN_FILE")]
|
||||
markdown_file: Option<PathBuf>,
|
||||
},
|
||||
/// Find tickets currently assigned to you
|
||||
/// Find issues currently assigned to you
|
||||
List {
|
||||
/// Print json rather than pretty print
|
||||
/// Print JSON rather than pretty print
|
||||
#[arg(long)]
|
||||
json: bool,
|
||||
},
|
||||
/// Setup the configuration
|
||||
/// Search for issues
|
||||
Search {
|
||||
/// Print JSON rather than pretty print
|
||||
#[arg(long)]
|
||||
json: bool,
|
||||
|
||||
/// A JQL string
|
||||
#[arg(value_name = "JQL")]
|
||||
jql: String,
|
||||
},
|
||||
/// Set up the configuration
|
||||
Init {
|
||||
/// Jira instance URL
|
||||
#[arg(long)]
|
||||
|
|
|
@ -1,2 +1,3 @@
|
|||
pub mod create;
|
||||
pub mod list;
|
||||
pub mod search;
|
||||
|
|
116
src/cmd/list.rs
116
src/cmd/list.rs
|
@ -1,115 +1,5 @@
|
|||
use crate::jira_config::JiraConfig;
|
||||
use colored::Colorize;
|
||||
use reqwest::header::{HeaderMap, HeaderValue, CONTENT_TYPE};
|
||||
use serde::{Deserialize, Serialize};
|
||||
use std::io::Write;
|
||||
|
||||
#[derive(Debug, Deserialize, Serialize)]
|
||||
struct JiraIssue {
|
||||
key: String,
|
||||
#[serde(rename = "self")]
|
||||
href: String,
|
||||
fields: JiraIssueResponseFields,
|
||||
}
|
||||
|
||||
#[derive(Debug, Deserialize, Serialize)]
|
||||
struct JiraIssueResponseFields {
|
||||
summary: String,
|
||||
status: Status,
|
||||
created: chrono::DateTime<chrono::Utc>,
|
||||
priority: Priority,
|
||||
assignee: Person,
|
||||
reporter: Person,
|
||||
creator: Person,
|
||||
#[serde(rename = "duedate")]
|
||||
due_date: Option<chrono::NaiveDate>,
|
||||
}
|
||||
|
||||
#[derive(Deserialize)]
|
||||
struct JiraSearchResponse {
|
||||
issues: Vec<JiraIssue>,
|
||||
total: u32,
|
||||
}
|
||||
|
||||
#[derive(Debug, Deserialize, Serialize)]
|
||||
struct Status {
|
||||
name: String,
|
||||
}
|
||||
|
||||
#[derive(Debug, Deserialize, Serialize)]
|
||||
struct Priority {
|
||||
name: String,
|
||||
id: String,
|
||||
}
|
||||
|
||||
#[derive(Debug, Deserialize, Serialize)]
|
||||
struct Person {
|
||||
#[serde(rename = "self")]
|
||||
href: String,
|
||||
#[serde(rename = "displayName")]
|
||||
display_name: String,
|
||||
#[serde(rename = "accountId")]
|
||||
account_id: String,
|
||||
#[serde(rename = "emailAddress")]
|
||||
email_address: String,
|
||||
}
|
||||
|
||||
async fn list_jira_issues(
|
||||
config: &JiraConfig,
|
||||
) -> Result<JiraSearchResponse, Box<dyn std::error::Error>> {
|
||||
let client = reqwest::Client::new();
|
||||
|
||||
let mut headers = HeaderMap::new();
|
||||
headers.insert(CONTENT_TYPE, HeaderValue::from_static("application/json"));
|
||||
|
||||
// JQL query to find issues assigned to the specified user
|
||||
let jql =
|
||||
"assignee = currentUser() AND resolution = Unresolved order by updated DESC".to_string();
|
||||
let query = [("jql", jql.as_str())];
|
||||
|
||||
let response = client
|
||||
.get(format!("{}/rest/api/2/search", config.url))
|
||||
.basic_auth(&config.email, Some(&config.api_token))
|
||||
.headers(headers)
|
||||
.query(&query)
|
||||
.send()
|
||||
.await?;
|
||||
|
||||
if !response.status().is_success() {
|
||||
let error_text = response.text().await?;
|
||||
return Err(format!("Failed to fetch issues: {}", error_text).into());
|
||||
}
|
||||
|
||||
Ok(response.json::<JiraSearchResponse>().await?)
|
||||
}
|
||||
|
||||
fn display_issues_pretty(issues: &[JiraIssue]) -> Result<(), Box<dyn std::error::Error>> {
|
||||
println!("Found {} issues:", issues.len());
|
||||
println!("{:-<80}", "");
|
||||
|
||||
for issue in issues {
|
||||
let mut tw = tabwriter::TabWriter::new(vec![]);
|
||||
writeln!(tw, "{}:\t{}", "Key".blue(), issue.key)?;
|
||||
writeln!(tw, "{}:\t{}", "Summary".blue(), issue.fields.summary)?;
|
||||
writeln!(tw, "{}:\t{}", "Status".blue(), issue.fields.status.name)?;
|
||||
writeln!(tw, "{}:\t{}", "Created".blue(), issue.fields.created)?;
|
||||
writeln!(tw, "{}:\t{:?}", "Due Date".blue(), issue.fields.due_date)?;
|
||||
writeln!(tw, "{}:\t{}", "URL".blue(), issue.href.underline())?;
|
||||
tw.flush().unwrap();
|
||||
|
||||
let written = String::from_utf8(tw.into_inner().unwrap()).unwrap();
|
||||
print!("{}", written);
|
||||
println!("{:-<80}", "");
|
||||
}
|
||||
|
||||
Ok(())
|
||||
}
|
||||
|
||||
fn display_issues_json(issues: &[JiraIssue]) -> Result<(), Box<dyn std::error::Error>> {
|
||||
let j = serde_json::to_string_pretty(issues)?;
|
||||
println!("{}", j);
|
||||
Ok(())
|
||||
}
|
||||
use crate::types::issue::{display_issues_json, display_issues_pretty};
|
||||
|
||||
pub async fn list(json: bool) -> Result<(), Box<dyn std::error::Error>> {
|
||||
let config = JiraConfig::load().map_err(|e| format!("Configuration error: {}", e))?;
|
||||
|
@ -117,7 +7,9 @@ pub async fn list(json: bool) -> Result<(), Box<dyn std::error::Error>> {
|
|||
println!("Fetching issues assigned...");
|
||||
}
|
||||
|
||||
match list_jira_issues(&config).await {
|
||||
let jql = "assignee = currentUser() AND resolution = Unresolved order by updated DESC";
|
||||
|
||||
match crate::jql::run(&config, jql).await {
|
||||
Ok(response) => {
|
||||
if json {
|
||||
if response.issues.is_empty() {
|
||||
|
|
32
src/cmd/search.rs
Normal file
32
src/cmd/search.rs
Normal file
|
@ -0,0 +1,32 @@
|
|||
use crate::jira_config::JiraConfig;
|
||||
use crate::types::issue::{display_issues_json, display_issues_pretty};
|
||||
|
||||
pub async fn exec(json: bool, jql: String) -> Result<(), Box<dyn std::error::Error>> {
|
||||
let config = JiraConfig::load().map_err(|e| format!("Configuration error: {}", e))?;
|
||||
if !json {
|
||||
println!("Searching for issues...");
|
||||
}
|
||||
|
||||
match crate::jql::run(&config, &jql).await {
|
||||
Ok(response) => {
|
||||
if json {
|
||||
if response.issues.is_empty() {
|
||||
println!("[]");
|
||||
} else {
|
||||
display_issues_json(&response.issues)?;
|
||||
}
|
||||
} else if response.issues.is_empty() {
|
||||
println!("No results found for query.");
|
||||
} else {
|
||||
display_issues_pretty(&response.issues)?;
|
||||
println!("Total issues: {}", response.total);
|
||||
}
|
||||
}
|
||||
Err(e) => {
|
||||
eprintln!("Error fetching issues: {}", e);
|
||||
std::process::exit(1);
|
||||
}
|
||||
}
|
||||
|
||||
Ok(())
|
||||
}
|
37
src/jql.rs
Normal file
37
src/jql.rs
Normal file
|
@ -0,0 +1,37 @@
|
|||
use crate::jira_config::JiraConfig;
|
||||
use crate::types::issue::JiraIssue;
|
||||
use reqwest::header::{HeaderMap, HeaderValue, CONTENT_TYPE};
|
||||
use serde::Deserialize;
|
||||
|
||||
#[derive(Deserialize)]
|
||||
pub struct JiraSearchResponse {
|
||||
pub issues: Vec<JiraIssue>,
|
||||
pub total: u32,
|
||||
}
|
||||
|
||||
pub async fn run(
|
||||
config: &JiraConfig,
|
||||
jql: &str,
|
||||
) -> Result<JiraSearchResponse, Box<dyn std::error::Error>> {
|
||||
let client = reqwest::Client::new();
|
||||
|
||||
let mut headers = HeaderMap::new();
|
||||
headers.insert(CONTENT_TYPE, HeaderValue::from_static("application/json"));
|
||||
|
||||
let query = [("jql", jql)];
|
||||
|
||||
let response = client
|
||||
.get(format!("{}/rest/api/2/search", config.url))
|
||||
.basic_auth(&config.email, Some(&config.api_token))
|
||||
.headers(headers)
|
||||
.query(&query)
|
||||
.send()
|
||||
.await?;
|
||||
|
||||
if !response.status().is_success() {
|
||||
let error_text = response.text().await?;
|
||||
return Err(format!("Failed to fetch issues: {}", error_text).into());
|
||||
}
|
||||
|
||||
Ok(response.json::<JiraSearchResponse>().await?)
|
||||
}
|
|
@ -1,6 +1,8 @@
|
|||
mod cli;
|
||||
mod cmd;
|
||||
mod jira_config;
|
||||
mod jql;
|
||||
mod types;
|
||||
|
||||
use clap::Parser;
|
||||
use cli::{Cli, Commands};
|
||||
|
@ -17,6 +19,7 @@ async fn main() -> Result<(), Box<dyn std::error::Error>> {
|
|||
markdown_file,
|
||||
} => cmd::create::create(project, open, markdown_file).await?,
|
||||
Commands::List { json } => cmd::list::list(json).await?,
|
||||
Commands::Search { json, jql } => cmd::search::exec(json, jql).await?,
|
||||
Commands::Init { url, email, token } => {
|
||||
JiraConfig::init(url, email, token).await?;
|
||||
println!("Configuration initialized successfully!");
|
||||
|
|
1
src/types.rs
Normal file
1
src/types.rs
Normal file
|
@ -0,0 +1 @@
|
|||
pub mod issue;
|
129
src/types/issue.rs
Normal file
129
src/types/issue.rs
Normal file
|
@ -0,0 +1,129 @@
|
|||
use crossterm::style::Stylize;
|
||||
use serde::{Deserialize, Serialize};
|
||||
use std::io::Write;
|
||||
|
||||
#[derive(Debug, Deserialize, Serialize)]
|
||||
pub struct JiraIssue {
|
||||
pub key: String,
|
||||
#[serde(rename = "self")]
|
||||
pub href: String,
|
||||
pub fields: JiraIssueResponseFields,
|
||||
}
|
||||
|
||||
#[derive(Debug, Deserialize, Serialize)]
|
||||
pub struct JiraIssueResponseFields {
|
||||
pub summary: String,
|
||||
pub status: Status,
|
||||
pub created: chrono::DateTime<chrono::Utc>,
|
||||
pub priority: Priority,
|
||||
pub assignee: Person,
|
||||
pub reporter: Person,
|
||||
pub creator: Person,
|
||||
#[serde(rename = "duedate")]
|
||||
pub due_date: Option<chrono::NaiveDate>,
|
||||
pub comment: Option<Comments>,
|
||||
pub votes: Votes,
|
||||
}
|
||||
|
||||
#[derive(Debug, Deserialize, Serialize)]
|
||||
pub struct Status {
|
||||
pub name: String,
|
||||
}
|
||||
|
||||
#[derive(Debug, Deserialize, Serialize)]
|
||||
pub struct Priority {
|
||||
pub name: String,
|
||||
pub id: String,
|
||||
}
|
||||
|
||||
#[derive(Debug, Deserialize, Serialize)]
|
||||
pub struct Person {
|
||||
#[serde(rename = "self")]
|
||||
pub href: String,
|
||||
#[serde(rename = "displayName")]
|
||||
pub display_name: String,
|
||||
#[serde(rename = "accountId")]
|
||||
pub account_id: String,
|
||||
#[serde(rename = "emailAddress")]
|
||||
pub email_address: Option<String>,
|
||||
}
|
||||
|
||||
#[derive(Debug, Deserialize, Serialize)]
|
||||
pub struct Comments {
|
||||
#[serde(rename = "self")]
|
||||
pub href: String,
|
||||
pub total: u32,
|
||||
#[serde(rename = "maxResults")]
|
||||
pub max_results: u32,
|
||||
#[serde(rename = "startAt")]
|
||||
pub start_at: u32,
|
||||
pub comments: Vec<Comment>,
|
||||
}
|
||||
|
||||
#[derive(Debug, Deserialize, Serialize)]
|
||||
pub struct Comment {
|
||||
#[serde(rename = "self")]
|
||||
pub href: String,
|
||||
pub id: String,
|
||||
pub author: Person,
|
||||
pub body: String,
|
||||
#[serde(rename = "updateAuthor")]
|
||||
pub update_author: Person,
|
||||
}
|
||||
|
||||
#[derive(Debug, Deserialize, Serialize)]
|
||||
pub struct Votes {
|
||||
#[serde(rename = "self")]
|
||||
pub href: String,
|
||||
#[serde(rename = "votes")]
|
||||
pub count: i32,
|
||||
#[serde(rename = "hasVoted")]
|
||||
pub has_voted: bool,
|
||||
}
|
||||
|
||||
pub fn display_issues_pretty(issues: &[JiraIssue]) -> Result<(), Box<dyn std::error::Error>> {
|
||||
println!("Found {} issues:", issues.len());
|
||||
println!("{:-<80}", "");
|
||||
|
||||
for issue in issues {
|
||||
let mut tw = tabwriter::TabWriter::new(vec![]);
|
||||
writeln!(tw, "{}:\t{}", "Key".blue(), issue.key)?;
|
||||
writeln!(tw, "{}:\t{}", "Summary".blue(), issue.fields.summary)?;
|
||||
writeln!(tw, "{}:\t{}", "Status".blue(), issue.fields.status.name)?;
|
||||
writeln!(
|
||||
tw,
|
||||
"{}:\t{}",
|
||||
"Created".blue(),
|
||||
issue.fields.created.with_timezone(&chrono::Local)
|
||||
)?;
|
||||
writeln!(
|
||||
tw,
|
||||
"{}:\t{}",
|
||||
"Due Date".blue(),
|
||||
match issue.fields.due_date {
|
||||
None => "None".to_string(),
|
||||
Some(x) => x.to_string(),
|
||||
}
|
||||
)?;
|
||||
writeln!(
|
||||
tw,
|
||||
"\u{1b}]8;;{}\u{7}{}\u{1b}]8;;\u{7}",
|
||||
issue.href,
|
||||
"Open Issue".green()
|
||||
)?;
|
||||
|
||||
tw.flush().unwrap();
|
||||
|
||||
let written = String::from_utf8(tw.into_inner().unwrap()).unwrap();
|
||||
print!("{}", written);
|
||||
println!("{:-<80}", "");
|
||||
}
|
||||
|
||||
Ok(())
|
||||
}
|
||||
|
||||
pub fn display_issues_json(issues: &[JiraIssue]) -> Result<(), Box<dyn std::error::Error>> {
|
||||
let j = serde_json::to_string_pretty(issues)?;
|
||||
println!("{}", j);
|
||||
Ok(())
|
||||
}
|
Loading…
Reference in a new issue