1use crate::cli::{Cli, Commands, QueryCommands};
2use crate::graph::KnowledgeGraph;
3use crate::query::Query;
4use crate::visualization::{
5 DotGenerator, DotOptions, DotTheme, EdgeStyle, RankDir, SvgGenerator, SvgOptions,
6};
7use std::fs;
8
9#[must_use]
17#[allow(clippy::too_many_lines)]
18pub fn run_cli(cli: Cli) -> i32 {
19 match cli.command {
20 Commands::Build {
21 path,
22 config,
23 no_ignore,
24 no_cache,
25 rebuild,
26 json,
27 dot,
28 svg,
29 dot_clusters,
30 dot_legend,
31 dot_theme,
32 dot_rankdir,
33 dot_splines,
34 dot_rounded,
35 svg_interactive,
36 save,
37 } => {
38 let mode = if rebuild {
40 crate::utils::cache::CacheMode::Rebuild
41 } else if no_cache {
42 crate::utils::cache::CacheMode::Ignore
43 } else {
44 crate::utils::cache::CacheMode::Use
45 };
46
47 let build_path = std::path::Path::new(&path);
48 if matches!(mode, crate::utils::cache::CacheMode::Rebuild) {
49 crate::utils::cache::clear_cache(build_path);
50 }
51
52 let graph = match KnowledgeGraph::build_from_directory_with_cache_opts(
53 build_path, mode, no_ignore,
54 ) {
55 Ok(g) => g,
56 Err(e) => {
57 eprintln!("Build failed: {e}");
58 return 1;
59 }
60 };
61
62 if let Some(json_path) = json {
64 let serialized =
65 serde_json::to_string_pretty(&graph).expect("serialize graph to JSON");
66 if let Err(e) = fs::write(&json_path, serialized) {
67 eprintln!("Failed to write JSON output {json_path}: {e}");
68 }
69 }
70
71 let mut clusters = matches!(dot_clusters.as_str(), "on");
73 let mut legend = matches!(dot_legend.as_str(), "on");
74 let mut theme = match dot_theme.as_str() {
75 "dark" => DotTheme::Dark,
76 _ => DotTheme::Light,
77 };
78 let mut rankdir = match dot_rankdir.as_str() {
79 "TB" => RankDir::TB,
80 _ => RankDir::LR,
81 };
82 let mut splines = match dot_splines.as_str() {
83 "ortho" => EdgeStyle::Ortho,
84 "polyline" => EdgeStyle::Polyline,
85 _ => EdgeStyle::Curved,
86 };
87 let mut rounded = matches!(dot_rounded.as_str(), "on");
88 if let Some(cfg_path) = config.as_ref() {
89 if let Some(cfg) =
90 crate::utils::config::load_config_at(std::path::Path::new(cfg_path))
91 {
92 if let Some(dot) = cfg.dot {
93 if let Some(v) = dot.clusters {
94 clusters = v;
95 }
96 if let Some(v) = dot.legend {
97 legend = v;
98 }
99 if let Some(v) = dot.theme {
100 theme = if v == "dark" { DotTheme::Dark } else { DotTheme::Light };
101 }
102 if let Some(v) = dot.rankdir {
103 rankdir = if v == "TB" { RankDir::TB } else { RankDir::LR };
104 }
105 if let Some(v) = dot.splines {
106 splines = match v.as_str() {
107 "ortho" => EdgeStyle::Ortho,
108 "polyline" => EdgeStyle::Polyline,
109 _ => EdgeStyle::Curved,
110 };
111 }
112 if let Some(v) = dot.rounded {
113 rounded = v;
114 }
115 }
116 }
117 }
118 let dot_opts = DotOptions { clusters, legend, theme, rankdir, splines, rounded };
119
120 if let Some(dot_path) = dot {
121 match DotGenerator::new().generate_dot_with_options(&graph, dot_opts) {
122 Ok(content) => {
123 if let Err(e) = fs::write(&dot_path, content) {
124 eprintln!("Failed to write DOT output {dot_path}: {e}");
125 }
126 }
127 Err(e) => eprintln!("Visualization error: {e}"),
128 }
129 }
130
131 if let Some(svg_path) = svg {
132 let mut interactive = matches!(svg_interactive.as_str(), "on");
133 if let Some(cfg_path) = config.as_ref() {
134 if let Some(cfg) =
135 crate::utils::config::load_config_at(std::path::Path::new(cfg_path))
136 {
137 if let Some(svg) = cfg.svg {
138 if let Some(v) = svg.interactive {
139 interactive = v;
140 }
141 }
142 }
143 }
144 let svg_opts = SvgOptions { dot: dot_opts, interactive };
145 match SvgGenerator::new().generate_svg_with_options(&graph, svg_opts) {
146 Ok(content) => {
147 if let Err(e) = fs::write(&svg_path, content) {
148 eprintln!("Failed to write SVG output {svg_path}: {e}");
149 }
150 }
151 Err(e) => eprintln!("Visualization error: {e}"),
152 }
153 }
154
155 if let Some(save_path) = save {
156 if let Err(e) = KnowledgeGraph::save_json(&graph, std::path::Path::new(&save_path))
157 {
158 eprintln!("Failed to save graph JSON {save_path}: {e}");
159 }
160 }
161
162 println!("Build completed for path: {path}");
163 0
164 }
165 Commands::Query { query } => match query {
166 QueryCommands::ConnectedFiles {
167 path,
168 config,
169 no_ignore,
170 file,
171 graph: graph_path,
172 format,
173 } => {
174 let graph = if let Some(p) = graph_path {
175 match KnowledgeGraph::load_json(std::path::Path::new(&p)) {
176 Ok(g) => g,
177 Err(e) => {
178 eprintln!("Load graph failed: {e}");
179 return 1;
180 }
181 }
182 } else {
183 let res = match KnowledgeGraph::build_from_directory_opts(
184 std::path::Path::new(&path),
185 no_ignore,
186 ) {
187 Ok(g) => g,
188 Err(e) => {
189 eprintln!("Build failed: {e}");
190 return 1;
191 }
192 };
193 res
194 };
195 let q = crate::query::ConnectedFilesQuery::new(&file);
196 let results = q.run(&graph);
197 let fmt = if let Some(cfg_path) = config.as_ref() {
198 if let Some(cfg) =
199 crate::utils::config::load_config_at(std::path::Path::new(cfg_path))
200 {
201 cfg.query.and_then(|q| q.default_format).unwrap_or(format.clone())
202 } else {
203 format.clone()
204 }
205 } else {
206 format.clone()
207 };
208 if fmt == "json" {
209 let out: Vec<String> =
210 results.into_iter().map(|p| p.display().to_string()).collect();
211 match serde_json::to_string_pretty(&out) {
212 Ok(s) => println!("{s}"),
213 Err(e) => {
214 eprintln!("JSON encode error: {e}");
215 return 1;
216 }
217 }
218 } else {
219 let rows: Vec<Vec<String>> = results
220 .into_iter()
221 .enumerate()
222 .map(|(i, p)| vec![format!("{}", i + 1), p.display().to_string()])
223 .collect();
224 let table = crate::utils::table::render(&["#", "Path"], &rows);
225 println!("{table}");
226 }
227 0
228 }
229 QueryCommands::FunctionUsage {
230 path,
231 config,
232 no_ignore,
233 function,
234 direction,
235 graph: graph_path,
236 format,
237 } => {
238 let graph = if let Some(p) = graph_path {
239 match KnowledgeGraph::load_json(std::path::Path::new(&p)) {
240 Ok(g) => g,
241 Err(e) => {
242 eprintln!("Load graph failed: {e}");
243 return 1;
244 }
245 }
246 } else {
247 if no_ignore {
248 std::env::set_var("KNOWLEDGE_RS_NO_IGNORE", "1");
249 }
250 let res =
251 match KnowledgeGraph::build_from_directory(std::path::Path::new(&path)) {
252 Ok(g) => g,
253 Err(e) => {
254 eprintln!("Build failed: {e}");
255 if no_ignore {
256 std::env::remove_var("KNOWLEDGE_RS_NO_IGNORE");
257 }
258 return 1;
259 }
260 };
261 if no_ignore {
262 std::env::remove_var("KNOWLEDGE_RS_NO_IGNORE");
263 }
264 res
265 };
266 let dir = match direction.as_str() {
267 "callees" => crate::query::UsageDirection::Callees,
268 _ => crate::query::UsageDirection::Callers,
269 };
270 let q = crate::query::FunctionUsageQuery { function, direction: dir };
271 let results = q.run(&graph);
272 let fmt = if let Some(cfg_path) = config.as_ref() {
273 if let Some(cfg) =
274 crate::utils::config::load_config_at(std::path::Path::new(cfg_path))
275 {
276 cfg.query.and_then(|q| q.default_format).unwrap_or(format.clone())
277 } else {
278 format.clone()
279 }
280 } else {
281 format.clone()
282 };
283 if fmt == "json" {
284 let out: Vec<String> =
285 results.into_iter().map(|p| p.display().to_string()).collect();
286 match serde_json::to_string_pretty(&out) {
287 Ok(s) => println!("{s}"),
288 Err(e) => {
289 eprintln!("JSON encode error: {e}");
290 return 1;
291 }
292 }
293 } else {
294 for p in results {
295 println!("{}", p.display());
296 }
297 }
298 0
299 }
300 QueryCommands::Cycles { path, config, no_ignore, graph: graph_path, format } => {
301 let graph = if let Some(p) = graph_path {
302 match KnowledgeGraph::load_json(std::path::Path::new(&p)) {
303 Ok(g) => g,
304 Err(e) => {
305 eprintln!("Load graph failed: {e}");
306 return 1;
307 }
308 }
309 } else {
310 if no_ignore {
311 std::env::set_var("KNOWLEDGE_RS_NO_IGNORE", "1");
312 }
313 let res =
314 match KnowledgeGraph::build_from_directory(std::path::Path::new(&path)) {
315 Ok(g) => g,
316 Err(e) => {
317 eprintln!("Build failed: {e}");
318 if no_ignore {
319 std::env::remove_var("KNOWLEDGE_RS_NO_IGNORE");
320 }
321 return 1;
322 }
323 };
324 if no_ignore {
325 std::env::remove_var("KNOWLEDGE_RS_NO_IGNORE");
326 }
327 res
328 };
329 let q = crate::query::CycleDetectionQuery::new();
330 let cycles = q.run(&graph);
331 let fmt = if let Some(cfg_path) = config.as_ref() {
332 if let Some(cfg) =
333 crate::utils::config::load_config_at(std::path::Path::new(cfg_path))
334 {
335 cfg.query.and_then(|q| q.default_format).unwrap_or(format.clone())
336 } else {
337 format.clone()
338 }
339 } else {
340 format.clone()
341 };
342 if fmt == "json" {
343 let out: Vec<Vec<String>> = cycles
344 .into_iter()
345 .map(|cyc| cyc.into_iter().map(|p| p.display().to_string()).collect())
346 .collect();
347 match serde_json::to_string_pretty(&out) {
348 Ok(s) => println!("{s}"),
349 Err(e) => {
350 eprintln!("JSON encode error: {e}");
351 return 1;
352 }
353 }
354 } else {
355 for cyc in cycles {
356 let parts: Vec<String> =
357 cyc.iter().map(|p| p.display().to_string()).collect();
358 println!("{}", parts.join(" -> "));
359 }
360 }
361 0
362 }
363 QueryCommands::Path {
364 path,
365 config,
366 no_ignore,
367 from,
368 to,
369 graph: graph_path,
370 format,
371 } => {
372 let graph = if let Some(p) = graph_path {
373 match KnowledgeGraph::load_json(std::path::Path::new(&p)) {
374 Ok(g) => g,
375 Err(e) => {
376 eprintln!("Load graph failed: {e}");
377 return 1;
378 }
379 }
380 } else {
381 if no_ignore {
382 std::env::set_var("KNOWLEDGE_RS_NO_IGNORE", "1");
383 }
384 let res =
385 match KnowledgeGraph::build_from_directory(std::path::Path::new(&path)) {
386 Ok(g) => g,
387 Err(e) => {
388 eprintln!("Build failed: {e}");
389 if no_ignore {
390 std::env::remove_var("KNOWLEDGE_RS_NO_IGNORE");
391 }
392 return 1;
393 }
394 };
395 if no_ignore {
396 std::env::remove_var("KNOWLEDGE_RS_NO_IGNORE");
397 }
398 res
399 };
400 let q = crate::query::ShortestPathQuery::new(&from, &to);
401 let results = q.run(&graph);
402 let fmt = if let Some(cfg_path) = config.as_ref() {
403 if let Some(cfg) =
404 crate::utils::config::load_config_at(std::path::Path::new(cfg_path))
405 {
406 cfg.query.and_then(|q| q.default_format).unwrap_or(format.clone())
407 } else {
408 format.clone()
409 }
410 } else {
411 format.clone()
412 };
413 if fmt == "json" {
414 let out: Vec<String> =
415 results.into_iter().map(|p| p.display().to_string()).collect();
416 match serde_json::to_string_pretty(&out) {
417 Ok(s) => println!("{s}"),
418 Err(e) => {
419 eprintln!("JSON encode error: {e}");
420 return 1;
421 }
422 }
423 } else if results.is_empty() {
424 println!("<no path>");
425 } else {
426 let rows: Vec<Vec<String>> = results
427 .into_iter()
428 .enumerate()
429 .map(|(i, p)| vec![format!("{}", i + 1), p.display().to_string()])
430 .collect();
431 let table = crate::utils::table::render(&["Step", "Path"], &rows);
432 println!("{table}");
433 }
434 0
435 }
436 QueryCommands::Hubs {
437 path,
438 config,
439 no_ignore,
440 graph: graph_path,
441 metric,
442 top,
443 format,
444 } => {
445 use crate::query::{CentralityMetric, HubsQuery};
446 let graph = if let Some(p) = graph_path {
447 match KnowledgeGraph::load_json(std::path::Path::new(&p)) {
448 Ok(g) => g,
449 Err(e) => {
450 eprintln!("Load graph failed: {e}");
451 return 1;
452 }
453 }
454 } else {
455 if no_ignore {
456 std::env::set_var("KNOWLEDGE_RS_NO_IGNORE", "1");
457 }
458 let res =
459 match KnowledgeGraph::build_from_directory(std::path::Path::new(&path)) {
460 Ok(g) => g,
461 Err(e) => {
462 eprintln!("Build failed: {e}");
463 if no_ignore {
464 std::env::remove_var("KNOWLEDGE_RS_NO_IGNORE");
465 }
466 return 1;
467 }
468 };
469 if no_ignore {
470 std::env::remove_var("KNOWLEDGE_RS_NO_IGNORE");
471 }
472 res
473 };
474 let m = match metric.as_str() {
475 "in" => CentralityMetric::In,
476 "out" => CentralityMetric::Out,
477 _ => CentralityMetric::Total,
478 };
479 let q = HubsQuery::new(m, top);
480 let rows = q.run(&graph);
481 let fmt = if let Some(cfg_path) = config.as_ref() {
482 if let Some(cfg) =
483 crate::utils::config::load_config_at(std::path::Path::new(cfg_path))
484 {
485 cfg.query.and_then(|q| q.default_format).unwrap_or(format.clone())
486 } else {
487 format.clone()
488 }
489 } else {
490 format.clone()
491 };
492 if fmt == "json" {
493 #[derive(serde::Serialize)]
494 struct HubRow {
495 path: String,
496 indegree: usize,
497 outdegree: usize,
498 }
499 let out: Vec<HubRow> = rows
500 .into_iter()
501 .map(|(p, i, o)| HubRow {
502 path: p.display().to_string(),
503 indegree: i,
504 outdegree: o,
505 })
506 .collect();
507 match serde_json::to_string_pretty(&out) {
508 Ok(s) => println!("{s}"),
509 Err(e) => {
510 eprintln!("JSON encode error: {e}");
511 return 1;
512 }
513 }
514 } else {
515 let body: Vec<Vec<String>> = rows
516 .into_iter()
517 .map(|(p, i, o)| {
518 vec![
519 p.display().to_string(),
520 i.to_string(),
521 o.to_string(),
522 (i + o).to_string(),
523 ]
524 })
525 .collect();
526 let table = crate::utils::table::render(&["Path", "In", "Out", "Total"], &body);
527 println!("{table}");
528 }
529 0
530 }
531 QueryCommands::ModuleCentrality {
532 path,
533 config,
534 no_ignore,
535 graph: graph_path,
536 metric,
537 top,
538 format,
539 } => {
540 use crate::query::{CentralityMetric, ModuleCentralityQuery};
541 let graph = if let Some(p) = graph_path {
542 match KnowledgeGraph::load_json(std::path::Path::new(&p)) {
543 Ok(g) => g,
544 Err(e) => {
545 eprintln!("Load graph failed: {e}");
546 return 1;
547 }
548 }
549 } else {
550 if no_ignore {
551 std::env::set_var("KNOWLEDGE_RS_NO_IGNORE", "1");
552 }
553 let res =
554 match KnowledgeGraph::build_from_directory(std::path::Path::new(&path)) {
555 Ok(g) => g,
556 Err(e) => {
557 eprintln!("Build failed: {e}");
558 if no_ignore {
559 std::env::remove_var("KNOWLEDGE_RS_NO_IGNORE");
560 }
561 return 1;
562 }
563 };
564 if no_ignore {
565 std::env::remove_var("KNOWLEDGE_RS_NO_IGNORE");
566 }
567 res
568 };
569 let m = match metric.as_str() {
570 "in" => CentralityMetric::In,
571 "out" => CentralityMetric::Out,
572 _ => CentralityMetric::Total,
573 };
574 let q = ModuleCentralityQuery::new(m, top);
575 let rows = q.run(&graph);
576 let fmt = if let Some(cfg_path) = config.as_ref() {
577 if let Some(cfg) =
578 crate::utils::config::load_config_at(std::path::Path::new(cfg_path))
579 {
580 cfg.query.and_then(|q| q.default_format).unwrap_or(format.clone())
581 } else {
582 format.clone()
583 }
584 } else {
585 format.clone()
586 };
587 if fmt == "json" {
588 #[derive(serde::Serialize)]
589 struct Row {
590 module: String,
591 indegree: usize,
592 outdegree: usize,
593 }
594 let out: Vec<Row> = rows
595 .into_iter()
596 .map(|(p, i, o)| Row {
597 module: p.display().to_string(),
598 indegree: i,
599 outdegree: o,
600 })
601 .collect();
602 match serde_json::to_string_pretty(&out) {
603 Ok(s) => println!("{s}"),
604 Err(e) => {
605 eprintln!("JSON encode error: {e}");
606 return 1;
607 }
608 }
609 } else {
610 let body: Vec<Vec<String>> = rows
611 .into_iter()
612 .map(|(p, i, o)| {
613 vec![
614 p.display().to_string(),
615 i.to_string(),
616 o.to_string(),
617 (i + o).to_string(),
618 ]
619 })
620 .collect();
621 let table =
622 crate::utils::table::render(&["Module", "In", "Out", "Total"], &body);
623 println!("{table}");
624 }
625 0
626 }
627 QueryCommands::TraitImpls {
628 path,
629 config,
630 no_ignore,
631 r#trait,
632 graph: graph_path,
633 format,
634 } => {
635 use crate::query::TraitImplsQuery;
636 let graph = if let Some(p) = graph_path {
637 match KnowledgeGraph::load_json(std::path::Path::new(&p)) {
638 Ok(g) => g,
639 Err(e) => {
640 eprintln!("Load graph failed: {e}");
641 return 1;
642 }
643 }
644 } else {
645 if no_ignore {
646 std::env::set_var("KNOWLEDGE_RS_NO_IGNORE", "1");
647 }
648 let res =
649 match KnowledgeGraph::build_from_directory(std::path::Path::new(&path)) {
650 Ok(g) => g,
651 Err(e) => {
652 eprintln!("Build failed: {e}");
653 if no_ignore {
654 std::env::remove_var("KNOWLEDGE_RS_NO_IGNORE");
655 }
656 return 1;
657 }
658 };
659 if no_ignore {
660 std::env::remove_var("KNOWLEDGE_RS_NO_IGNORE");
661 }
662 res
663 };
664 let q = TraitImplsQuery::new(&r#trait);
665 let rows = q.run(&graph);
666 let fmt = if let Some(cfg_path) = config.as_ref() {
667 if let Some(cfg) =
668 crate::utils::config::load_config_at(std::path::Path::new(cfg_path))
669 {
670 cfg.query.and_then(|q| q.default_format).unwrap_or(format.clone())
671 } else {
672 format.clone()
673 }
674 } else {
675 format.clone()
676 };
677 if fmt == "json" {
678 #[derive(serde::Serialize)]
679 struct Row {
680 path: String,
681 r#type: String,
682 }
683 let out: Vec<Row> = rows
684 .into_iter()
685 .map(|(p, t)| Row { path: p.display().to_string(), r#type: t })
686 .collect();
687 match serde_json::to_string_pretty(&out) {
688 Ok(s) => println!("{s}"),
689 Err(e) => {
690 eprintln!("JSON encode error: {e}");
691 return 1;
692 }
693 }
694 } else if rows.is_empty() {
695 println!("<no implementations found>");
696 } else {
697 let body: Vec<Vec<String>> =
698 rows.into_iter().map(|(p, t)| vec![p.display().to_string(), t]).collect();
699 let table = crate::utils::table::render(&["Path", "Type"], &body);
700 println!("{table}");
701 }
702 0
703 }
704 QueryCommands::UnreferencedItems {
705 path,
706 config,
707 no_ignore,
708 include_public,
709 exclude,
710 graph: graph_path,
711 format,
712 } => {
713 use crate::query::UnreferencedItemsQuery;
714 let graph = if let Some(p) = graph_path {
715 match KnowledgeGraph::load_json(std::path::Path::new(&p)) {
716 Ok(g) => g,
717 Err(e) => {
718 eprintln!("Load graph failed: {e}");
719 return 1;
720 }
721 }
722 } else {
723 if no_ignore {
724 std::env::set_var("KNOWLEDGE_RS_NO_IGNORE", "1");
725 }
726 let res =
727 match KnowledgeGraph::build_from_directory(std::path::Path::new(&path)) {
728 Ok(g) => g,
729 Err(e) => {
730 eprintln!("Build failed: {e}");
731 if no_ignore {
732 std::env::remove_var("KNOWLEDGE_RS_NO_IGNORE");
733 }
734 return 1;
735 }
736 };
737 if no_ignore {
738 std::env::remove_var("KNOWLEDGE_RS_NO_IGNORE");
739 }
740 res
741 };
742 let exclude_re = if let Some(pat) = exclude.as_ref() {
743 match regex::Regex::new(pat) {
744 Ok(r) => Some(r),
745 Err(e) => {
746 eprintln!("Invalid --exclude regex: {e}");
747 return 1;
748 }
749 }
750 } else {
751 None
752 };
753 let q = UnreferencedItemsQuery::new(include_public, exclude_re);
754 let rows = q.run(&graph);
755 let fmt = if let Some(cfg_path) = config.as_ref() {
756 if let Some(cfg) =
757 crate::utils::config::load_config_at(std::path::Path::new(cfg_path))
758 {
759 cfg.query.and_then(|q| q.default_format).unwrap_or(format.clone())
760 } else {
761 format.clone()
762 }
763 } else {
764 format.clone()
765 };
766 if fmt == "json" {
767 #[derive(serde::Serialize)]
768 struct Row {
769 path: String,
770 id: String,
771 name: String,
772 kind: String,
773 visibility: String,
774 }
775 let out: Vec<Row> = rows
776 .into_iter()
777 .map(|(p, id, name, kind, vis)| Row {
778 path: p.display().to_string(),
779 id,
780 name,
781 kind,
782 visibility: vis,
783 })
784 .collect();
785 match serde_json::to_string_pretty(&out) {
786 Ok(s) => println!("{s}"),
787 Err(e) => {
788 eprintln!("JSON encode error: {e}");
789 return 1;
790 }
791 }
792 } else if rows.is_empty() {
793 println!("<no unreferenced items>");
794 } else {
795 let body: Vec<Vec<String>> = rows
796 .into_iter()
797 .map(|(p, id, name, kind, vis)| {
798 vec![p.display().to_string(), id, name, kind, vis]
799 })
800 .collect();
801 let table = crate::utils::table::render(
802 &["Path", "ItemId", "Name", "Kind", "Vis"],
803 &body,
804 );
805 println!("{table}");
806 }
807 0
808 }
809 QueryCommands::ItemInfo {
810 path,
811 config,
812 no_ignore,
813 item_id,
814 graph: graph_path,
815 show_code,
816 format,
817 } => {
818 use crate::query::ItemInfoQuery;
819 let graph = if let Some(p) = graph_path {
820 match KnowledgeGraph::load_json(std::path::Path::new(&p)) {
821 Ok(g) => g,
822 Err(e) => {
823 eprintln!("Load graph failed: {e}");
824 return 1;
825 }
826 }
827 } else {
828 if no_ignore {
829 std::env::set_var("KNOWLEDGE_RS_NO_IGNORE", "1");
830 }
831 let res =
832 match KnowledgeGraph::build_from_directory(std::path::Path::new(&path)) {
833 Ok(g) => g,
834 Err(e) => {
835 eprintln!("Build failed: {e}");
836 if no_ignore {
837 std::env::remove_var("KNOWLEDGE_RS_NO_IGNORE");
838 }
839 return 1;
840 }
841 };
842 if no_ignore {
843 std::env::remove_var("KNOWLEDGE_RS_NO_IGNORE");
844 }
845 res
846 };
847 let id = crate::graph::ItemId(item_id);
848 let q = ItemInfoQuery::new(id, show_code);
849 let result = q.run(&graph);
850 let fmt = if let Some(cfg_path) = config.as_ref() {
851 if let Some(cfg) =
852 crate::utils::config::load_config_at(std::path::Path::new(cfg_path))
853 {
854 cfg.query.and_then(|q| q.default_format).unwrap_or(format.clone())
855 } else {
856 format.clone()
857 }
858 } else {
859 format.clone()
860 };
861 if fmt == "json" {
862 match serde_json::to_string_pretty(&result) {
863 Ok(s) => println!("{s}"),
864 Err(e) => {
865 eprintln!("JSON encode error: {e}");
866 return 1;
867 }
868 }
869 } else {
870 match result {
871 None => println!("<item not found>"),
872 Some(info) => {
873 println!("Item: {}", info.name);
874 println!("Id: {}", info.id);
875 println!("Kind: {}", info.kind);
876 println!("Vis: {}", info.visibility);
877 println!(
878 "Location: {}:{}-{}",
879 info.path, info.line_start, info.line_end
880 );
881 if show_code {
882 if let Some(code) = info.code.as_deref() {
883 println!("\n--- code ---\n{}\n--- end code ---", code);
884 }
885 }
886 if info.inbound.is_empty() {
887 println!("\nInbound: <none>");
888 } else {
889 println!("\nInbound:");
890 for r in info.inbound {
891 println!(
892 "- [{}] {} ({}) @ {} :: {}",
893 r.relation, r.name, r.id, r.path, r.context
894 );
895 }
896 }
897 if info.outbound.is_empty() {
898 println!("\nOutbound: <none>");
899 } else {
900 println!("\nOutbound:");
901 for r in info.outbound {
902 println!(
903 "- [{}] {} ({}) @ {} :: {}",
904 r.relation, r.name, r.id, r.path, r.context
905 );
906 }
907 }
908 }
909 }
910 }
911 0
912 }
913 },
914 }
915}