radicle_cli/commands/
ls.rs1use std::ffi::OsString;
2
3use radicle::storage::{ReadStorage, RepositoryInfo};
4
5use crate::terminal as term;
6use crate::terminal::args::{Args, Error, Help};
7
8use term::Element;
9
10pub const HELP: Help = Help {
11 name: "ls",
12 description: "List repositories",
13 version: env!("RADICLE_VERSION"),
14 usage: r#"
15Usage
16
17 rad ls [<option>...]
18
19 By default, this command shows you all repositories that you have forked or initialized.
20 If you wish to see all seeded repositories, use the `--seeded` option.
21
22Options
23
24 --private Show only private repositories
25 --public Show only public repositories
26 --seeded, -s Show all seeded repositories
27 --all, -a Show all repositories in storage
28 --verbose, -v Verbose output
29 --help Print help
30"#,
31};
32
33pub struct Options {
34 #[allow(dead_code)]
35 verbose: bool,
36 public: bool,
37 private: bool,
38 all: bool,
39 seeded: bool,
40}
41
42impl Args for Options {
43 fn from_args(args: Vec<OsString>) -> anyhow::Result<(Self, Vec<OsString>)> {
44 use lexopt::prelude::*;
45
46 let mut parser = lexopt::Parser::from_args(args);
47 let mut verbose = false;
48 let mut private = false;
49 let mut public = false;
50 let mut all = false;
51 let mut seeded = false;
52
53 while let Some(arg) = parser.next()? {
54 match arg {
55 Long("help") | Short('h') => {
56 return Err(Error::Help.into());
57 }
58 Long("all") | Short('a') => {
59 all = true;
60 }
61 Long("seeded") | Short('s') => {
62 seeded = true;
63 }
64 Long("private") => {
65 private = true;
66 }
67 Long("public") => {
68 public = true;
69 }
70 Long("verbose") | Short('v') => verbose = true,
71 _ => anyhow::bail!(arg.unexpected()),
72 }
73 }
74
75 Ok((
76 Options {
77 verbose,
78 private,
79 public,
80 all,
81 seeded,
82 },
83 vec![],
84 ))
85 }
86}
87
88pub fn run(options: Options, ctx: impl term::Context) -> anyhow::Result<()> {
89 let profile = ctx.profile()?;
90 let storage = &profile.storage;
91 let repos = storage.repositories()?;
92 let policy = profile.policies()?;
93 let mut table = term::Table::new(term::TableOptions::bordered());
94 let mut rows = Vec::new();
95
96 if repos.is_empty() {
97 return Ok(());
98 }
99
100 for RepositoryInfo {
101 rid,
102 head,
103 doc,
104 refs,
105 ..
106 } in repos
107 {
108 if doc.is_public() && options.private && !options.public {
109 continue;
110 }
111 if !doc.is_public() && !options.private && options.public {
112 continue;
113 }
114 if refs.is_none() && !options.all && !options.seeded {
115 continue;
116 }
117 let seeded = policy.is_seeding(&rid)?;
118
119 if !seeded && !options.all {
120 continue;
121 }
122 if !seeded && options.seeded {
123 continue;
124 }
125 let proj = match doc.project() {
126 Ok(p) => p,
127 Err(e) => {
128 log::error!(target: "cli", "Error loading project payload for {rid}: {e}");
129 continue;
130 }
131 };
132 let head = term::format::oid(head).into();
133
134 rows.push([
135 term::format::bold(proj.name().to_owned()),
136 term::format::tertiary(rid.urn()),
137 if seeded {
138 term::format::visibility(doc.visibility()).into()
139 } else {
140 term::format::dim("local").into()
141 },
142 term::format::secondary(head),
143 term::format::italic(proj.description().to_owned()),
144 ]);
145 }
146 rows.sort();
147
148 if rows.is_empty() {
149 term::print(term::format::italic("Nothing to show."));
150 } else {
151 table.header([
152 "Name".into(),
153 "RID".into(),
154 "Visibility".into(),
155 "Head".into(),
156 "Description".into(),
157 ]);
158 table.divider();
159 table.extend(rows);
160 table.print();
161 }
162
163 Ok(())
164}