Add a web extension and debian package compilation #5
1
Cargo.lock
generated
1
Cargo.lock
generated
@@ -182,6 +182,7 @@ dependencies = [
|
|||||||
"open",
|
"open",
|
||||||
"reqwest",
|
"reqwest",
|
||||||
"serde",
|
"serde",
|
||||||
|
"serde_json",
|
||||||
"simple-home-dir",
|
"simple-home-dir",
|
||||||
"tokio",
|
"tokio",
|
||||||
"toml",
|
"toml",
|
||||||
|
|||||||
@@ -5,6 +5,14 @@ edition = "2021"
|
|||||||
|
|
||||||
# See more keys and their definitions at https://doc.rust-lang.org/cargo/reference/manifest.html
|
# See more keys and their definitions at https://doc.rust-lang.org/cargo/reference/manifest.html
|
||||||
|
|
||||||
|
[[bin]]
|
||||||
|
name="cl-cli"
|
||||||
|
path="src/main.rs"
|
||||||
|
|
||||||
|
[[bin]]
|
||||||
|
name="webext"
|
||||||
|
path="src/webext.rs"
|
||||||
|
|
||||||
[dependencies]
|
[dependencies]
|
||||||
clap = { version = "4.1.13", features = ["derive"] }
|
clap = { version = "4.1.13", features = ["derive"] }
|
||||||
gitlab = "0.1804.0"
|
gitlab = "0.1804.0"
|
||||||
@@ -16,3 +24,4 @@ tokio = { version = "1.0.0", features = ["rt", "rt-multi-thread", "macros"] }
|
|||||||
simple-home-dir = "0.5.2"
|
simple-home-dir = "0.5.2"
|
||||||
log = "0.4.28"
|
log = "0.4.28"
|
||||||
open = "5.3.2"
|
open = "5.3.2"
|
||||||
|
serde_json = "1.0.145"
|
||||||
|
|||||||
@@ -3,7 +3,7 @@ use std::path::PathBuf;
|
|||||||
use clap::{Args, Parser, Subcommand};
|
use clap::{Args, Parser, Subcommand};
|
||||||
|
|
||||||
#[derive(Parser)]
|
#[derive(Parser)]
|
||||||
pub(crate) struct Cli {
|
pub struct Cli {
|
||||||
#[arg(short, long, value_name = "FILE")]
|
#[arg(short, long, value_name = "FILE")]
|
||||||
pub config: Option<PathBuf>,
|
pub config: Option<PathBuf>,
|
||||||
|
|
||||||
@@ -12,19 +12,19 @@ pub(crate) struct Cli {
|
|||||||
}
|
}
|
||||||
|
|
||||||
#[derive(Subcommand)]
|
#[derive(Subcommand)]
|
||||||
pub(crate) enum Commands {
|
pub enum Commands {
|
||||||
#[command(subcommand)]
|
#[command(subcommand)]
|
||||||
Planning(Planning),
|
Planning(Planning),
|
||||||
Test,
|
Test,
|
||||||
}
|
}
|
||||||
|
|
||||||
#[derive(Subcommand)]
|
#[derive(Subcommand)]
|
||||||
pub(crate) enum Planning {
|
pub enum Planning {
|
||||||
I2work(Issue2Work),
|
I2work(Issue2Work),
|
||||||
}
|
}
|
||||||
|
|
||||||
#[derive(Args, Debug)]
|
#[derive(Args, Debug)]
|
||||||
pub(crate) struct Issue2Work {
|
pub struct Issue2Work {
|
||||||
pub issue_url: String,
|
pub issue_url: String,
|
||||||
pub project_id: String,
|
pub project_id: String,
|
||||||
#[arg(short, long)]
|
#[arg(short, long)]
|
||||||
|
|||||||
@@ -1,26 +1,59 @@
|
|||||||
|
use crate::error::GeneralError;
|
||||||
use serde::Deserialize;
|
use serde::Deserialize;
|
||||||
|
use std::fs;
|
||||||
|
use std::path::Path;
|
||||||
|
|
||||||
#[derive(Deserialize, Debug)]
|
#[derive(Deserialize, Debug)]
|
||||||
pub(crate) struct Config {
|
pub struct Config {
|
||||||
pub gitlab: Vec<GitlabConfig>,
|
pub gitlab: Vec<GitlabConfig>,
|
||||||
pub gitea: Vec<GiteaConfig>,
|
pub gitea: Vec<GiteaConfig>,
|
||||||
pub openproject: OpenProjectConfig,
|
pub openproject: OpenProjectConfig,
|
||||||
}
|
}
|
||||||
|
|
||||||
#[derive(Deserialize, Debug)]
|
#[derive(Deserialize, Debug)]
|
||||||
pub(crate) struct GitlabConfig {
|
pub struct GitlabConfig {
|
||||||
pub token: String,
|
pub token: String,
|
||||||
pub domain: String,
|
pub domain: String,
|
||||||
}
|
}
|
||||||
|
|
||||||
#[derive(Deserialize, Debug)]
|
#[derive(Deserialize, Debug)]
|
||||||
pub(crate) struct GiteaConfig {
|
pub struct GiteaConfig {
|
||||||
pub token: String,
|
pub token: String,
|
||||||
pub domain: String,
|
pub domain: String,
|
||||||
}
|
}
|
||||||
|
|
||||||
#[derive(Deserialize, Debug)]
|
#[derive(Deserialize, Debug)]
|
||||||
pub(crate) struct OpenProjectConfig {
|
pub struct OpenProjectConfig {
|
||||||
pub token: String,
|
pub token: String,
|
||||||
pub base_url: String,
|
pub base_url: String,
|
||||||
}
|
}
|
||||||
|
|
||||||
|
pub struct BuildConfigError {
|
||||||
|
msg: String,
|
||||||
|
}
|
||||||
|
|
||||||
|
impl Into<GeneralError> for BuildConfigError {
|
||||||
|
fn into(self) -> GeneralError {
|
||||||
|
GeneralError {
|
||||||
|
description: format!("Could not build config: {}", self.msg),
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
pub fn build_config(config_path: &Path) -> Result<Config, BuildConfigError> {
|
||||||
|
let config_path_content = match fs::read_to_string(config_path) {
|
||||||
|
Ok(content) => content,
|
||||||
|
Err(e) => {
|
||||||
|
return Err(BuildConfigError {
|
||||||
|
msg: format!(
|
||||||
|
"Could not read a config file at {:?}, error: {}",
|
||||||
|
config_path, e
|
||||||
|
),
|
||||||
|
});
|
||||||
|
}
|
||||||
|
};
|
||||||
|
|
||||||
|
let config: Config = toml::from_str(&*config_path_content).expect("Could not parse config");
|
||||||
|
|
||||||
|
Ok(config)
|
||||||
|
}
|
||||||
|
|||||||
@@ -3,7 +3,7 @@ use crate::error::GeneralError;
|
|||||||
use crate::gitea::issue::Issue;
|
use crate::gitea::issue::Issue;
|
||||||
use url::Url;
|
use url::Url;
|
||||||
|
|
||||||
pub(crate) async fn debug(config: Config) -> Result<(), GeneralError> {
|
pub async fn debug(config: Config) -> Result<(), GeneralError> {
|
||||||
println!("test");
|
println!("test");
|
||||||
|
|
||||||
let gitea_client = crate::gitea::client::Client::from_config(config.gitea.first().unwrap());
|
let gitea_client = crate::gitea::client::Client::from_config(config.gitea.first().unwrap());
|
||||||
|
|||||||
@@ -3,7 +3,7 @@ use reqwest::Error;
|
|||||||
|
|
||||||
#[derive(Debug)]
|
#[derive(Debug)]
|
||||||
pub struct GeneralError {
|
pub struct GeneralError {
|
||||||
pub(crate) description: String,
|
pub description: String,
|
||||||
}
|
}
|
||||||
|
|
||||||
impl From<InvalidHeaderValue> for GeneralError {
|
impl From<InvalidHeaderValue> for GeneralError {
|
||||||
|
|||||||
@@ -7,12 +7,18 @@ use crate::openproject::user::{GetMe, User};
|
|||||||
use crate::openproject::work::WorkPackageWriter;
|
use crate::openproject::work::WorkPackageWriter;
|
||||||
use crate::planning::utils::{append_related_issues, IssueRelated};
|
use crate::planning::utils::{append_related_issues, IssueRelated};
|
||||||
use crate::planning::Issue2WorkActionTrait;
|
use crate::planning::Issue2WorkActionTrait;
|
||||||
|
use crate::planning::Issue2WorkResult;
|
||||||
use url::Url;
|
use url::Url;
|
||||||
|
|
||||||
pub(crate) struct GiteaAction {}
|
pub(crate) struct GiteaAction {}
|
||||||
|
|
||||||
impl Issue2WorkActionTrait for GiteaAction {
|
impl Issue2WorkActionTrait for GiteaAction {
|
||||||
async fn run(&self, url: &Url, config: &Config, args: &Issue2Work) -> Result<(), GeneralError> {
|
async fn run(
|
||||||
|
&self,
|
||||||
|
url: &Url,
|
||||||
|
config: &Config,
|
||||||
|
args: &Issue2Work,
|
||||||
|
) -> Result<Issue2WorkResult, GeneralError> {
|
||||||
let gitea_client = crate::gitea::client::Client::from_config(config.gitea.first().unwrap());
|
let gitea_client = crate::gitea::client::Client::from_config(config.gitea.first().unwrap());
|
||||||
let issue: Issue = gitea_client.get(issue_html_url_to_api(url)?).await?;
|
let issue: Issue = gitea_client.get(issue_html_url_to_api(url)?).await?;
|
||||||
let open_project_client =
|
let open_project_client =
|
||||||
@@ -49,16 +55,10 @@ impl Issue2WorkActionTrait for GiteaAction {
|
|||||||
)
|
)
|
||||||
.await?;
|
.await?;
|
||||||
|
|
||||||
println!(
|
Ok(Issue2WorkResult {
|
||||||
"new work package created: {:?}, edit at {}",
|
work_package_url: url_wp,
|
||||||
work_package.subject, url_wp
|
subject: work_package.subject,
|
||||||
);
|
})
|
||||||
|
|
||||||
if let Err(e) = open::that(url_wp) {
|
|
||||||
println!("failed to open work package in browser: {}", e);
|
|
||||||
};
|
|
||||||
|
|
||||||
Ok(())
|
|
||||||
}
|
}
|
||||||
|
|
||||||
fn supports(&self, url: &Url, config: &Config, _args: &Issue2Work) -> bool {
|
fn supports(&self, url: &Url, config: &Config, _args: &Issue2Work) -> bool {
|
||||||
|
|||||||
@@ -7,6 +7,7 @@ use crate::openproject::client::Client;
|
|||||||
use crate::openproject::user::{GetMe, User};
|
use crate::openproject::user::{GetMe, User};
|
||||||
use crate::openproject::work::WorkPackageWriterAssignee;
|
use crate::openproject::work::WorkPackageWriterAssignee;
|
||||||
use crate::planning::Issue2WorkActionTrait;
|
use crate::planning::Issue2WorkActionTrait;
|
||||||
|
use crate::planning::Issue2WorkResult;
|
||||||
use gitlab::api::{issues, projects, AsyncQuery};
|
use gitlab::api::{issues, projects, AsyncQuery};
|
||||||
use url::Url;
|
use url::Url;
|
||||||
|
|
||||||
@@ -26,7 +27,12 @@ struct Issue2WorkPackageDTO {
|
|||||||
pub(crate) struct GitlabAction {}
|
pub(crate) struct GitlabAction {}
|
||||||
|
|
||||||
impl Issue2WorkActionTrait for GitlabAction {
|
impl Issue2WorkActionTrait for GitlabAction {
|
||||||
async fn run(&self, url: &Url, config: &Config, args: &Issue2Work) -> Result<(), GeneralError> {
|
async fn run(
|
||||||
|
&self,
|
||||||
|
url: &Url,
|
||||||
|
config: &Config,
|
||||||
|
args: &Issue2Work,
|
||||||
|
) -> Result<Issue2WorkResult, GeneralError> {
|
||||||
let client = client_for_url(&url, &config).await?;
|
let client = client_for_url(&url, &config).await?;
|
||||||
let data = extract_issue_info(&url).unwrap();
|
let data = extract_issue_info(&url).unwrap();
|
||||||
|
|
||||||
@@ -65,12 +71,15 @@ impl Issue2WorkActionTrait for GitlabAction {
|
|||||||
.create_work_package(&(&dto).into(), &args.project_id)
|
.create_work_package(&(&dto).into(), &args.project_id)
|
||||||
.await?;
|
.await?;
|
||||||
|
|
||||||
println!(
|
let result = Issue2WorkResult {
|
||||||
"new work package created: {:?}, edit at {}/projects/{}/work_packages/{}",
|
work_package_url: format!(
|
||||||
work_package.subject, config.openproject.base_url, args.project_id, work_package.id
|
"{}/projects/{}/work_packages/{}",
|
||||||
);
|
config.openproject.base_url, args.project_id, work_package.id
|
||||||
|
),
|
||||||
|
subject: work_package.subject,
|
||||||
|
};
|
||||||
|
|
||||||
Ok(())
|
Ok(result)
|
||||||
}
|
}
|
||||||
|
|
||||||
fn supports(&self, url: &Url, config: &Config, _args: &Issue2Work) -> bool {
|
fn supports(&self, url: &Url, config: &Config, _args: &Issue2Work) -> bool {
|
||||||
|
|||||||
@@ -6,6 +6,7 @@ pub struct Issue {
|
|||||||
pub title: String,
|
pub title: String,
|
||||||
pub web_url: String,
|
pub web_url: String,
|
||||||
pub project_id: u64,
|
pub project_id: u64,
|
||||||
|
pub description: String,
|
||||||
}
|
}
|
||||||
|
|
||||||
#[derive(Clone, Debug, Deserialize)]
|
#[derive(Clone, Debug, Deserialize)]
|
||||||
|
|||||||
9
src/lib.rs
Normal file
9
src/lib.rs
Normal file
@@ -0,0 +1,9 @@
|
|||||||
|
// Regroupe la logique commune accessible par les deux binaires
|
||||||
|
pub mod cli;
|
||||||
|
pub mod config;
|
||||||
|
pub mod debug;
|
||||||
|
pub mod error;
|
||||||
|
pub mod gitea;
|
||||||
|
pub mod gitlab;
|
||||||
|
pub mod openproject;
|
||||||
|
pub mod planning;
|
||||||
37
src/main.rs
37
src/main.rs
@@ -3,22 +3,11 @@ extern crate reqwest;
|
|||||||
extern crate serde;
|
extern crate serde;
|
||||||
extern crate simple_home_dir;
|
extern crate simple_home_dir;
|
||||||
|
|
||||||
mod cli;
|
use cl_cli::cli::Cli;
|
||||||
mod config;
|
use cl_cli::cli::Commands::{Planning, Test};
|
||||||
mod debug;
|
use cl_cli::cli::Planning::I2work;
|
||||||
mod error;
|
use cl_cli::error::GeneralError;
|
||||||
mod gitea;
|
|
||||||
mod gitlab;
|
|
||||||
mod openproject;
|
|
||||||
mod planning;
|
|
||||||
|
|
||||||
use crate::cli::Commands::{Planning, Test};
|
|
||||||
use crate::cli::Planning::I2work;
|
|
||||||
use crate::error::GeneralError;
|
|
||||||
use clap::Parser;
|
use clap::Parser;
|
||||||
use cli::Cli;
|
|
||||||
use config::Config;
|
|
||||||
use std::fs;
|
|
||||||
use std::path::PathBuf;
|
use std::path::PathBuf;
|
||||||
use std::process::exit;
|
use std::process::exit;
|
||||||
|
|
||||||
@@ -34,22 +23,20 @@ async fn main() {
|
|||||||
None => &default_config_path,
|
None => &default_config_path,
|
||||||
};
|
};
|
||||||
|
|
||||||
let config_path_content = match fs::read_to_string(config_path) {
|
let config = match cl_cli::config::build_config(config_path) {
|
||||||
Ok(content) => content,
|
Ok(c) => c,
|
||||||
Err(e) => {
|
Err(e) => {
|
||||||
println!(
|
let general_error: GeneralError = e.into();
|
||||||
"Could not read config file at {:?}, error: {}",
|
println!("{}", general_error.description);
|
||||||
config_path, e
|
|
||||||
);
|
|
||||||
exit(1);
|
exit(1);
|
||||||
}
|
}
|
||||||
};
|
};
|
||||||
|
|
||||||
let config: Config = toml::from_str(&*config_path_content).expect("Could not parse config");
|
|
||||||
|
|
||||||
let result = match cli.command {
|
let result = match cli.command {
|
||||||
Some(Planning(I2work(args))) => planning::issue2work::issue2work(config, &args).await,
|
Some(Planning(I2work(args))) => {
|
||||||
Some(Test) => debug::debug(config).await,
|
cl_cli::planning::issue2work::issue2work_cli(config, &args).await
|
||||||
|
}
|
||||||
|
Some(Test) => cl_cli::debug::debug(config).await,
|
||||||
None => Err(GeneralError {
|
None => Err(GeneralError {
|
||||||
description: "No command launched".to_string(),
|
description: "No command launched".to_string(),
|
||||||
}),
|
}),
|
||||||
|
|||||||
@@ -3,7 +3,7 @@ use crate::config::Config;
|
|||||||
use crate::error::GeneralError;
|
use crate::error::GeneralError;
|
||||||
use crate::gitea::action::GiteaAction;
|
use crate::gitea::action::GiteaAction;
|
||||||
use crate::gitlab::action::GitlabAction;
|
use crate::gitlab::action::GitlabAction;
|
||||||
use crate::planning::Issue2WorkActionTrait;
|
use crate::planning::{Issue2WorkActionTrait, Issue2WorkResult};
|
||||||
use url::Url;
|
use url::Url;
|
||||||
|
|
||||||
struct App {
|
struct App {
|
||||||
@@ -11,20 +11,44 @@ struct App {
|
|||||||
gitea_issue2work_action: GiteaAction,
|
gitea_issue2work_action: GiteaAction,
|
||||||
}
|
}
|
||||||
|
|
||||||
pub(crate) async fn issue2work(config: Config, args: &Issue2Work) -> Result<(), GeneralError> {
|
pub async fn handle_issue2work(
|
||||||
|
config: Config,
|
||||||
|
args: &Issue2Work,
|
||||||
|
) -> Result<Issue2WorkResult, GeneralError> {
|
||||||
let url = Url::parse(&*args.issue_url).expect("issue_url is not valid");
|
let url = Url::parse(&*args.issue_url).expect("issue_url is not valid");
|
||||||
let app = App {
|
let app = App {
|
||||||
gitlab_issue2work_action: GitlabAction {},
|
gitlab_issue2work_action: GitlabAction {},
|
||||||
gitea_issue2work_action: GiteaAction {},
|
gitea_issue2work_action: GiteaAction {},
|
||||||
};
|
};
|
||||||
|
|
||||||
|
let result: Issue2WorkResult;
|
||||||
if app.gitlab_issue2work_action.supports(&url, &config, args) {
|
if app.gitlab_issue2work_action.supports(&url, &config, args) {
|
||||||
app.gitlab_issue2work_action.run(&url, &config, args).await
|
result = app
|
||||||
|
.gitlab_issue2work_action
|
||||||
|
.run(&url, &config, args)
|
||||||
|
.await?;
|
||||||
} else if app.gitea_issue2work_action.supports(&url, &config, args) {
|
} else if app.gitea_issue2work_action.supports(&url, &config, args) {
|
||||||
app.gitea_issue2work_action.run(&url, &config, args).await
|
result = app.gitea_issue2work_action.run(&url, &config, args).await?
|
||||||
} else {
|
} else {
|
||||||
Err(GeneralError {
|
return Err(GeneralError {
|
||||||
description: format!("This action is not supported for this url: {}", url),
|
description: format!("This action is not supported for this url: {}", url),
|
||||||
})
|
});
|
||||||
}
|
}
|
||||||
|
|
||||||
|
Ok(result)
|
||||||
|
}
|
||||||
|
|
||||||
|
pub async fn issue2work_cli(config: Config, args: &Issue2Work) -> Result<(), GeneralError> {
|
||||||
|
let result = handle_issue2work(config, args).await?;
|
||||||
|
|
||||||
|
println!(
|
||||||
|
"new work package created: {:?}, edit at {}",
|
||||||
|
result.subject, result.work_package_url
|
||||||
|
);
|
||||||
|
|
||||||
|
if let Err(e) = open::that(result.work_package_url.as_str()) {
|
||||||
|
println!("failed to open work package in browser: {}", e);
|
||||||
|
};
|
||||||
|
|
||||||
|
Ok(())
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -3,11 +3,21 @@ use crate::config::Config;
|
|||||||
use crate::error::GeneralError;
|
use crate::error::GeneralError;
|
||||||
use url::Url;
|
use url::Url;
|
||||||
|
|
||||||
pub(crate) mod issue2work;
|
pub mod issue2work;
|
||||||
pub mod utils;
|
pub mod utils;
|
||||||
|
|
||||||
pub trait Issue2WorkActionTrait {
|
pub trait Issue2WorkActionTrait {
|
||||||
async fn run(&self, url: &Url, config: &Config, args: &Issue2Work) -> Result<(), GeneralError>;
|
fn run(
|
||||||
|
&self,
|
||||||
|
url: &Url,
|
||||||
|
config: &Config,
|
||||||
|
args: &Issue2Work,
|
||||||
|
) -> impl std::future::Future<Output = Result<Issue2WorkResult, GeneralError>> + Send;
|
||||||
|
|
||||||
fn supports(&self, url: &Url, config: &Config, args: &Issue2Work) -> bool;
|
fn supports(&self, url: &Url, config: &Config, args: &Issue2Work) -> bool;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
pub struct Issue2WorkResult {
|
||||||
|
pub work_package_url: String,
|
||||||
|
pub subject: String,
|
||||||
|
}
|
||||||
|
|||||||
163
src/webext.rs
Normal file
163
src/webext.rs
Normal file
@@ -0,0 +1,163 @@
|
|||||||
|
use cl_cli::cli::Issue2Work as Issue2WorkCli;
|
||||||
|
use cl_cli::config::build_config;
|
||||||
|
use cl_cli::error::GeneralError;
|
||||||
|
use cl_cli::planning::issue2work::handle_issue2work;
|
||||||
|
use serde::{Deserialize, Serialize};
|
||||||
|
use std::io::{self, Read, Write};
|
||||||
|
use std::path::PathBuf;
|
||||||
|
use std::process;
|
||||||
|
|
||||||
|
#[derive(Debug, Deserialize)]
|
||||||
|
#[serde(tag = "type", content = "data")]
|
||||||
|
enum InputMessage {
|
||||||
|
Issue2Work(Issue2WorkWebExtMessage),
|
||||||
|
}
|
||||||
|
|
||||||
|
#[derive(Debug, Serialize)]
|
||||||
|
enum OutputContent {
|
||||||
|
Issue2Work(OutputIssue2Work),
|
||||||
|
}
|
||||||
|
|
||||||
|
#[derive(Debug, Serialize)]
|
||||||
|
#[serde(tag = "result", content = "data")]
|
||||||
|
enum OutputMessage {
|
||||||
|
Ok(OutputContent),
|
||||||
|
Error(String),
|
||||||
|
}
|
||||||
|
|
||||||
|
/// Input message for creating an issue.
|
||||||
|
#[derive(Debug, Deserialize)]
|
||||||
|
struct Issue2WorkWebExtMessage {
|
||||||
|
url: String,
|
||||||
|
project: String,
|
||||||
|
}
|
||||||
|
|
||||||
|
/// Output message returned to the web extension.
|
||||||
|
#[derive(Debug, Serialize)]
|
||||||
|
struct OutputIssue2Work {
|
||||||
|
created: String,
|
||||||
|
}
|
||||||
|
|
||||||
|
/// Reads a *raw* message from stdin (Native Messaging format).
|
||||||
|
/// - first reads 4 bytes (length)
|
||||||
|
/// - then reads exactly `len` bytes into a Vec<u8>
|
||||||
|
/// Returns Ok(None) if EOF (no more data).
|
||||||
|
fn read_message_bytes(stdin: &mut io::StdinLock<'_>) -> io::Result<Option<Vec<u8>>> {
|
||||||
|
let mut len_buf = [0u8; 4];
|
||||||
|
|
||||||
|
// Lire les 4 octets de longueur
|
||||||
|
match stdin.read_exact(&mut len_buf) {
|
||||||
|
Ok(()) => {}
|
||||||
|
Err(e) if e.kind() == io::ErrorKind::UnexpectedEof => {
|
||||||
|
// Plus de données : fin normale
|
||||||
|
return Ok(None);
|
||||||
|
}
|
||||||
|
Err(e) => return Err(e),
|
||||||
|
}
|
||||||
|
|
||||||
|
// Longueur annoncée (endianness native, comme dans la spec)
|
||||||
|
let len = u32::from_ne_bytes(len_buf) as usize;
|
||||||
|
|
||||||
|
// Ici, on peut recevoir un *nombre arbitraire d'octets* (dans la limite de u32).
|
||||||
|
let mut buf = vec![0u8; len];
|
||||||
|
stdin.read_exact(&mut buf)?;
|
||||||
|
|
||||||
|
Ok(Some(buf))
|
||||||
|
}
|
||||||
|
|
||||||
|
/// Write a JSON message to stdout with a header of 4 bytes.
|
||||||
|
fn write_json_message<T: serde::Serialize>(
|
||||||
|
stdout: &mut io::StdoutLock<'_>,
|
||||||
|
msg: &T,
|
||||||
|
) -> io::Result<()> {
|
||||||
|
let data =
|
||||||
|
serde_json::to_vec(msg).map_err(|e| io::Error::new(io::ErrorKind::InvalidData, e))?;
|
||||||
|
|
||||||
|
let len = data.len() as u32;
|
||||||
|
let len_buf = len.to_ne_bytes();
|
||||||
|
|
||||||
|
stdout.write_all(&len_buf)?;
|
||||||
|
stdout.write_all(&data)?;
|
||||||
|
stdout.flush()?;
|
||||||
|
|
||||||
|
Ok(())
|
||||||
|
}
|
||||||
|
|
||||||
|
/// "Logique métier" : construit une URL "created" à partir de l'entrée.
|
||||||
|
async fn handle_business_logic(input: InputMessage) -> Result<OutputContent, GeneralError> {
|
||||||
|
let mut default_config_path = PathBuf::new();
|
||||||
|
//default_config_path.push(simple_home_dir::home_dir().unwrap());
|
||||||
|
//default_config_path.push(".config/cl-cli/config.toml");
|
||||||
|
default_config_path.push("/tmp/cl-cli/config.toml");
|
||||||
|
let config = match build_config(&default_config_path) {
|
||||||
|
Ok(c) => c,
|
||||||
|
Err(e) => {
|
||||||
|
return Err(e.into());
|
||||||
|
}
|
||||||
|
};
|
||||||
|
|
||||||
|
let result = match input {
|
||||||
|
InputMessage::Issue2Work(i2w) => {
|
||||||
|
let arg = Issue2WorkCli {
|
||||||
|
issue_url: i2w.url,
|
||||||
|
project_id: i2w.project,
|
||||||
|
assign_to_me: false,
|
||||||
|
};
|
||||||
|
let result = handle_issue2work(config, &arg).await?;
|
||||||
|
|
||||||
|
OutputContent::Issue2Work(OutputIssue2Work {
|
||||||
|
created: result.work_package_url,
|
||||||
|
})
|
||||||
|
}
|
||||||
|
};
|
||||||
|
|
||||||
|
Ok(result)
|
||||||
|
}
|
||||||
|
|
||||||
|
#[tokio::main]
|
||||||
|
async fn main() {
|
||||||
|
eprintln!("Native host Rust démarré.");
|
||||||
|
|
||||||
|
let stdin = io::stdin();
|
||||||
|
let mut stdin_lock = stdin.lock();
|
||||||
|
let stdout = io::stdout();
|
||||||
|
let mut stdout_lock = stdout.lock();
|
||||||
|
|
||||||
|
loop {
|
||||||
|
match read_message_bytes(&mut stdin_lock) {
|
||||||
|
Ok(Some(raw_bytes)) => {
|
||||||
|
eprintln!("Reçu {} octets depuis l'extension.", raw_bytes.len());
|
||||||
|
|
||||||
|
// On parse le JSON *après* avoir lu tous les octets.
|
||||||
|
let input: InputMessage = match serde_json::from_slice(&raw_bytes) {
|
||||||
|
Ok(v) => v,
|
||||||
|
Err(e) => {
|
||||||
|
eprintln!("JSON invalide reçu: {e}");
|
||||||
|
// Option : renvoyer une erreur JSON au lieu d'abandonner
|
||||||
|
continue;
|
||||||
|
}
|
||||||
|
};
|
||||||
|
|
||||||
|
// Logique métier -> OutputMessage
|
||||||
|
let output = match handle_business_logic(input).await {
|
||||||
|
Ok(v) => OutputMessage::Ok(v),
|
||||||
|
Err(e) => OutputMessage::Error(e.description),
|
||||||
|
};
|
||||||
|
|
||||||
|
if let Err(e) = write_json_message(&mut stdout_lock, &output) {
|
||||||
|
eprintln!("Erreur lors de l'écriture de la réponse: {e}");
|
||||||
|
// Si on ne sait plus écrire, on termine.
|
||||||
|
process::exit(1);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
Ok(None) => {
|
||||||
|
eprintln!("EOF sur stdin, arrêt du host.");
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
Err(e) => {
|
||||||
|
eprintln!("Erreur de lecture: {e}");
|
||||||
|
process::exit(1);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
Reference in New Issue
Block a user