Make clippy happy for new REPL, minor changes

- Move some code blocks to be able to remove string cloning
- Use const strings for user-facing REPL messages
- Fix running input code twice when interrupted
- Add notes for future extensions and considerations
This commit is contained in:
Filip Tibell 2023-08-16 10:56:25 -05:00
parent d8f6703c70
commit 2b855156bf
No known key found for this signature in database
2 changed files with 46 additions and 51 deletions

View file

@ -1,7 +1,7 @@
use std::{fmt::Write as _, process::ExitCode}; use std::{fmt::Write as _, process::ExitCode};
use anyhow::{Context, Result}; use anyhow::{Context, Result};
use clap::{CommandFactory, Parser}; use clap::Parser;
use lune::Lune; use lune::Lune;
use tokio::{ use tokio::{
@ -147,14 +147,9 @@ impl Cli {
if generate_file_requested { if generate_file_requested {
return Ok(ExitCode::SUCCESS); return Ok(ExitCode::SUCCESS);
} }
// If we did not generate any typedefs we know that the user did not
// HACK: We know that we didn't get any arguments here but since // provide any other options, and in that case we should enter the REPL
// script_path is optional clap will not error on its own, to fix return repl::show_interface().await;
// we will duplicate the CLI command and fetch the version of
// lune to display
let exit_code_status = repl::show_interface(Cli::command()).await;
return exit_code_status;
} }
// Figure out if we should read from stdin or from a file, // Figure out if we should read from stdin or from a file,
// reading from stdin is marked by passing a single "-" // reading from stdin is marked by passing a single "-"

View file

@ -1,42 +1,40 @@
use std::{path::PathBuf, process::ExitCode}; use std::{path::PathBuf, process::ExitCode};
use anyhow::{Error, Result}; use anyhow::{Context, Result};
use clap::Command;
use directories::UserDirs; use directories::UserDirs;
use lune::Lune;
use rustyline::{error::ReadlineError, DefaultEditor}; use rustyline::{error::ReadlineError, DefaultEditor};
use lune::Lune;
const MESSAGE_WELCOME: &str = concat!("Lune v", env!("CARGO_PKG_VERSION"));
const MESSAGE_INTERRUPT: &str = "Interrupt: ^C again to exit";
#[derive(PartialEq)] #[derive(PartialEq)]
enum PromptState { enum PromptState {
Regular, Regular,
Continuation, Continuation,
} }
// Isn't dependency injection plain awesome?! pub async fn show_interface() -> Result<ExitCode> {
pub async fn show_interface(cmd: Command) -> Result<ExitCode> { println!("{MESSAGE_WELCOME}");
let lune_version = cmd.get_version();
// The version is mandatory and will always exist
println!("Lune v{}", lune_version.unwrap());
let lune_instance = Lune::new();
let mut repl = DefaultEditor::new()?;
let history_file_path: &PathBuf = &UserDirs::new() let history_file_path: &PathBuf = &UserDirs::new()
.ok_or(Error::msg("cannot find user home directory"))? .context("Failed to find user home directory")?
.home_dir() .home_dir()
.join(".lune_history"); .join(".lune_history");
if !history_file_path.exists() { if !history_file_path.exists() {
std::fs::write(&history_file_path, String::new())?; tokio::fs::write(history_file_path, &[]).await?;
} }
let mut repl = DefaultEditor::new()?;
repl.load_history(history_file_path)?; repl.load_history(history_file_path)?;
let mut interrupt_counter = 0u32; let mut interrupt_counter = 0;
let mut prompt_state: PromptState = PromptState::Regular; let mut prompt_state = PromptState::Regular;
let mut source_code = String::new(); let mut source_code = String::new();
let lune_instance = Lune::new();
loop { loop {
let prompt = match prompt_state { let prompt = match prompt_state {
PromptState::Regular => "> ", PromptState::Regular => "> ",
@ -45,43 +43,43 @@ pub async fn show_interface(cmd: Command) -> Result<ExitCode> {
match repl.readline(prompt) { match repl.readline(prompt) {
Ok(code) => { Ok(code) => {
if prompt_state == PromptState::Continuation {
source_code.push_str(&code);
} else if prompt_state == PromptState::Regular {
source_code = code.clone();
}
repl.add_history_entry(code.as_str())?;
// If source code eval was requested, we reset the counter
interrupt_counter = 0; interrupt_counter = 0;
}
Err(ReadlineError::Interrupted) => { // TODO: Should we add history entries for each separate line?
// HACK: We actually want the user to do ^C twice to exit, // Or should we add and save history only when we have complete
// but the user would need to ^C one more time even after // lua input that may or may not be multiple lines long?
// the check passes, so we check for 1 instead of 2 repl.add_history_entry(&code)?;
if interrupt_counter != 1 { repl.save_history(history_file_path)?;
println!("Interrupt: ^C again to exit");
// Increment the counter match prompt_state {
interrupt_counter += 1; PromptState::Regular => source_code = code,
} else { PromptState::Continuation => source_code.push_str(&code),
repl.save_history(history_file_path)?;
break;
} }
} }
Err(ReadlineError::Eof) => {
repl.save_history(history_file_path)?; Err(ReadlineError::Eof) => break,
Err(ReadlineError::Interrupted) => {
interrupt_counter += 1;
// NOTE: We actually want the user to do ^C twice to exit,
// and if we get an interrupt we should continue to the next
// readline loop iteration so we don't run input code twice
if interrupt_counter == 1 {
println!("{MESSAGE_INTERRUPT}");
continue;
}
break; break;
} }
Err(err) => { Err(err) => {
eprintln!("REPL ERROR: {err}"); eprintln!("REPL ERROR: {err}");
return Ok(ExitCode::FAILURE); return Ok(ExitCode::FAILURE);
} }
}; };
let eval_result = lune_instance.run("REPL", source_code.clone()).await; // TODO: Preserve context here somehow?
let eval_result = lune_instance.run("REPL", &source_code).await;
match eval_result { match eval_result {
Ok(_) => prompt_state = PromptState::Regular, Ok(_) => prompt_state = PromptState::Regular,
@ -89,7 +87,7 @@ pub async fn show_interface(cmd: Command) -> Result<ExitCode> {
Err(err) => { Err(err) => {
if err.is_incomplete_input() { if err.is_incomplete_input() {
prompt_state = PromptState::Continuation; prompt_state = PromptState::Continuation;
source_code.push('\n') source_code.push('\n');
} else { } else {
eprintln!("{err}"); eprintln!("{err}");
} }
@ -97,5 +95,7 @@ pub async fn show_interface(cmd: Command) -> Result<ExitCode> {
}; };
} }
repl.save_history(history_file_path)?;
Ok(ExitCode::SUCCESS) Ok(ExitCode::SUCCESS)
} }