• Home
  • Features
  • Pricing
  • Docs
  • Announcements
  • Sign In

dustinblackman / oatmeal / 7024900226

28 Nov 2023 09:44PM UTC coverage: 49.79%. Remained the same
7024900226

Pull #4

github

web-flow
chore(deps): Bump openssl from 0.10.57 to 0.10.60

Bumps [openssl](https://github.com/sfackler/rust-openssl) from 0.10.57 to 0.10.60.
- [Release notes](https://github.com/sfackler/rust-openssl/releases)
- [Commits](https://github.com/sfackler/rust-openssl/compare/openssl-v0.10.57...openssl-v0.10.60)

---
updated-dependencies:
- dependency-name: openssl
  dependency-type: indirect
...

Signed-off-by: dependabot[bot] <support@github.com>
Pull Request #4: chore(deps): Bump openssl from 0.10.57 to 0.10.60

1066 of 2141 relevant lines covered (49.79%)

16.29 hits per line

Source File
Press 'n' to go to next uncovered line, 'b' for previous

0.0
/src/application/cli.rs
1
use std::io;
2

3
use anyhow::Result;
4
use clap::value_parser;
5
use clap::Arg;
6
use clap::ArgAction;
7
use clap::ArgGroup;
8
use clap::Command;
9
use clap_complete::generate;
10
use clap_complete::Generator;
11
use clap_complete::Shell;
12
use dialoguer::theme::ColorfulTheme;
13
use dialoguer::Select;
14
use owo_colors::OwoColorize;
15
use owo_colors::Stream;
16

17
use crate::config::Config;
18
use crate::config::ConfigKey;
19
use crate::domain::models::Session;
20
use crate::domain::services::actions::help_text;
21
use crate::domain::services::Sessions;
22
use crate::domain::services::Themes;
23

24
fn print_completions<G: Generator>(gen: G, cmd: &mut Command) {
×
25
    generate(gen, cmd, cmd.get_name().to_string(), &mut io::stdout());
×
26
    std::process::exit(0);
×
27
}
28

29
fn format_session(session: &Session) -> String {
×
30
    let mut res = format!(
×
31
        "- (ID: {}) {}, Model: {}",
×
32
        session.id.bold(),
×
33
        session.timestamp,
×
34
        session.state.backend_model,
×
35
    );
×
36

×
37
    if !session.state.editor_language.is_empty() {
×
38
        res = format!("{res}, Lang: {}", session.state.editor_language)
×
39
    }
×
40

41
    return res;
×
42
}
×
43

44
async fn print_sessions_list() -> Result<()> {
×
45
    let mut sessions = Sessions::default()
×
46
        .list()
×
47
        .await?
×
48
        .iter()
×
49
        .map(|session| {
×
50
            return format_session(session);
×
51
        })
×
52
        .collect::<Vec<String>>();
×
53

×
54
    sessions.reverse();
×
55

×
56
    if sessions.is_empty() {
×
57
        println!("There are no sessions available. You should start your first one!");
×
58
    } else {
×
59
        println!("{}", sessions.join("\n"));
×
60
    }
×
61

62
    return Ok(());
×
63
}
×
64

65
async fn load_config_from_session(session_id: &str) -> Result<()> {
×
66
    let session = Sessions::default().load(session_id).await?;
×
67
    Config::set(ConfigKey::Backend, &session.state.backend_name);
×
68
    Config::set(ConfigKey::Model, &session.state.backend_model);
×
69
    Config::set(ConfigKey::SessionID, session_id);
×
70

×
71
    return Ok(());
×
72
}
×
73

74
async fn load_config_from_session_interactive() -> Result<()> {
×
75
    let mut sessions = Sessions::default().list().await?;
×
76
    sessions.reverse();
×
77

×
78
    if sessions.is_empty() {
×
79
        println!("There are no sessions available. You should start your first one!");
×
80
        return Ok(());
×
81
    }
×
82

×
83
    let session_options = sessions
×
84
        .iter()
×
85
        .map(|session| {
×
86
            return format_session(session);
×
87
        })
×
88
        .collect::<Vec<String>>();
×
89

90
    let idx = Select::with_theme(&ColorfulTheme::default())
×
91
        .with_prompt("Which session would you like to load?")
×
92
        .default(0)
×
93
        .items(&session_options)
×
94
        .interact_opt()?
×
95
        .unwrap();
×
96

×
97
    load_config_from_session(&sessions[idx].id).await?;
×
98

99
    return Ok(());
×
100
}
×
101

102
fn subcommand_completions() -> Command {
×
103
    return Command::new("completions")
×
104
        .about("Generates shell completions")
×
105
        .arg(
×
106
            clap::Arg::new("shell")
×
107
                .short('s')
×
108
                .long("shell")
×
109
                .help("Which shell to generate completions for")
×
110
                .action(ArgAction::Set)
×
111
                .value_parser(value_parser!(Shell))
×
112
                .required(true),
×
113
        );
×
114
}
×
115

116
fn subcommand_sessions_delete() -> Command {
×
117
    return Command::new("delete")
×
118
        .about("Delete one or all sessions")
×
119
        .arg(
×
120
            clap::Arg::new("session-id")
×
121
                .short('i')
×
122
                .long("id")
×
123
                .help("Session ID")
×
124
                .num_args(1),
×
125
        )
×
126
        .arg(
×
127
            clap::Arg::new("all")
×
128
                .long("all")
×
129
                .help("Delete all sessions")
×
130
                .num_args(0),
×
131
        )
×
132
        .group(
×
133
            ArgGroup::new("delete-args")
×
134
                .args(["session-id", "all"])
×
135
                .required(true),
×
136
        );
×
137
}
×
138

139
fn arg_backend() -> Arg {
×
140
    return Arg::new("backend")
×
141
        .short('b')
×
142
        .long("backend")
×
143
        .env("OATMEAL_BACKEND")
×
144
        .num_args(1)
×
145
        .help(
×
146
            "The initial backend hosting a model to connect to. [Possible values: ollama, openai]",
×
147
        )
×
148
        .default_value("ollama");
×
149
}
×
150

151
fn arg_model() -> Arg {
×
152
    return Arg::new("model")
×
153
        .short('m')
×
154
        .long("model")
×
155
        .env("OATMEAL_MODEL")
×
156
        .num_args(1)
×
157
        .help("The initial model on a backend to consume")
×
158
        .default_value("llama2:latest");
×
159
}
×
160

161
fn subcommand_chat() -> Command {
×
162
    return Command::new("chat")
×
163
        .about("Start a new chat session")
×
164
        .arg(arg_backend())
×
165
        .arg(arg_model());
×
166
}
×
167

168
fn subcommand_sessions() -> Command {
×
169
    return Command::new("sessions")
×
170
        .about("Manage past chat sessions")
×
171
        .arg_required_else_help(true)
×
172
        .subcommand(Command::new("dir").about("Print the sessions cache directory path"))
×
173
        .subcommand(Command::new("list").about("List all previous sessions with their ids and models"))
×
174
        .subcommand(
×
175
            Command::new("open")
×
176
                .about("Open a previous session by ID. Omit passing any session ID to load an interactive selection")
×
177
                .arg(
×
178
                    clap::Arg::new("session-id")
×
179
                        .short('i')
×
180
                        .long("id")
×
181
                        .help("Session ID")
×
182
                        .required(false),
×
183
                ),
×
184
        )
×
185
        .subcommand(subcommand_sessions_delete());
×
186
}
×
187

188
fn build() -> Command {
×
189
    let commands_text = help_text()
×
190
        .split('\n')
×
191
        .map(|line| {
×
192
            if line.starts_with('-') {
×
193
                return format!("  {line}");
×
194
            }
×
195
            if line.starts_with("COMMANDS:")
×
196
                || line.starts_with("HOTKEYS:")
×
197
                || line.starts_with("CODE ACTIONS:")
×
198
            {
199
                return format!("CHAT {line}")
×
200
                    .if_supports_color(Stream::Stdout, |text| {
×
201
                        return text.underline().bold().to_string();
×
202
                    })
×
203
                    .to_string();
×
204
            }
×
205
            return line.to_string();
×
206
        })
×
207
        .collect::<Vec<String>>()
×
208
        .join("\n");
×
209

×
210
    let about = format!(
×
211
        "{}\n\nVersion: {}\nCommit: {}",
×
212
        env!("CARGO_PKG_DESCRIPTION"),
×
213
        env!("CARGO_PKG_VERSION"),
×
214
        env!("VERGEN_GIT_DESCRIBE")
×
215
    );
×
216
    let themes = Themes::list().join(", ");
×
217

×
218
    return Command::new("oatmeal")
×
219
        .about(about)
×
220
        .author(env!("CARGO_PKG_AUTHORS"))
×
221
        .version(env!("CARGO_PKG_VERSION"))
×
222
        .after_help(commands_text)
×
223
        .arg_required_else_help(false)
×
224
        .subcommand(subcommand_chat())
×
225
        .subcommand(subcommand_completions())
×
226
        .subcommand(subcommand_sessions())
×
227
        .arg(arg_backend())
×
228
        .arg(arg_model())
×
229
        .arg(
×
230
            Arg::new("editor")
×
231
                .short('e')
×
232
                .long("editor")
×
233
                .env("OATMEAL_EDITOR")
×
234
                .num_args(1)
×
235
                .help("The editor to integrate with. [Possible values: clipboard, neovim]")
×
236
                .default_value("clipboard")
×
237
                .global(true),
×
238
        )
×
239
        .arg(
×
240
            Arg::new("theme")
×
241
                .short('t')
×
242
                .long("theme")
×
243
                .env("OATMEAL_THEME")
×
244
                .num_args(1)
×
245
                .help(format!(
×
246
                    "Sets code syntax highlighting theme. [Possible values: {themes}]"
×
247
                ))
×
248
                .default_value("base16-onedark")
×
249
                .global(true),
×
250
        )
×
251
        .arg(
×
252
            Arg::new("theme-file")
×
253
                .long("theme-file")
×
254
                .env("OATMEAL_THEME_FILE")
×
255
                .num_args(1)
×
256
                .help(
×
257
                    "Absolute path to a TextMate tmTheme to use for code syntax highlighting"
×
258
                )
×
259
                .global(true),
×
260
        )
×
261
        .arg(
×
262
            Arg::new("openai-url")
×
263
                .long("openai-url")
×
264
                .env("OATMEAL_OPENAI_URL")
×
265
                .num_args(1)
×
266
                .help("OpenAI API URL when using the OpenAI backend. Can be swapped to a compatiable proxy")
×
267
                .default_value("https://api.openai.com")
×
268
                .global(true),
×
269
            )
×
270
        .arg(
×
271
            Arg::new("openai-token")
×
272
                .long("openai-token")
×
273
                .env("OATMEAL_OPENAI_TOKEN")
×
274
                .num_args(1)
×
275
                .help("OpenAI API token when using the OpenAI backend")
×
276
                .global(true),
×
277
        );
×
278
}
×
279

280
pub async fn parse() -> Result<bool> {
×
281
    let matches = build().get_matches();
×
282

×
283
    match matches.subcommand() {
×
284
        Some(("chat", subcmd_matches)) => {
×
285
            Config::set(
×
286
                ConfigKey::Backend,
×
287
                subcmd_matches.get_one::<String>("backend").unwrap(),
×
288
            );
×
289
            Config::set(
×
290
                ConfigKey::Model,
×
291
                subcmd_matches.get_one::<String>("model").unwrap(),
×
292
            );
×
293
        }
×
294
        Some(("completions", subcmd_matches)) => {
×
295
            if let Some(completions) = subcmd_matches.get_one::<Shell>("shell").copied() {
×
296
                let mut app = build();
×
297
                print_completions(completions, &mut app);
×
298
            }
×
299
        }
300
        Some(("sessions", subcmd_matches)) => {
×
301
            match subcmd_matches.subcommand() {
×
302
                Some(("dir", _)) => {
×
303
                    let dir = Sessions::default().cache_dir.to_string_lossy().to_string();
×
304
                    println!("{dir}");
×
305
                    return Ok(false);
×
306
                }
307
                Some(("list", _)) => {
×
308
                    print_sessions_list().await?;
×
309
                    return Ok(false);
×
310
                }
311
                Some(("open", open_matches)) => {
×
312
                    if let Some(session_id) = open_matches.get_one::<String>("session-id") {
×
313
                        load_config_from_session(session_id).await?;
×
314
                    } else {
315
                        load_config_from_session_interactive().await?;
×
316
                    }
317
                }
318
                Some(("delete", delete_matches)) => {
×
319
                    if let Some(session_id) = delete_matches.get_one::<String>("session-id") {
×
320
                        Sessions::default().delete(session_id).await?;
×
321
                        println!("Deleted session {session_id}");
×
322
                    } else if delete_matches.get_one::<bool>("all").is_some() {
×
323
                        Sessions::default().delete_all().await?;
×
324
                        println!("Deleted all sessions");
×
325
                    } else {
326
                        subcommand_sessions_delete().print_long_help()?;
×
327
                    }
328
                    return Ok(false);
×
329
                }
330
                _ => {
331
                    subcommand_sessions().print_long_help()?;
×
332
                    return Ok(false);
×
333
                }
334
            }
335
        }
336
        _ => {
×
337
            Config::set(
×
338
                ConfigKey::Backend,
×
339
                matches.get_one::<String>("backend").unwrap(),
×
340
            );
×
341
            Config::set(
×
342
                ConfigKey::Model,
×
343
                matches.get_one::<String>("model").unwrap(),
×
344
            );
×
345
        }
×
346
    }
347

348
    Config::set(
×
349
        ConfigKey::Editor,
×
350
        matches.get_one::<String>("editor").unwrap(),
×
351
    );
×
352
    Config::set(
×
353
        ConfigKey::Theme,
×
354
        matches.get_one::<String>("theme").unwrap(),
×
355
    );
×
356
    Config::set(
×
357
        ConfigKey::OpenAIURL,
×
358
        matches.get_one::<String>("openai-url").unwrap(),
×
359
    );
×
360

361
    if let Some(theme_file) = matches.get_one::<String>("theme-file") {
×
362
        Config::set(ConfigKey::ThemeFile, theme_file);
×
363
    }
×
364

365
    if let Some(openai_token) = matches.get_one::<String>("openai-token") {
×
366
        Config::set(ConfigKey::OpenAIToken, openai_token);
×
367
    }
×
368

369
    return Ok(true);
×
370
}
×
STATUS · Troubleshooting · Open an Issue · Sales · Support · CAREERS · ENTERPRISE · START FREE · SCHEDULE DEMO
ANNOUNCEMENTS · TWITTER · TOS & SLA · Supported CI Services · What's a CI service? · Automated Testing

© 2026 Coveralls, Inc