I want to use the crate dialoguer to let the user check and correct some suggested data.
Cargo.toml dependencies
[dependencies]
dialoguer = "0.7"
src/main.rs
use dialoguer::Input;
fn main() {
// searching the web
std::thread::sleep(std::time::Duration::from_secs(5));
let suggestion = "Catpictures".to_string();
let data : String = Input::new()
.with_prompt("suggested")
.with_initial_text(suggestion)
.interact_text()
.expect("failed to correct suggestion");
}
My problem is that, while the program searches for the suggestion, the user might start typing and may press ENTER.
Then the program displays the suggestion and immediately accepts the answer.
I would like to prevent this behavior.
Current behavior:
starting program
hitting enter (for what ever reason)
program displays suggestion and immediately accepts suggestion
desired behavior:
starting program
hitting enter (for what ever reason)
program displays suggestion
user can edit suggestion and accept with enter
Is there a way to clear the input?
Neither the standard library nor dialoguer features functionality for clearing stdin.
One workaround is to use the AsyncReader from the crossterm_input crate, in which you can poll input (events) from the stdin. That way you'll be able to clear any pending input, before using dialoguer.
// crossterm_input = "0.5"
fn clear_stdin() {
let input = crossterm_input::input();
let mut async_stdin = input.read_async();
while let Some(_) = async_stdin.next() {}
}
Your updated example, will then look like this:
use dialoguer::Input;
fn main() {
// searching the web
thread::sleep(time::Duration::from_millis(200));
let suggestion = "Catpictures".to_string();
clear_stdin();
let data = Input::new()
.with_prompt("suggested")
.with_initial_text(suggestion)
.interact_text()
.expect("failed to correct suggestion");
}
Related
The following code removes the _ character from png files in a folder:
use std::fs;
use std::path::Path;
fn main() {
let dir = Path::new("/home/alex/Desktop");
for entry in fs::read_dir(dir).unwrap() {
let entry = entry.unwrap();
let path = entry.path();
if path.is_file() && path.extension().unwrap() == "png" {
let new_path = path.with_file_name(path.file_name().unwrap().to_str().unwrap().replace("_",""));
fs::rename(path, new_path).unwrap();
}
}
}
As you can see unwrap() is used a lot. It is possible to remove them in this code, and use a cleaner approach?
You're using unwrap for several different things here. Let's break them down.
fs::read_dir(dir).unwrap()
read_dir can fail if an IO error occurs. That's not something under your control, and it's not something you can deal with. Using the excellent vexing exceptions analogy, this error would be an exogenous one: not your fault and not something you can prevent. unwrap makes sense here. In a larger program, we might let our function return io::Result<_> and could write fs::read_dir(dir)? to let the caller try to recover from the error. But for a small main-only program, unwrap makes sense here.
let entry = entry.unwrap();
Same thing. It's an IO error out of your hands. In a larger program, you would write entry? to propagate the error to the caller, but here on this small scale, unwrap is fine.
path.extension().unwrap()
Here's where things get interesting. extension doesn't fail. It returns None in the completely normal, reasonable situation where the file doesn't have an extension. For instance, if the file is named Rakefile or .gitignore. Panicking in this case is really unfortunate. Instead, we simply want the if statement to fail. What your if statement says right now is "assert that the extension exists, and do something if it's png". What you really want is to say "if the extension exists and is png". No assertion necessary. Consider
if let Some(extension) = path.extension() {
if extension == "png" {
...
}
}
In future versions of Rust, it will be possible to write if let in conjunction with &&, so we'll be able to shorten this to
if let Some(extension) = path.extension() && extension == "png" {
...
}
But that feature is unstable right now.
Moving on, I'm skipping over the line with several unwrap calls right now. We'll come back to that in a minute.
fs::rename(path, new_path).unwrap();
fs::rename is an IO operation and can fail like any IO operation can. Let it fail, or propagate in case of a containing function, just like the first two.
Now let's talk about the last line.
path.with_file_name(path.file_name().unwrap().to_str().unwrap().replace("_",""));
file_name() returns None if there's no filename. In that case, we shouldn't even be trying to rename the file, so that should be something we check in an if let before we get here.
if let Some(filename) = path.file_name() {
...
}
Next, you're using to_str. The reason you need to do this is that filenames use OsStr, which may or may not be valid UTF-8. So if you want to panic on such filenames, that's fine. Personally (given how rare and bizarre that situation would be), I'd probably panic as well (or propagate, similar to the other IO exceptions). If you want to recover, you could use to_string_lossy, which replaces invalid UTF-8 sequences with U+FFFD.
If you want to propagate, you can convert Option into io::Result with ok_or_else.
Finally, since you do have a lot of IO going on here, I would actually recommend going ahead and factoring this out into a separate function that results an io::Result. Then main can call unwrap (or expect) once on the result to indicate any IO errors, but other callers could theoretically handle or recover from those same errors.
With all of that in mind, we get down to one expect call in main that deals (uniformly) with all of the IO errors as follows.
use std::fs;
use std::io;
use std::path::Path;
fn replace_files(dir: &Path) -> io::Result<()> {
for entry in fs::read_dir(dir)? {
let path = entry?.path();
if let Some(extension) = path.extension() {
if let Some(filename) = path.file_name() {
if path.is_file() && extension == "png" {
let filename_utf8 =
filename.to_str()
.ok_or_else(|| io::Error::new(io::ErrorKind::Other, "Non-UTF-8 filename"))?;
let new_path = path.with_file_name(filename_utf8.replace("_",""));
fs::rename(path, new_path)?;
}
}
}
}
Ok(())
}
fn main() {
let dir = Path::new("/home/alex/Desktop");
replace_files(dir).expect("I/O error occurred!");
}
mod loginfo{
use std::io::Error;
use chrono::prelude::*;
use std::io::prelude::*;
use std::fs::OpenOptions;
const LOG_SYS :&'static str = "log.txt";
const LOG_ERR :&'static str = "log_error.txt";
pub fn set_log_error(info: String)->Result<(), String>{
let mut handler = OpenOptions::new().append(true)
.open(LOG_ERR);
if handler.is_err(){
create_file(LOG_ERR.to_owned()).unwrap();
set_log_error(info).unwrap();
}
if let Err(_errno) = handler.write_fmt(
format_args!("{:?}\t{:?} ->[Last OS error({:?})]\n",
Utc::now().to_rfc2822().to_string(), info,
Error::last_os_error()) ){
panic!(
"\nCannot write info log error\t Info\t:{:?}\n",
Error::last_os_error());
}
Ok(())
}
pub fn set_log(info: String)->Result<(), String>{
let mut handler = OpenOptions::new().append(true)
.open(LOG_SYS);
if handler.is_err(){
set_log_error("Cannot write info log".to_owned())
.unwrap();
}
if let Err(_errno) = handler.write_fmt(
format_args!("{:?}\t{:?}\n",
Utc::now().to_rfc2822().to_string(), info)){
set_log_error("Cannot write data log file".to_owned())
.unwrap();
}
Ok(())
}
pub fn create_file(filename : String)->Result<(), String>{
let handler = OpenOptions::new().write(true)
.create(true).open(filename);
if handler.is_err(){
panic!(
"\nCannot create log file\t Info\t:{:?}\n",
Error::last_os_error());
}
Ok(())
}
}
When compiling, I get the following errors, "error[E0599]: no method named write_fmt found for enum std::result::Result<std::fs::File, std::io::Error> in the current scope --> src/loginfo.rs:19:38`"
but despite using the right imports, I still get the same errors. Is this due to a bad implementation of the module?
Thank you in advance for your answers and remarks?
+1 #Masklinn Ok I think I understand it would be easier to just write
pub fn foo_write_log( info: String){
let mut handler = OpenOptions::new().append(true)
.create(true).open(LOG_SYS).expect("Cannot create log");
handler.write_fmt(
format_args!("{:?}\t{:?} ->[Last OS error({:?})]\n",
Utc::now().to_rfc2822().to_string(), info,
Error::last_os_error())).unwrap();
}
but despite using the right imports, I still get the same errors. Is this due to a bad implementation of the module?
Kind-of? If you look at the type specified in the error, handler is a Result<File, Error>. And while io::Write is implemented on File, it's not implemented on Result.
The problem is that while you're checking whether handler.is_err() you never get the file out of it, nor do you ever return in the error case. Normally you'd use something like match or if let or one of the higher-order methods (e.g. Result::map, Result::and_then) in order to handle or propagate the various cases.
And to be honest the entire thing is rather odd and awkward e.g. your functions can fail but they panic instead (you never actually return an Err); if you're going to try and create a file when opening it for writing fails, why not just do that directly[0]; you are manually calling write_fmt and format_args why not just write!; write_fmt already returns an io::Error why do you discard it then ask for it again via Error::last_os_error; etc...
It's also a bit strange to hand-roll your own logger thing when the rust ecosystem already has a bunch of them though you do you; and the naming is also somewhat awkward e.g. I'd expect something called set_X to actually set the X, so to me set_log would be a way to set the file being logged to.
[0] .create(true).append(true) should open the file in append mode if it exists and create it otherwise; not to mention your version has a concurrency issue: if the open-for-append fails you create the file in write mode, but someone else could have created the file -- with content -- between the two calls, in which case you're going to partially overwrite the file
The function is executed without error and returns Ok(()), but the text is not pushed into the clipboard:
pub fn copy_text(text_fragment: winrt::HString) -> winrt::Result<()> {
let data_package = DataPackage::new()?;
data_package.set_text(text_fragment)?;
Clipboard::set_content(data_package)
}
The documentation about the Windows runtime API has the following statement for the Clipboard.SetContent(DataPackage) function:
Use this method after creating and defining a DataPackage with the data you want to put on the clipboard. Call this method only when the application is in the foreground, or when a debugger is attached.
Is there any way I can use that function without a UI?
I don't know whether it's officially supported on a non-UI thread, but it seems to work if you add a call to flush as follows:
use windows::application_model::data_transfer::*;
fn main() -> winrt::Result<()> {
let content = DataPackage::new()?;
content.set_text("hello world from Rust")?;
Clipboard::set_content(content)?;
Clipboard::flush()?;
Ok(())
}
The flush method ensures that the content is copied onto the clipboard and will remain there even if the sending application/process terminates.
I'm trying to implement a project where I can tail the logs of multiple Kubernetes container logs simultaneously. Think tmux split pane with two tails in each pane. Anyway, I'm far far away from my actual project because I'm stuck right at the beginning. If you look at the following code then the commented out line for lp.follow = true will keep the log stream open and stream logs forever. I'm not sure how to actually use this. I found a function called .into_stream() that I can tack onto the pods.log function, but then I'm not sure how to actually use the stream. I'm not experienced enough to know if this is a limitation of the kube library, or if I'm just doing something wrong. Anyway, here is the repo if you want to look at anything else. https://github.com/bloveless/kube-logger
I'd be forever grateful for any advice or resources I can look at. Thanks!
use kube::{
api::Api,
client::APIClient,
config,
};
use kube::api::{LogParams, RawApi};
use futures::{FutureExt, Stream, future::IntoStream, StreamExt};
#[tokio::main]
async fn main() -> anyhow::Result<()> {
std::env::set_var("RUST_LOG", "info,kube=trace");
let config = config::load_kube_config().await?;
let client = APIClient::new(config);
// Manage pods
let pods = Api::v1Pod(client).within("fritzandandre");
let mut lp = LogParams::default();
lp.container = Some("php".to_string());
// lp.follow = true;
lp.tail_lines = Some(100);
let log_string = pods.log("fritzandandre-php-0", &lp).await?;
println!("FnA Log: {}", log_string);
Ok(())
}
Originally posted here https://www.reddit.com/r/learnrust/comments/eg49tx/help_with_futuresstreams_and_the_kubers_library/
Is there a way to check whether data is available on stdin in Rust, or to do a read that returns immediately with the currently available data?
My goal is to be able to read the input produced for instance by cursor keys in a shell that is setup to return all read data immediately. For instance with an equivalent to: stty -echo -echok -icanon min 1 time 0.
I suppose one solution would be to use ncurses or similar libraries, but I would like to avoid any kind of large dependencies.
So far, I got only blocking input, which is not what I want:
let mut reader = stdin();
let mut s = String::new();
match reader.read_to_string(&mut s) {...} // this blocks :(
Converting OP's comment into an answer:
You can spawn a thread and send data over a channel. You can then poll that channel in the main thread using try_recv.
use std::io;
use std::sync::mpsc;
use std::sync::mpsc::Receiver;
use std::sync::mpsc::TryRecvError;
use std::{thread, time};
fn main() {
let stdin_channel = spawn_stdin_channel();
loop {
match stdin_channel.try_recv() {
Ok(key) => println!("Received: {}", key),
Err(TryRecvError::Empty) => println!("Channel empty"),
Err(TryRecvError::Disconnected) => panic!("Channel disconnected"),
}
sleep(1000);
}
}
fn spawn_stdin_channel() -> Receiver<String> {
let (tx, rx) = mpsc::channel::<String>();
thread::spawn(move || loop {
let mut buffer = String::new();
io::stdin().read_line(&mut buffer).unwrap();
tx.send(buffer).unwrap();
});
rx
}
fn sleep(millis: u64) {
let duration = time::Duration::from_millis(millis);
thread::sleep(duration);
}
Most operating systems default to work with the standard input and output in a blocking way. No wonder then that the Rust library follows in stead.
To read from a blocking stream in a non-blocking way you might create a separate thread, so that the extra thread blocks instead of the main one. Checking whether a blocking file descriptor produced some input is similar: spawn a thread, make it read the data, check whether it produced any data so far.
Here's a piece of code that I use with a similar goal of processing a pipe output interactively and that can hopefully serve as an example. It sends the data over a channel, which supports the try_recv method - allowing you to check whether the data is available or not.
Someone has told me that mio might be used to read from a pipe in a non-blocking way, so you might want to check it out too. I suspect that passing the stdin file descriptor (0) to Receiver::from_raw_fd should just work.
You could also potentially look at using ncurses (also on crates.io) which would allow you read in raw mode. There are a few examples in the Github repository which show how to do this.