radicle_cli/commands/
ls.rs

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
use std::ffi::OsString;

use radicle::storage::{ReadStorage, RepositoryInfo};

use crate::terminal as term;
use crate::terminal::args::{Args, Error, Help};

use term::Element;

pub const HELP: Help = Help {
    name: "ls",
    description: "List repositories",
    version: env!("RADICLE_VERSION"),
    usage: r#"
Usage

    rad ls [<option>...]

    By default, this command shows you all repositories that you have forked or initialized.
    If you wish to see all seeded repositories, use the `--all` option.

Options

    --private       Show only private repositories
    --public        Show only public repositories
    --seeded, -s    Show all seeded repositories
    --all, -a       Show all repositories in storage
    --verbose, -v   Verbose output
    --help          Print help
"#,
};

pub struct Options {
    #[allow(dead_code)]
    verbose: bool,
    public: bool,
    private: bool,
    all: bool,
    seeded: bool,
}

impl Args for Options {
    fn from_args(args: Vec<OsString>) -> anyhow::Result<(Self, Vec<OsString>)> {
        use lexopt::prelude::*;

        let mut parser = lexopt::Parser::from_args(args);
        let mut verbose = false;
        let mut private = false;
        let mut public = false;
        let mut all = false;
        let mut seeded = false;

        while let Some(arg) = parser.next()? {
            match arg {
                Long("help") | Short('h') => {
                    return Err(Error::Help.into());
                }
                Long("all") | Short('a') => {
                    all = true;
                }
                Long("seeded") | Short('s') => {
                    seeded = true;
                }
                Long("private") => {
                    private = true;
                }
                Long("public") => {
                    public = true;
                }
                Long("verbose") | Short('v') => verbose = true,
                _ => return Err(anyhow::anyhow!(arg.unexpected())),
            }
        }

        Ok((
            Options {
                verbose,
                private,
                public,
                all,
                seeded,
            },
            vec![],
        ))
    }
}

pub fn run(options: Options, ctx: impl term::Context) -> anyhow::Result<()> {
    let profile = ctx.profile()?;
    let storage = &profile.storage;
    let repos = storage.repositories()?;
    let policy = profile.policies()?;
    let mut table = term::Table::new(term::TableOptions::bordered());
    let mut rows = Vec::new();

    if repos.is_empty() {
        return Ok(());
    }

    for RepositoryInfo {
        rid,
        head,
        doc,
        refs,
        ..
    } in repos
    {
        if doc.is_public() && options.private && !options.public {
            continue;
        }
        if !doc.is_public() && !options.private && options.public {
            continue;
        }
        if refs.is_none() && !options.all && !options.seeded {
            continue;
        }
        let seeded = policy.is_seeding(&rid)?;

        if !seeded && !options.all {
            continue;
        }
        if !seeded && options.seeded {
            continue;
        }
        let proj = match doc.project() {
            Ok(p) => p,
            Err(e) => {
                log::error!(target: "cli", "Error loading project payload for {rid}: {e}");
                continue;
            }
        };
        let head = term::format::oid(head).into();

        rows.push([
            term::format::bold(proj.name().to_owned()),
            term::format::tertiary(rid.urn()),
            if seeded {
                term::format::visibility(doc.visibility()).into()
            } else {
                term::format::dim("local").into()
            },
            term::format::secondary(head),
            term::format::italic(proj.description().to_owned()),
        ]);
    }
    rows.sort();

    if rows.is_empty() {
        term::print(term::format::italic("Nothing to show."));
    } else {
        table.header([
            "Name".into(),
            "RID".into(),
            "Visibility".into(),
            "Head".into(),
            "Description".into(),
        ]);
        table.divider();
        table.extend(rows);
        table.print();
    }

    Ok(())
}