Rust: Create editable CLI input - rust

To read user input in rust, we can do this:
use std::io;
let mut input = String::new();
print!("Edit username: ");
io::stdout().flush().ok();
io::stdin().readline(&mut input);
What I want is a way to create editable input text, like filling the stdin with some text that the user can edit. Something like this:
stdout stdin
| |
Edit username: <existing username>

Related

getting row data from sql::query_as

Im trying to use sqlx to get data from a mysql db. I have the following:
#[derive(Debug, PartialEq, Eq, sqlx::FromRow)]
struct Room {
name: String
}
let mut stream = sqlx::query_as::<_, Room>(r#"SELECT name FROM rooms"#)
.fetch_all(&db.pool).await;
for row in stream {
println!("{:?}",row);
}
So in stream there is a vector and each index seems to hold the actual query results. So
stream[0] = [Room{name: "Room 1"}, Room{name: "Room 3"}, Room{name: "Room 2"}]
So in order to get at that data i have to loop through stream[0]. Is there a way to have that data on the value returned from the query without the explicit index?
Use Query::fetch() (this will return a stream of results, fetching the next one as needed) instead of Query::fetch_all() (this will return a vec of all results, fetching them all before hand), and then loop over the result stream.
Like so:
let mut stream = sqlx::query_as::<_, Room>(r#"SELECT name FROM rooms"#)
.fetch(&db.pool);
stream.for_each(|room| println!("{:#?}", room));
Alternatively, you can use a while loop to print, but you may need to Pin your stream before calling next().
while let Some(room) = stream.next().await {
println! ("{:#?}", room);
}

How to refer to the program name in help strings?

In my CLI program the usage examples are provided as part of the help message. Using the clap derive interface I can do the following
#[derive(Parser, Debug, Default)]
#[clap( after_help = "EXAMPLES:\n $ foo abc.txt")]
pub struct CmdLine {...}
The program name foo is hard coded in the literal string above.
How can I avoid hard-coding the program name and get it dynamically; for example, from std::env::args[0] or clap::App:get_bin_name() ?
clap provides a macro called crate_name! that will take the name from your cargo.toml.
For example, suppose you have this in your cargo.toml.
[package]
name = "myapp"
description = "myapp description"
version = "0.1.0"
edition = "2021"
authors = [ "John Doe" ]
Then, in your application, you can fetch these values using the macros, like this:
let matches = Command::new(clap::crate_name!())
.version(clap::crate_version!())
.author(clap::crate_authors!())
.about(clap::crate_description!())
//
// abbreviated
//
The section below is appended to respond to the original poster's specific question. See the comments below for context. Also, including some learnings as well.
Appended per the discussion in comments.
Based on the comments/discussion below, initial thought is just to stuff the binary name from the arguments into a string and pass into the after_help() function. For example, something like this:
let bin_name = std::env:args().into_iter().next().unwrap();
let matches = Command::new(bin_name)
.after_help(format!("Text that includes {}", bin_name)) // This won't compile
.get_matches();
Taking this approach, you quickly run into a lifetime requirement in the function signature for after_help(). From clap's repo:
pub fn after_help<S: Into<&'help str>>(mut self, help: S)
In fact, if you look, there are many fields in the Command struct that have the lifetime annotation (&'help) on them. The Command::new() method doesn't have this lifetime annotation so it worked fine to just pass it bin_name as shown above.
Below is an abbreviated solution that dynamically generates after-help text in a manner that adheres to the lifetime requirements. Assuming a clean binary (application), called "foo", add the following code:
cargo.toml
[package]
name = "foo"
version = "0.1.0"
description = "A foo cli application"
authors = [ "John Doe" ]
edition = "2021"
[dependencies]
clap = { version = "3.1.6", features = ["cargo"] }
main.rs
fn main() {
// Get the binary name from the command line
let bin_name = std::env::args().into_iter().next().unwrap();
// Construct text that will be used in after_help.
let after_help_text = format!(
"Some after-help text that includes the binary name: {}",
bin_name
);
// clap, by default, will reference the name of your package. So, if you're
// doing the above, you might as well override the usage text too so you're
// being consistent.
let usage_text = format!("{}", bin_name);
if let Err(e) = foo::get_args(bin_name, after_help_text, usage_text).and_then(foo::run) {
eprintln!("{e}");
std::process::exit(1);
}
}
lib.rs
use clap::{ArgMatches, Command};
pub fn get_args(
bin_name: String,
after_help_text: String,
usage_text: String,
) -> std::io::Result<ArgMatches> {
let matches = Command::new(bin_name)
.override_usage(usage_text.as_str())
.version(clap::crate_version!())
.after_help(after_help_text.as_str())
.author(clap::crate_authors!())
.about(clap::crate_description!())
// add and configure args...
.get_matches();
Result::Ok(matches)
}
pub fn run(matches: ArgMatches) -> std::io::Result<()> {
// Do your CLI logic here based on matches.
Ok(())
}
Running the solution ( cargo run -- --help ) will produce the following output:
./foo 0.1.0
John Doe
A foo cli application
USAGE:
./foo
OPTIONS:
-h, --help Print help information
-V, --version Print version information
Some after-help text that includes the binary name: ./foo

Is there a way to make a macro replace things in strings?

This macro should be able to replace entries in a string via an argument. For example, this would work:
let string = "Hello, world!";
replace_macro!(string, "world", "Rust"); // Hello, Rust!
I'm not sure how to do this, as all my previous attempts of just writing a regular function and calling that don't work inside macros. If possible, I'd like to be using macro_rules as opposed to a proc macro.
It is not possible. Macros cannot inspect and/or change the value of variables.
It is possible if the literal is embedded in the call (replace_macro!("Hello, world!", "world", "Rust");) but requires a proc-macro: macro_rules! macros cannot inspect and/or change literals.
It's a rather simple with a proc macro:
use quote::ToTokens;
use syn::parse::Parser;
use syn::spanned::Spanned;
type Args = syn::punctuated::Punctuated<syn::LitStr, syn::Token![,]>;
#[proc_macro]
pub fn replace_macro(input: proc_macro::TokenStream) -> proc_macro::TokenStream {
let input_span = input.span();
let args = match Args::parse_terminated.parse(input) {
Ok(args) => Vec::from_iter(args),
Err(err) => return err.into_compile_error().into(),
};
let (original, text, replacement) = match args.as_slice() {
[original, text, replacement] => (original.value(), text.value(), replacement.value()),
_ => {
return syn::Error::new(
input_span,
r#"expected `"<original>", "<text>", "<replacement>"`"#,
)
.into_compile_error()
.into()
}
};
original
.replace(&text, &replacement)
.into_token_stream()
.into()
}
It parses a list of three string literals, punctated by commas, then calls str::replace() to do the real work.

Initialize Gstreamer's PadProbeId to a default value in Rust

I am familiar with Gstreamer but new to Rust,
TLDR; I want to be able to initialize PadProbeId to a default value before using it.
The details:
I have a Bin (containing audio + video encoders and hlssink).
I have been able to add this bin to the pipeline and it works fine.
The issue I have is the audio for the stream is optional and I want to do add_probe() only when audio is available. Below is a simplified version fo what I tried to implement
let mut audio_probe_id: PadProbeId;
let mut tee_audio_pad: Pad;
if media_info.audio_available {
// get encoded audio from the tee
tee_audio_pad = audio_tee.request_pad_simple("src_%u").unwrap();
audio_probe_id = tee_audio_pad.add_probe(gst::PadProbeType::BLOCK_DOWNSTREAM, |_pad, _info| {
gst::PadProbeReturn::Ok
}).unwrap();
// link the audio_tee.src to enc_bin ghost pad
let audio_sink_pad = enc_bin.static_pad("audio").unwrap();
tee_audio_pad.link(&audio_sink_pad).unwrap();
}
enc_bin.call_async(move |bin| {
bin.sync_state_with_parent().unwrap();
if media_info.audio_available {
tee_audio_pad.remove_probe(audio_probe_id);
}
}
However because of Rust compilers restriction to using uninitialized variables, it does not let me use audio_probe_id without initializing.
I tried to initialize it like this; let mut audio_probe_id: PadProbeId = PadProbeId(NonZeroU64(u64::MAX));. However compiler complains that it is a private field.
error[E0423]: cannot initialize a tuple struct which contains private fields
Thanks a lot for your help!
The rust way to have empty variables like this is to use Option, but in your case it would simpler to have a single conditional:
if media_info.audio_available {
// get encoded audio from the tee
let tee_audio_pad = audio_tee.request_pad_simple("src_%u").unwrap();
let audio_probe_id = tee_audio_pad.add_probe(gst::PadProbeType::BLOCK_DOWNSTREAM, |_pad, _info| {
gst::PadProbeReturn::Ok
}).unwrap();
// link the audio_tee.src to enc_bin ghost pad
let audio_sink_pad = enc_bin.static_pad("audio").unwrap();
tee_audio_pad.link(&audio_sink_pad).unwrap();
enc_bin.call_async(move |bin| {
bin.sync_state_with_parent().unwrap();
tee_audio_pad.remove_probe(audio_probe_id);
}
} else {
enc_bin.call_async(move |bin| {
bin.sync_state_with_parent().unwrap();
});
}

How do I parse a page with html5ever, modify the DOM, and serialize it?

I would like to parse a web page, insert anchors at certain positions and render the modified DOM out again in order to generate docsets for Dash. Is this possible?
From the examples included in html5ever, I can see how to read an HTML file and do a poor man's HTML output, but I don't understand how I can modify the RcDom object I retrieved.
I would like to see a snippet inserting an anchor element (<a name="foo"></a>) to an RcDom.
Note: this is a question regarding Rust and html5ever specifically ... I know how to do it in other languages or simpler HTML parsers.
Here is some code that parses a document, adds an achor to the link and prints the new document:
extern crate html5ever;
use html5ever::{ParseOpts, parse_document};
use html5ever::tree_builder::TreeBuilderOpts;
use html5ever::rcdom::RcDom;
use html5ever::rcdom::NodeEnum::Element;
use html5ever::serialize::{SerializeOpts, serialize};
use html5ever::tendril::TendrilSink;
fn main() {
let opts = ParseOpts {
tree_builder: TreeBuilderOpts {
drop_doctype: true,
..Default::default()
},
..Default::default()
};
let data = "<!DOCTYPE html><html><body></body></html>".to_string();
let dom = parse_document(RcDom::default(), opts)
.from_utf8()
.read_from(&mut data.as_bytes())
.unwrap();
let document = dom.document.borrow();
let html = document.children[0].borrow();
let body = html.children[1].borrow(); // Implicit head element at children[0].
{
let mut a = body.children[0].borrow_mut();
if let Element(_, _, ref mut attributes) = a.node {
attributes[0].value.push_tendril(&From::from("#anchor"));
}
}
let mut bytes = vec![];
serialize(&mut bytes, &dom.document, SerializeOpts::default()).unwrap();
let result = String::from_utf8(bytes).unwrap();
println!("{}", result);
}
This prints the following:
<html><head></head><body></body></html>
As you can see, we can navigate through the child nodes via the children attribute.
And we can change an attribute present in the vector of attributes of an Element.

Resources