+470 -27 +/-16 browse
1 | diff --git a/Cargo.lock b/Cargo.lock |
2 | index 814f8c7..660e41a 100644 |
3 | --- a/Cargo.lock |
4 | +++ b/Cargo.lock |
5 | @@ -698,6 +698,12 @@ source = "registry+https://github.com/rust-lang/crates.io-index" |
6 | checksum = "9d297deb1925b89f2ccc13d7635fa0714f12c87adce1c75356b39ca9b7178567" |
7 | |
8 | [[package]] |
9 | + name = "base64" |
10 | + version = "0.22.0" |
11 | + source = "registry+https://github.com/rust-lang/crates.io-index" |
12 | + checksum = "9475866fec1451be56a3c2400fd081ff546538961565ccb5b7142cbd22bc7a51" |
13 | + |
14 | + [[package]] |
15 | name = "base64ct" |
16 | version = "1.6.0" |
17 | source = "registry+https://github.com/rust-lang/crates.io-index" |
18 | @@ -2350,6 +2356,7 @@ dependencies = [ |
19 | "pin-project-lite", |
20 | "smallvec", |
21 | "tokio", |
22 | + "want", |
23 | ] |
24 | |
25 | [[package]] |
26 | @@ -2366,12 +2373,29 @@ dependencies = [ |
27 | ] |
28 | |
29 | [[package]] |
30 | + name = "hyper-tls" |
31 | + version = "0.6.0" |
32 | + source = "registry+https://github.com/rust-lang/crates.io-index" |
33 | + checksum = "70206fc6890eaca9fde8a0bf71caa2ddfc9fe045ac9e5c70df101a7dbde866e0" |
34 | + dependencies = [ |
35 | + "bytes", |
36 | + "http-body-util", |
37 | + "hyper 1.2.0", |
38 | + "hyper-util", |
39 | + "native-tls", |
40 | + "tokio", |
41 | + "tokio-native-tls", |
42 | + "tower-service", |
43 | + ] |
44 | + |
45 | + [[package]] |
46 | name = "hyper-util" |
47 | version = "0.1.3" |
48 | source = "registry+https://github.com/rust-lang/crates.io-index" |
49 | checksum = "ca38ef113da30126bbff9cd1705f9273e15d45498615d138b0c20279ac7a76aa" |
50 | dependencies = [ |
51 | "bytes", |
52 | + "futures-channel", |
53 | "futures-util", |
54 | "http 1.1.0", |
55 | "http-body 1.0.0", |
56 | @@ -2379,6 +2403,9 @@ dependencies = [ |
57 | "pin-project-lite", |
58 | "socket2 0.5.6", |
59 | "tokio", |
60 | + "tower", |
61 | + "tower-service", |
62 | + "tracing", |
63 | ] |
64 | |
65 | [[package]] |
66 | @@ -2582,7 +2609,7 @@ dependencies = [ |
67 | "socket2 0.5.6", |
68 | "widestring", |
69 | "windows-sys 0.48.0", |
70 | - "winreg", |
71 | + "winreg 0.50.0", |
72 | ] |
73 | |
74 | [[package]] |
75 | @@ -4108,6 +4135,25 @@ dependencies = [ |
76 | ] |
77 | |
78 | [[package]] |
79 | + name = "quipu" |
80 | + version = "0.2.1" |
81 | + dependencies = [ |
82 | + "ayllu_api", |
83 | + "ayllu_config", |
84 | + "ayllu_git", |
85 | + "ayllu_rpc", |
86 | + "clap 4.5.3", |
87 | + "clap_complete", |
88 | + "reqwest 0.12.3", |
89 | + "serde", |
90 | + "thiserror", |
91 | + "tokio", |
92 | + "tracing", |
93 | + "tracing-subscriber", |
94 | + "url", |
95 | + ] |
96 | + |
97 | + [[package]] |
98 | name = "quote" |
99 | version = "1.0.35" |
100 | source = "registry+https://github.com/rust-lang/crates.io-index" |
101 | @@ -4254,7 +4300,7 @@ dependencies = [ |
102 | "http 0.2.12", |
103 | "http-body 0.4.6", |
104 | "hyper 0.14.28", |
105 | - "hyper-tls", |
106 | + "hyper-tls 0.5.0", |
107 | "ipnet", |
108 | "js-sys", |
109 | "log", |
110 | @@ -4263,7 +4309,7 @@ dependencies = [ |
111 | "once_cell", |
112 | "percent-encoding", |
113 | "pin-project-lite", |
114 | - "rustls-pemfile", |
115 | + "rustls-pemfile 1.0.4", |
116 | "serde", |
117 | "serde_json", |
118 | "serde_urlencoded", |
119 | @@ -4278,7 +4324,49 @@ dependencies = [ |
120 | "wasm-bindgen-futures", |
121 | "wasm-streams", |
122 | "web-sys", |
123 | - "winreg", |
124 | + "winreg 0.50.0", |
125 | + ] |
126 | + |
127 | + [[package]] |
128 | + name = "reqwest" |
129 | + version = "0.12.3" |
130 | + source = "registry+https://github.com/rust-lang/crates.io-index" |
131 | + checksum = "3e6cc1e89e689536eb5aeede61520e874df5a4707df811cd5da4aa5fbb2aae19" |
132 | + dependencies = [ |
133 | + "base64 0.22.0", |
134 | + "bytes", |
135 | + "encoding_rs", |
136 | + "futures-core", |
137 | + "futures-util", |
138 | + "h2 0.4.3", |
139 | + "http 1.1.0", |
140 | + "http-body 1.0.0", |
141 | + "http-body-util", |
142 | + "hyper 1.2.0", |
143 | + "hyper-tls 0.6.0", |
144 | + "hyper-util", |
145 | + "ipnet", |
146 | + "js-sys", |
147 | + "log", |
148 | + "mime", |
149 | + "native-tls", |
150 | + "once_cell", |
151 | + "percent-encoding", |
152 | + "pin-project-lite", |
153 | + "rustls-pemfile 2.1.2", |
154 | + "serde", |
155 | + "serde_json", |
156 | + "serde_urlencoded", |
157 | + "sync_wrapper", |
158 | + "system-configuration", |
159 | + "tokio", |
160 | + "tokio-native-tls", |
161 | + "tower-service", |
162 | + "url", |
163 | + "wasm-bindgen", |
164 | + "wasm-bindgen-futures", |
165 | + "web-sys", |
166 | + "winreg 0.52.0", |
167 | ] |
168 | |
169 | [[package]] |
170 | @@ -4417,6 +4505,22 @@ dependencies = [ |
171 | ] |
172 | |
173 | [[package]] |
174 | + name = "rustls-pemfile" |
175 | + version = "2.1.2" |
176 | + source = "registry+https://github.com/rust-lang/crates.io-index" |
177 | + checksum = "29993a25686778eb88d4189742cd713c9bce943bc54251a33509dc63cbacf73d" |
178 | + dependencies = [ |
179 | + "base64 0.22.0", |
180 | + "rustls-pki-types", |
181 | + ] |
182 | + |
183 | + [[package]] |
184 | + name = "rustls-pki-types" |
185 | + version = "1.4.1" |
186 | + source = "registry+https://github.com/rust-lang/crates.io-index" |
187 | + checksum = "ecd36cc4259e3e4514335c4a138c6b43171a8d61d8f5c9348f9fc7529416f247" |
188 | + |
189 | + [[package]] |
190 | name = "rustls-webpki" |
191 | version = "0.101.7" |
192 | source = "registry+https://github.com/rust-lang/crates.io-index" |
193 | @@ -4895,7 +4999,7 @@ dependencies = [ |
194 | "paste", |
195 | "percent-encoding", |
196 | "rustls", |
197 | - "rustls-pemfile", |
198 | + "rustls-pemfile 1.0.4", |
199 | "serde", |
200 | "serde_json", |
201 | "sha2", |
202 | @@ -5447,9 +5551,9 @@ dependencies = [ |
203 | |
204 | [[package]] |
205 | name = "tokio" |
206 | - version = "1.36.0" |
207 | + version = "1.37.0" |
208 | source = "registry+https://github.com/rust-lang/crates.io-index" |
209 | - checksum = "61285f6515fa018fb2d1e46eb21223fff441ee8db5d0f1435e8ab4f5cdb80931" |
210 | + checksum = "1adbebffeca75fcfd058afa480fb6c0b81e165a0323f9c9d39c9697e37c46787" |
211 | dependencies = [ |
212 | "backtrace", |
213 | "bytes", |
214 | @@ -5937,6 +6041,7 @@ dependencies = [ |
215 | "form_urlencoded", |
216 | "idna 0.5.0", |
217 | "percent-encoding", |
218 | + "serde", |
219 | ] |
220 | |
221 | [[package]] |
222 | @@ -6144,7 +6249,7 @@ version = "0.5.1" |
223 | source = "registry+https://github.com/rust-lang/crates.io-index" |
224 | checksum = "f395336b42f1be22490390830ccfe7a735725eef086cd0574bf8759408ecb3af" |
225 | dependencies = [ |
226 | - "reqwest", |
227 | + "reqwest 0.11.26", |
228 | "serde", |
229 | ] |
230 | |
231 | @@ -6380,6 +6485,16 @@ dependencies = [ |
232 | ] |
233 | |
234 | [[package]] |
235 | + name = "winreg" |
236 | + version = "0.52.0" |
237 | + source = "registry+https://github.com/rust-lang/crates.io-index" |
238 | + checksum = "a277a57398d4bfa075df44f501a17cfdf8542d224f0d36095a2adc7aee4ef0a5" |
239 | + dependencies = [ |
240 | + "cfg-if 1.0.0", |
241 | + "windows-sys 0.48.0", |
242 | + ] |
243 | + |
244 | + [[package]] |
245 | name = "wio" |
246 | version = "0.2.2" |
247 | source = "registry+https://github.com/rust-lang/crates.io-index" |
248 | @@ -6411,7 +6526,7 @@ source = "git+https://ayllu-forge.org/forks/xmpp-rs?branch=ayllu#3df636d178234f7 |
249 | dependencies = [ |
250 | "futures", |
251 | "log", |
252 | - "reqwest", |
253 | + "reqwest 0.11.26", |
254 | "tokio", |
255 | "tokio-util", |
256 | "tokio-xmpp", |
257 | diff --git a/Cargo.toml b/Cargo.toml |
258 | index 0ddcdf2..1dc3f8f 100644 |
259 | --- a/Cargo.toml |
260 | +++ b/Cargo.toml |
261 | @@ -1,4 +1,5 @@ |
262 | [workspace] |
263 | + resolver = "2" |
264 | members = [ |
265 | "crates/api", |
266 | "crates/config", |
267 | @@ -9,18 +10,6 @@ members = [ |
268 | "ayllu", |
269 | "ayllu-build", |
270 | "ayllu-mail", |
271 | - "ayllu-xmpp" |
272 | + "ayllu-xmpp", |
273 | + "quipu" |
274 | ] |
275 | - |
276 | - resolver = "2" |
277 | - |
278 | - # [workspace.dependencies] |
279 | - # ayllu_api = { version = "0.2.1", path = "./crates/api"} |
280 | - # ayllu_config = { version = "0.2.1", path = "./crates/config"} |
281 | - # ayllu_database = { version = "0.2.1", path = "./crates/database"} |
282 | - # ayllu_git = { version = "0.2.1", path = "./crates/git"} |
283 | - # ayllu_rpc = { version = "0.2.1", path = "./crates/rpc"} |
284 | - # ayllu_scheduler = {version = "0.2.1", path = "./crates/scheduler"} |
285 | - # ayllu-xmpp = { version = "0.2.1", path = "ayllu-xmpp"} |
286 | - # ayllu-build = {version = "0.2.1", path = "ayllu-build"} |
287 | - # ayllu-mail = { version = "0.2.1", path = "ayllu-mail"} |
288 | diff --git a/ayllu-spaces/README.md b/ayllu-spaces/README.md |
289 | deleted file mode 100644 |
290 | index a158610..0000000 |
291 | --- a/ayllu-spaces/README.md |
292 | +++ /dev/null |
293 | @@ -1,4 +0,0 @@ |
294 | - # ayllu-spaces |
295 | - |
296 | - Placeholder for "Code Spaces" like functionality in Ayllu designed around |
297 | - LXC. |
298 | diff --git a/ayllu/src/web2/routes/mod.rs b/ayllu/src/web2/routes/mod.rs |
299 | index 98e70a1..de35b73 100644 |
300 | --- a/ayllu/src/web2/routes/mod.rs |
301 | +++ b/ayllu/src/web2/routes/mod.rs |
302 | @@ -12,6 +12,7 @@ pub mod index; |
303 | pub mod log; |
304 | pub mod mail; |
305 | pub mod refs; |
306 | + pub mod rest; |
307 | pub mod repo; |
308 | pub mod robots; |
309 | pub mod rss; |
310 | diff --git a/ayllu/src/web2/routes/rest/discovery.rs b/ayllu/src/web2/routes/rest/discovery.rs |
311 | new file mode 100644 |
312 | index 0000000..2bf6dd4 |
313 | --- /dev/null |
314 | +++ b/ayllu/src/web2/routes/rest/discovery.rs |
315 | @@ -0,0 +1,31 @@ |
316 | + use axum::{extract::Extension, response::Json}; |
317 | + |
318 | + use crate::config::Config; |
319 | + use crate::web2::error::Error; |
320 | + |
321 | + use ayllu_api::discovery::{Collection, Repository}; |
322 | + use ayllu_git::{Scanner, Wrapper}; |
323 | + |
324 | + pub async fn serve(Extension(cfg): Extension<Config>) -> Result<Json<Vec<Collection>>, Error> { |
325 | + let mut collections: Vec<Collection> = Vec::new(); |
326 | + for config in cfg.collections { |
327 | + if config.hidden.is_some_and(|hidden| hidden) { |
328 | + continue; |
329 | + } |
330 | + let mut repositories: Vec<Repository> = Vec::new(); |
331 | + for repo in Scanner::from_path(&config.path)? { |
332 | + let repository = Wrapper::new(repo.as_path())?; |
333 | + let repo_config = repository.config()?; |
334 | + repositories.push(Repository { |
335 | + name: repository.name(), |
336 | + description: repo_config.description.clone(), |
337 | + }); |
338 | + } |
339 | + collections.push(Collection { |
340 | + name: config.name.clone(), |
341 | + description: config.description.clone(), |
342 | + repositories, |
343 | + }); |
344 | + } |
345 | + Ok(Json(collections)) |
346 | + } |
347 | diff --git a/ayllu/src/web2/routes/rest/mod.rs b/ayllu/src/web2/routes/rest/mod.rs |
348 | new file mode 100644 |
349 | index 0000000..fc4b5cb |
350 | --- /dev/null |
351 | +++ b/ayllu/src/web2/routes/rest/mod.rs |
352 | @@ -0,0 +1 @@ |
353 | + pub mod discovery; |
354 | diff --git a/ayllu/src/web2/server.rs b/ayllu/src/web2/server.rs |
355 | index 1738afe..51f4c10 100644 |
356 | --- a/ayllu/src/web2/server.rs |
357 | +++ b/ayllu/src/web2/server.rs |
358 | @@ -41,6 +41,7 @@ use crate::web2::routes::log as log_route; |
359 | use crate::web2::routes::mail; |
360 | use crate::web2::routes::refs; |
361 | use crate::web2::routes::repo; |
362 | + use crate::web2::routes::rest::discovery; |
363 | use crate::web2::routes::robots; |
364 | use crate::web2::routes::rss; |
365 | use crate::web2::routes::xmpp; |
366 | @@ -176,6 +177,10 @@ pub async fn serve(cfg: &Config) -> Result<(), Box<dyn Error>> { |
367 | )), |
368 | ) |
369 | .nest( |
370 | + "/0", |
371 | + Router::new().route("/index", routing::get(discovery::serve)), |
372 | + ) |
373 | + .nest( |
374 | "/mail", |
375 | Router::new() |
376 | .route("/", routing::get(mail::lists)) |
377 | diff --git a/crates/api/src/discovery.rs b/crates/api/src/discovery.rs |
378 | new file mode 100644 |
379 | index 0000000..1efd80b |
380 | --- /dev/null |
381 | +++ b/crates/api/src/discovery.rs |
382 | @@ -0,0 +1,16 @@ |
383 | + use serde::{Deserialize, Serialize}; |
384 | + |
385 | + #[derive(Debug, Deserialize, Serialize)] |
386 | + /// A Git based repository |
387 | + pub struct Repository { |
388 | + pub name: String, |
389 | + pub description: Option<String>, |
390 | + } |
391 | + |
392 | + #[derive(Debug, Deserialize, Serialize)] |
393 | + /// A collection of Git based repositories |
394 | + pub struct Collection { |
395 | + pub name: String, |
396 | + pub description: Option<String>, |
397 | + pub repositories: Vec<Repository>, |
398 | + } |
399 | diff --git a/crates/api/src/lib.rs b/crates/api/src/lib.rs |
400 | index 1daadf3..cc02f23 100644 |
401 | --- a/crates/api/src/lib.rs |
402 | +++ b/crates/api/src/lib.rs |
403 | @@ -1,4 +1,5 @@ |
404 | pub mod build; |
405 | + pub mod discovery; |
406 | pub mod error; |
407 | pub mod jobs; |
408 | pub mod mail; |
409 | diff --git a/quipu/Cargo.toml b/quipu/Cargo.toml |
410 | new file mode 100644 |
411 | index 0000000..5966609 |
412 | --- /dev/null |
413 | +++ b/quipu/Cargo.toml |
414 | @@ -0,0 +1,23 @@ |
415 | + [package] |
416 | + name = "quipu" |
417 | + version = "0.2.1" |
418 | + edition = "2021" |
419 | + |
420 | + [[bin]] |
421 | + name = "quipu" |
422 | + |
423 | + [dependencies] |
424 | + ayllu_api = { path = "../crates/api" } |
425 | + ayllu_config = { path = "../crates/config" } |
426 | + ayllu_rpc = { path = "../crates/rpc" } |
427 | + ayllu_git = { path = "../crates/git" } |
428 | + |
429 | + clap = { version = "4.3.0", features = ["derive"] } |
430 | + tokio = { version = "1.37.0", features = ["full"] } |
431 | + reqwest = { version = "0.12.3", features = ["json"] } |
432 | + tracing = "0.1.40" |
433 | + tracing-subscriber = "0.3.18" |
434 | + clap_complete = "4.5.1" |
435 | + thiserror = "1.0.58" |
436 | + serde = { version = "1.0.197", features = ["derive"] } |
437 | + url = { version = "2.5.0", features = ["serde"] } |
438 | diff --git a/quipu/README.md b/quipu/README.md |
439 | new file mode 100644 |
440 | index 0000000..dca820a |
441 | --- /dev/null |
442 | +++ b/quipu/README.md |
443 | @@ -0,0 +1,10 @@ |
444 | + # quipu |
445 | + |
446 | + Quipu is a CLI tool for interacting with remote instances of Ayllu. |
447 | + |
448 | + ## Name |
449 | + |
450 | + The name [Quipu](https://en.wikipedia.org/wiki/Quipu) _/kē′poo͞/_ is the Quechua |
451 | + word for an ancient recording device used by several cultures from the Andean |
452 | + region of South America. |
453 | + |
454 | diff --git a/quipu/src/client_rest.rs b/quipu/src/client_rest.rs |
455 | new file mode 100644 |
456 | index 0000000..e0d4f1d |
457 | --- /dev/null |
458 | +++ b/quipu/src/client_rest.rs |
459 | @@ -0,0 +1,32 @@ |
460 | + use reqwest::{Client, ClientBuilder}; |
461 | + use url::Url; |
462 | + |
463 | + use ayllu_api::discovery::Collection; |
464 | + |
465 | + use crate::error::QuipuError; |
466 | + |
467 | + const QUIPU_USER_AGENT: &str = "Quipu 0.0.0"; |
468 | + |
469 | + pub struct Quipu { |
470 | + endpoint: Url, |
471 | + client: Client, |
472 | + } |
473 | + |
474 | + impl Quipu { |
475 | + pub fn new(endpoint: Url) -> Self { |
476 | + let client = ClientBuilder::new() |
477 | + .user_agent(QUIPU_USER_AGENT) |
478 | + .build() |
479 | + .unwrap(); |
480 | + Self { endpoint, client } |
481 | + } |
482 | + |
483 | + pub async fn get_index(&self) -> Result<Vec<Collection>, QuipuError> { |
484 | + let response = self |
485 | + .client |
486 | + .get(self.endpoint.join("/0/index")?) |
487 | + .send() |
488 | + .await?; |
489 | + Ok(response.json().await?) |
490 | + } |
491 | + } |
492 | diff --git a/quipu/src/config.rs b/quipu/src/config.rs |
493 | new file mode 100644 |
494 | index 0000000..32bd9f5 |
495 | --- /dev/null |
496 | +++ b/quipu/src/config.rs |
497 | @@ -0,0 +1,47 @@ |
498 | + use serde::{Deserialize, Serialize}; |
499 | + use url::Url; |
500 | + |
501 | + use ayllu_config::Configurable; |
502 | + |
503 | + #[derive(Clone, Debug, Deserialize, Serialize)] |
504 | + pub struct Instance { |
505 | + pub url: Url, |
506 | + pub name: String, |
507 | + } |
508 | + |
509 | + #[derive(Clone, Debug, Deserialize, Serialize)] |
510 | + pub struct Quipu { |
511 | + pub instances: Vec<Instance>, |
512 | + } |
513 | + |
514 | + #[derive(Clone, Debug, Serialize, Deserialize)] |
515 | + pub struct Config { |
516 | + pub log_level: String, |
517 | + pub quipu: Option<Quipu>, |
518 | + } |
519 | + |
520 | + impl Config { |
521 | + // return the first configured instance if it exists |
522 | + pub fn default_instance(&self) -> Option<Instance> { |
523 | + if let Some(quipu_conf) = self.quipu.as_ref() { |
524 | + quipu_conf.instances.first().cloned() |
525 | + } else { |
526 | + None |
527 | + } |
528 | + } |
529 | + |
530 | + /// find an instance by name |
531 | + pub fn instance_by_name(&self, name: &str) -> Option<Instance> { |
532 | + if let Some(quipu_conf) = self.quipu.as_ref() { |
533 | + quipu_conf |
534 | + .instances |
535 | + .iter() |
536 | + .find(|instance| instance.name == name) |
537 | + .cloned() |
538 | + } else { |
539 | + None |
540 | + } |
541 | + } |
542 | + } |
543 | + |
544 | + impl Configurable for Config {} |
545 | diff --git a/quipu/src/error.rs b/quipu/src/error.rs |
546 | new file mode 100644 |
547 | index 0000000..6d1ef8a |
548 | --- /dev/null |
549 | +++ b/quipu/src/error.rs |
550 | @@ -0,0 +1,22 @@ |
551 | + use reqwest::Error as ReqwestError; |
552 | + use thiserror::Error; |
553 | + use tracing::metadata::ParseLevelError; |
554 | + use url::ParseError as ParseUrlError; |
555 | + |
556 | + use ayllu_config::Error as ConfigError; |
557 | + |
558 | + #[derive(Error, Debug)] |
559 | + pub enum QuipuError { |
560 | + #[error("IO Error")] |
561 | + Disconnect(#[from] std::io::Error), |
562 | + #[error("Configuration Error")] |
563 | + Config(#[from] ConfigError), |
564 | + #[error("Invalid Log Level")] |
565 | + LogLevel(#[from] ParseLevelError), |
566 | + #[error("Invalid Url")] |
567 | + URLParsing(#[from] ParseUrlError), |
568 | + #[error("Request Error")] |
569 | + Request(#[from] ReqwestError), |
570 | + #[error("Internal Quipu Error")] |
571 | + Message(String) |
572 | + } |
573 | diff --git a/quipu/src/main.rs b/quipu/src/main.rs |
574 | new file mode 100644 |
575 | index 0000000..f1defe2 |
576 | --- /dev/null |
577 | +++ b/quipu/src/main.rs |
578 | @@ -0,0 +1,123 @@ |
579 | + use std::io::stdout; |
580 | + use std::path::PathBuf; |
581 | + use std::str::FromStr; |
582 | + |
583 | + use clap::{arg, Command, CommandFactory, Parser, Subcommand, ValueEnum}; |
584 | + use clap_complete::{generate, Generator, Shell}; |
585 | + use tracing::Level; |
586 | + use url::Url; |
587 | + |
588 | + use ayllu_config::Reader; |
589 | + |
590 | + mod client_rest; |
591 | + mod config; |
592 | + mod error; |
593 | + mod output; |
594 | + |
595 | + #[derive(Parser)] |
596 | + #[command(author, version, about, long_about = None)] |
597 | + #[command(name = "quipu")] |
598 | + #[command(about = "Ayllu RPC Client")] |
599 | + struct Cli { |
600 | + /// Path to your configuration file |
601 | + #[arg(short, long, value_name = "FILE")] |
602 | + config: Option<PathBuf>, |
603 | + |
604 | + /// Sets the logging level |
605 | + #[arg(short, long, value_name = "LEVEL")] |
606 | + level: Option<Level>, |
607 | + |
608 | + #[command(subcommand)] |
609 | + command: Commands, |
610 | + } |
611 | + |
612 | + #[derive(Copy, Clone, PartialEq, Eq, PartialOrd, Ord, ValueEnum, Debug)] |
613 | + /// a resource that exists on the remote server |
614 | + enum Resource { |
615 | + /// an amalgamation of collections and repositories |
616 | + Index, |
617 | + } |
618 | + |
619 | + #[derive(Subcommand, Debug, PartialEq)] |
620 | + enum Commands { |
621 | + /// generate autocomplete commands for common shells |
622 | + Complete { |
623 | + #[arg(long)] |
624 | + shell: Shell, |
625 | + }, |
626 | + /// get resources from a remote Ayllu server |
627 | + Get { |
628 | + #[arg(short, long)] |
629 | + /// instance name from your quipu configuration |
630 | + instance: Option<String>, |
631 | + #[arg(short, long)] |
632 | + /// alternative url to contact |
633 | + url: Option<Url>, |
634 | + /// resource to request from remote server |
635 | + resource: Resource, |
636 | + }, |
637 | + } |
638 | + |
639 | + fn print_completions<G: Generator>(gen: G, cmd: &mut Command) { |
640 | + generate(gen, cmd, cmd.get_name().to_string(), &mut stdout()); |
641 | + } |
642 | + |
643 | + fn get_instance( |
644 | + cfg: &config::Config, |
645 | + url: Option<Url>, |
646 | + name: Option<String>, |
647 | + ) -> Result<config::Instance, error::QuipuError> { |
648 | + if let Some(url) = url { |
649 | + Ok(config::Instance { |
650 | + url, |
651 | + name: String::new(), |
652 | + }) |
653 | + } else if let Some(name) = name { |
654 | + if let Some(instance) = cfg.instance_by_name(&name) { |
655 | + Ok(instance) |
656 | + } else { |
657 | + Err(error::QuipuError::Message(format!( |
658 | + "no configured instance: {}", |
659 | + name |
660 | + ))) |
661 | + } |
662 | + } else if let Some(instance) = cfg.default_instance() { |
663 | + Ok(instance) |
664 | + } else { |
665 | + Err(error::QuipuError::Message(String::from( |
666 | + "no configured ayllu instances", |
667 | + ))) |
668 | + } |
669 | + } |
670 | + |
671 | + #[tokio::main(flavor = "current_thread")] |
672 | + async fn main() -> Result<(), error::QuipuError> { |
673 | + let cli = Cli::parse(); |
674 | + let cfg: config::Config = Reader::load(cli.config.as_deref())?; |
675 | + let log_level = Level::from_str(&cfg.log_level)?; |
676 | + tracing_subscriber::fmt() |
677 | + .compact() |
678 | + .with_line_number(true) |
679 | + .with_level(true) |
680 | + .with_max_level(cli.level.unwrap_or(log_level)) |
681 | + .init(); |
682 | + tracing::info!("logger initialized"); |
683 | + match cli.command { |
684 | + Commands::Complete { shell } => { |
685 | + let mut cmd = Cli::command(); |
686 | + print_completions(shell, &mut cmd); |
687 | + Ok(()) |
688 | + } |
689 | + Commands::Get { |
690 | + instance, |
691 | + url, |
692 | + resource: _, |
693 | + } => { |
694 | + let instance = get_instance(&cfg, url, instance)?; |
695 | + let client = client_rest::Quipu::new(instance.url); |
696 | + let collections = client.get_index().await?; |
697 | + output::pretty(output::Resource::Collections(collections))?; |
698 | + Ok(()) |
699 | + } |
700 | + } |
701 | + } |
702 | diff --git a/quipu/src/output.rs b/quipu/src/output.rs |
703 | new file mode 100644 |
704 | index 0000000..627058a |
705 | --- /dev/null |
706 | +++ b/quipu/src/output.rs |
707 | @@ -0,0 +1,31 @@ |
708 | + use ayllu_api::discovery::Collection; |
709 | + |
710 | + use crate::error::QuipuError; |
711 | + |
712 | + pub enum Resource { |
713 | + Collections(Vec<Collection>), |
714 | + } |
715 | + |
716 | + /// pretty print the resource to the terminal |
717 | + pub fn pretty(resource: Resource) -> Result<(), QuipuError> { |
718 | + match resource { |
719 | + Resource::Collections(collections) => { |
720 | + for collection in collections { |
721 | + println!( |
722 | + "{} - {} [{} repositories]", |
723 | + collection.name, |
724 | + collection.description.unwrap_or("None".to_string()), |
725 | + collection.repositories.len() |
726 | + ); |
727 | + for repository in collection.repositories { |
728 | + println!( |
729 | + "\t{} - {}", |
730 | + repository.name, |
731 | + repository.description.unwrap_or("None".to_string()) |
732 | + ); |
733 | + } |
734 | + } |
735 | + } |
736 | + }; |
737 | + Ok(()) |
738 | + } |