In Rust, how can I make this code less repetitive? - rust

The goal is to write a function that gets two paths, input_dir and output_dir, and convertes all markdown files from input_dir to html files in output_dir.
I finally managed to get it to run but it was rather frustrating. The parts that should be hard are super easy: the actual conversion from Markdown to HTML is effectively only one line. The seemingly easy parts are what took me the longest. Using a vector of paths and put all files into it is something I replaced with the glob crate. Not because I couldn't get it to work but it was a mess of if let and unwrap. A simple function that iterates over the list of elements and figures out which of them are actually files and not directories? Either I need four indentation levels if if let or I freak out over matches.
What am I doing wrong?
But lets start with some things I tried to get a list of items in a directory filtered to only contain actual files:
use std::fs;
use std::vec::Vec;
fn list_files (path: &str) -> Result<Vec<&str>, &str> {
if let Ok(dir_list) = fs::read_dir(path) {
Ok(dir_list.filter_map(|e| {
match e {
Ok(entry) => match entry.file_type() {
Ok(_) => entry.file_name().to_str(),
_ => None
},
_ => None
}
}).collect())
} else {
Err("nope")
}
}
fn main() {
let files = list_files("testdir");
println!("{:?}", files.unwrap_or(Vec::new()));
}
So, this code doesn't build, because the file name in Line 10 doesn't live long enough. I guess I could somehow create an owned String but that would introduce another nesting level because OsStr.to_string() returns a Result.
Now I looked through the code of the glob crate and they just use a mutable vector:
fn list_files (path: &str) -> Result<Vec<&str>, &str> {
let mut list = Vec::new();
if let Ok(dir_list) = fs::read_dir(path) {
for entry in dir_list {
if let Ok(entry) = entry {
if let Ok(file_type) = entry.file_type() {
if file_type.is_file() {
if let Some(name) = entry.file_name().to_str() {
list.push(name)
}
}
}
}
}
Ok(list)
} else {
Err("nope")
}
}
This not only adds crazy nesting, it also fails with the same problem. If I change from Vec<&str> to Vec<String>, it works:
fn list_files (path: &str) -> Result<Vec<String>, &str> {
let mut list = Vec::new();
if let Ok(dir_list) = fs::read_dir(path) {
for entry in dir_list {
if let Ok(entry) = entry {
if let Ok(file_type) = entry.file_type() {
if file_type.is_file() {
if let Ok(name) = entry.file_name().into_string() {
list.push(name)
}
}
}
}
}
Ok(list)
} else {
Err("nope")
}
}
Looks like I should apply that to my first try, right?
fn list_files (path: &str) -> Result<Vec<String>, &str> {
if let Ok(dir_list) = fs::read_dir(path) {
Ok(dir_list.filter_map(|e| {
match e {
Ok(entry) => match entry.file_type() {
Ok(_) => Some(entry.file_name().into_string().ok()),
_ => None
},
_ => None
}
}).collect())
} else {
Err("nope")
}
}
At least a bit shorter… but it fails to compile because a collection of type std::vec::Vec<std::string::String> cannot be built from an iterator over elements of type std::option::Option<std::string::String>.
It is hard to stay patient here. Why does .filter_map return Options instead of just using them to filter? Now I have to change line 15 from }).collect()) to }).map(|e| e.unwrap()).collect()) which iterates once more over the result set.
That can't be right!

You can massively rely on ? operator:
use std::fs;
use std::io::{Error, ErrorKind};
fn list_files(path: &str) -> Result<Vec<String>, Error> {
let mut list = Vec::new();
for entry in fs::read_dir(path)? {
let entry = entry?;
if entry.file_type()?.is_file() {
list.push(entry.file_name().into_string().map_err(|_| {
Error::new(ErrorKind::InvalidData, "Cannot convert file name")
})?)
}
}
Ok(list)
}
Do not forget that you can split your code into functions or implement your own traits to simplify the final code:
use std::fs;
use std::io::{Error, ErrorKind};
trait CustomGetFileName {
fn get_file_name(self) -> Result<String, Error>;
}
impl CustomGetFileName for std::fs::DirEntry {
fn get_file_name(self) -> Result<String, Error> {
Ok(self.file_name().into_string().map_err(|_|
Error::new(ErrorKind::InvalidData, "Cannot convert file name")
)?)
}
}
fn list_files(path: &str) -> Result<Vec<String>, Error> {
let mut list = Vec::new();
for entry in fs::read_dir(path)? {
let entry = entry?;
if entry.file_type()?.is_file() {
list.push(entry.get_file_name()?)
}
}
Ok(list)
}

An alternative answer with iterators, playground
use std::fs;
use std::error::Error;
use std::path::PathBuf;
fn list_files(path: &str) -> Result<Vec<PathBuf>, Box<Error>> {
let x = fs::read_dir(path)?
.filter_map(|e| e.ok())
.filter(|e| e.metadata().is_ok())
.filter(|e| e.metadata().unwrap().is_file())
.map(|e| e.path())
.collect();
Ok(x)
}
fn main() {
let path = ".";
for res in list_files(path).unwrap() {
println!("{:#?}", res);
}
}

Related

Rust : Cleanest way to transfert result and catch an error in when calling function?

in order to learn Rust, I try to create small snippets to apply what we learn in the Rust book and implement good practices.
Have a small function to list content of a repository :
use std::{io, fs, path::PathBuf, path::Path};
pub fn get_directory_content(path: &str) -> Result<Vec<PathBuf>, io::Error> {
let _path: bool = Path::new(path).is_dir();
match _path {
true => {
let mut result = vec![];
for file in fs::read_dir(path).unwrap() {
result.push(file.unwrap().path());
}
Ok(result)
},
false => Err(io::Error::new(io::ErrorKind::Other, " is not a directory")),
}
}
my goal is to be able to catch the error if the folder does not exist without triggering a panic.
in main.rs :
mod utils;
fn main() {
let directory = "./qsdsqd";
let test = utils::get_directory_content(directory).unwrap();
println!("{:?}", a);
}
if directory exist : ok, unwrap is happy. But does anyone know a "trick" for get the content of the error in var test ? Also, can we put the name of a variable in io::ErrorKind::Other to get more precision (here : &path) ?
Next try
fn main() {
let directory = "./qsdqsd";
let a = match utils::get_directory_content(directory){
Err(e) => println!("an error: {:?}", e),
Ok(c) => println!("{:?}", c),
};
println!("{:?}", a);
}
When error, ok, we have message, but here, if we put a correct folder : a "just" print result but content is empty, and we can't say Ok(c) => c for just return Ok content from function :/
Have a small function to list content of a repository :
That's already a pretty bad start, because it combines a TOCTOU with unnecessary extra work: if you're checking is_dir then trying to read the directory, it's possible for the entry to get deleted or swapped from under you.
This is a shame, since read_dir already does exactly what you want:
pub fn get_directory_content(path: &str) -> Result<Vec<PathBuf>, io::Error> {
let mut result = vec![];
for file in fs::read_dir(path)? {
result.push(file.unwrap().path());
}
Ok(result)
}
And you can apply this to the individual entries as well:
pub fn get_directory_content(path: &str) -> Result<Vec<PathBuf>, io::Error> {
let mut result = vec![];
for file in fs::read_dir(path)? {
result.push(file?.path());
}
Ok(result)
}
When error, ok, we have message, but here, if we put a correct folder : a "just" print result but content is empty, and we can't say Ok(c) => c for just return Ok content from function :/
Sure you can, however you still have to do something for the Err case: as most things in Rust, match is an expression, so all the branches need to return values of the same type... or not return at all:
let a = match get_directory_content(directory) {
Err(e) => {
println!("an error: {:?}", e);
return;
}
Ok(c) => c,
};
return has type !, which is Rust's "bottom" type: it's compatible with everything, because return does not "terminate", and thus there's npo reason for it to be incompatible with anything.
Alternatively, you could update main to return a Result as well, though that also requires updating it to return a value:
fn main() -> Result<(), io::Error> {
let directory = "./.config";
let a = get_directory_content(directory)?;
println!("{:?}", a);
Ok(())
}
You need to return c from your match statement.
Further, you need to do something in the Err case other than just print. What should a be in the error case?
I assume that you simply want to end the program, so I inserted a return.
mod utils {
use std::{fs, io, path::Path, path::PathBuf};
pub fn get_directory_content(path: &str) -> Result<Vec<PathBuf>, io::Error> {
let _path: bool = Path::new(path).is_dir();
match _path {
true => {
let mut result = vec![];
for file in fs::read_dir(path).unwrap() {
result.push(file.unwrap().path());
}
Ok(result)
}
false => Err(io::Error::new(io::ErrorKind::Other, " is not a directory")),
}
}
}
fn main() {
let directory = "./qsdqsd";
let a = match utils::get_directory_content(directory) {
Err(e) => {
println!("an error: {:?}", e);
return;
}
Ok(c) => {
println!("{:?}", c);
c
}
};
println!("{:?}", a);
}
["./qsdqsd/a.txt"]
["./qsdqsd/a.txt"]
DISCLAIMER: My answer is very much superficial. #Masklinn goes into much more detail about the "cleanest way" and other issues with the given code.
Because this is the accepted answer (at the time of writing), here is how a "cleanest way" version of the code could look like:
use std::{fs, io, path::PathBuf};
pub fn get_directory_content(path: &str) -> Result<Vec<PathBuf>, io::Error> {
let mut result = vec![];
for file in fs::read_dir(path)? {
result.push(file?.path());
}
Ok(result)
}
fn main() {
let directory = "./qsdqsd2";
let a = match get_directory_content(directory) {
Err(e) => {
println!("an error: {:?}", e);
return;
}
Ok(c) => c,
};
println!("{:?}", a);
}
["./qsdqsd/a.txt"]
Alternatively, you could have main() return a Result, which makes this even cleaner:
use std::{fs, io, path::PathBuf};
pub fn get_directory_content(path: &str) -> Result<Vec<PathBuf>, io::Error> {
let mut result = vec![];
for file in fs::read_dir(path)? {
result.push(file?.path());
}
Ok(result)
}
fn main() -> Result<(), io::Error> {
let directory = "./qsdqsd";
let a = get_directory_content(directory)?;
println!("{:?}", a);
Ok(())
}

How to use actix field stream by two consumers?

I have an actix web service and would like to parse the contents of a multipart field while streaming with async-gcode and in addition store the contents e.g. in a database.
However, I have no clue how to feed in the stream to the Parser and at the same time collect the bytes into a Vec<u8> or a String.
The first problem I face is that field is a stream of actix::web::Bytes and not of u8.
#[post("/upload")]
pub async fn upload_job(
mut payload: Multipart,
) -> Result<HttpResponse, Error> {
let mut contents : Vec<u8> = Vec::new();
while let Ok(Some(mut field)) = payload.try_next().await {
let content_disp = field.content_disposition().unwrap();
match content_disp.get_name().unwrap() {
"file" => {
while let Some(chunk) = field.next().await {
contents.append(&mut chunk.unwrap().to_vec());
// already parse the contents
// and additionally store contents somewhere
}
}
_ => (),
}
}
Ok(HttpResponse::Ok().finish())
}
Any hint or suggestion is very much appreciated.
One of the options is to wrap field in a struct and implement Stream trait for it.
use actix_web::{HttpRequest, HttpResponse, Error};
use futures_util::stream::Stream;
use std::pin::Pin;
use actix_multipart::{Multipart, Field};
use futures::stream::{self, StreamExt};
use futures_util::TryStreamExt;
use std::task::{Context, Poll};
use async_gcode::{Parser, Error as PError};
use bytes::BytesMut;
use std::cell::RefCell;
pub struct Wrapper {
field: Field,
buffer: RefCell<BytesMut>,
index: usize,
}
impl Wrapper {
pub fn new(field: Field, buffer: RefCell<BytesMut>) -> Self {
buffer.borrow_mut().truncate(0);
Wrapper {
field,
buffer,
index: 0
}
}
}
impl Stream for Wrapper {
type Item = Result<u8, PError>;
fn poll_next(
mut self: Pin<&mut Self>,
cx: &mut Context<'_>,
) -> Poll<Option<Result<u8, PError>>> {
if self.index == self.buffer.borrow().len() {
match Pin::new(&mut self.field).poll_next(cx) {
Poll::Ready(Some(Ok(chunk))) => self.buffer.get_mut().extend_from_slice(&chunk),
Poll::Pending => return Poll::Pending,
Poll::Ready(None) => return Poll::Ready(None),
Poll::Ready(Some(Err(_))) => return Poll::Ready(Some(Err(PError::BadNumberFormat/* ??? */)))
};
} else {
let b = self.buffer.borrow()[self.index];
self.index += 1;
return Poll::Ready(Some(Ok(b)));
}
Poll::Ready(None)
}
}
#[post("/upload")]
pub async fn upload_job(
mut payload: Multipart,
) -> Result<HttpResponse, Error> {
while let Ok(Some(field)) = payload.try_next().await {
let content_disp = field.content_disposition().unwrap();
match content_disp.get_name().unwrap() {
"file" => {
let mut contents: RefCell<BytesMut> = RefCell::new(BytesMut::new());
let mut w = Wrapper::new(field, contents.clone());
let mut p = Parser::new(w);
while let Some(res) = p.next().await {
// Do something with results
};
// Do something with the buffer
let a = contents.get_mut()[0];
}
_ => (),
}
}
Ok(HttpResponse::Ok().finish())
}
Copying the Bytes from the Field won't be necessary when
Bytes::try_unsplit will be implemented. (https://github.com/tokio-rs/bytes/issues/287)
The answer from dmitryvm (thanks for your effort) showed me that there are actually two problems. At first, flatten the Bytes into u8's and, secondly, to "split" the stream into a buffer for later storage and the async-gcode parser.
This shows how I solved it:
#[post("/upload")]
pub async fn upload_job(
mut payload: Multipart,
) -> Result<HttpResponse, Error> {
let mut contents : Vec<u8> = Vec::new();
while let Ok(Some(mut field)) = payload.try_next().await {
let content_disp = field.content_disposition().unwrap();
match content_disp.get_name().unwrap() {
"file" => {
let field_stream = field
.map_err(|_| async_gcode::Error::BadNumberFormat) // Translate error
.map_ok(|y| { // Translate Bytes into stream with Vec<u8>
contents.extend_from_slice(&y); // Copy and store for later usage
stream::iter(y).map(Result::<_, async_gcode::Error>::Ok)
})
.try_flatten(); // Flatten the streams of u8's
let mut parser = Parser::new(field_stream);
while let Some(gcode) = parser.next().await {
// Process result from parser
}
}
_ => (),
}
}
Ok(HttpResponse::Ok().finish())
}

More compact, easier to read Rust error handling

I'm a Java developer and in the process of learning Rust. Here is a tiny example that reads the file names of the current directory into a string list/vector and then outputs it.
Java with nio:
import java.io.*;
import java.nio.file.*;
import java.util.*;
import java.util.stream.*;
public class ListFileNames {
public static void main(String[] args) {
final Path dir = Paths.get(".");
final List<String> fileNames = new ArrayList<>();
try {
final Stream<Path> dirEntries = Files.list(dir);
dirEntries.forEach(path -> {
if (Files.isRegularFile(path)) {
fileNames.add(path.getFileName().toString());
}
});
}
catch (IOException ex) {
System.err.println("Failed to read " + dir + ": " + ex.getMessage());
}
print(fileNames);
}
private static void print(List<String> names) {
for (String name : names) {
System.out.println(name);
}
}
}
Here is what I came up with in Rust:
use std::fs;
use std::path::Path;
fn main() {
let dir = Path::new(".");
let mut entries: Vec<String> = Vec::new();
let dir_name = dir.to_str().unwrap();
let dir_content = fs::read_dir(dir);
match dir_content {
Ok(dir_content) => {
for entry in dir_content {
if let Ok(dir_entry) = entry {
if let Ok(file_type) = dir_entry.file_type() {
if file_type.is_file() {
if let Some(string) = dir_entry.file_name().to_str() {
entries.push(String::from(string));
}
}
}
}
}
}
Err(error) => println!("failed to read {}: {}", dir_name, error)
}
print(entries);
}
fn print(names: Vec<String>) {
for name in &names {
println!("{}", name);
}
}
Are there means to reduce the excessive indentations in the Rust code making it more easier to read similar to the Java code?
It is possible to use ? to short-circuit execution, as demonstrated by #Bazaim, although this has slightly different semantics as it stops on the first error, rather than ignoring it.
To keep true to your semantics, you would move to a Stream-based Iterator-based approach as can be seen here:
use std::error::Error;
use std::fs;
use std::path::Path;
fn main() -> Result<(), Box<dyn Error>> {
let dir = Path::new(".");
let dir_content = fs::read_dir(dir)?;
dir_content.into_iter()
.filter_map(|entry| entry.ok())
.filter(|entry| if let Ok(t) = entry.file_type() { t.is_file() } else { false })
.filter_map(|entry| entry.file_name().into_string().ok())
.for_each(|file_name| println!("{}", file_name));
Ok(())
}
I am not quite happy with that filter step, but I do like the fact that the functional-style API cleanly separates each step.
I'm also a beginner at Rust.
The ? operator is very usefull.
Here is the smallest version I get :
use std::error::Error;
use std::fs;
use std::path::Path;
fn main() -> Result<(), Box<dyn Error>> {
let dir = Path::new(".");
let mut entries: Vec<String> = Vec::new();
let dir_content = fs::read_dir(dir)?;
for entry in dir_content {
let entry = entry?;
if entry.file_type()?.is_file() {
if let Some(string) = entry.file_name().to_str() {
entries.push(String::from(string));
}
}
}
print(entries);
Ok(())
}
fn print(names: Vec<String>) {
for name in &names {
println!("{}", name);
}
}
Slightly modified version of #Bazaim's answer.
So, you can write the fallible code in a separate function and pass it reference to the vector.
If it fails, you can print the error and still print the names from the vector you passed in.
use std::error::Error;
use std::fs;
use std::path::Path;
fn main() {
let mut names = vec![];
if let Err(e) = collect(&mut names) {
println!("Error: {}", e);
}
for name in names {
println!("{}", name);
}
}
fn collect(names: &mut Vec<String>) -> Result<(), Box<dyn Error>> {
let dir = Path::new(".");
let dir_content = fs::read_dir(dir)?;
for entry in dir_content {
let entry = entry?;
if entry.file_type()?.is_file() {
if let Some(string) = entry.file_name().to_str() {
names.push(String::from(string));
}
}
}
Ok(())
}

Is it possible to use Iterator::filter_map to add conditional filtering?

This code walks the /tmp folder to show files that end in .txt:
const FOLDER_NAME: &str = "/tmp";
const PATTERN: &str = ".txt";
use std::error::Error;
use walkdir::WalkDir; // 2.2.9
fn main() -> Result<(), Box<dyn Error>> {
println!("Walking folder {}", FOLDER_NAME);
for entry in WalkDir::new(FOLDER_NAME).into_iter().filter_map(|e| e.ok()) {
let x = entry.file_name().to_str();
match x {
Some(x) if x.contains(PATTERN) => println!("This file matches: {:?}", entry),
_ => (),
}
}
Ok(())
}
Although this works, is it possible to leverage filter_map to do the suffix filtering that's currently happening in match?
You need to return the entry wrapped in a Some when the condition is true:
use std::error::Error;
use walkdir::WalkDir; // 2.2.9
const FOLDER_NAME: &str = "/tmp";
const PATTERN: &str = ".txt";
fn main() -> Result<(), Box<dyn Error>> {
println!("Walking folder {}", FOLDER_NAME);
let valid_entries = WalkDir::new(FOLDER_NAME)
.into_iter()
.flat_map(|e| e)
.flat_map(|e| {
let name = e.file_name().to_str()?;
if name.contains(PATTERN) {
Some(e)
} else {
None
}
});
for entry in valid_entries {
println!("This file matches: {:?}", entry);
}
Ok(())
}
You'll note that I've secretly switched to Iterator::flat_map. Iterator::filter_map would also work, but I find flat_map more ergonomic, especially for your "ignore the errors" case.
It's debatable whether this is useful compared to a regular Iterator::filter call:
let valid_entries = WalkDir::new(FOLDER_NAME)
.into_iter()
.flat_map(|e| e)
.filter(|e| {
e.file_name()
.to_str()
.map_or(false, |n| n.contains(PATTERN))
});
See also:
Why does `Option` support `IntoIterator`?
How can I filter an iterator when the predicate returns a Result<bool, _>?

Using the same iterator multiple times in Rust

Editor's note: This code example is from a version of Rust prior to 1.0 when many iterators implemented Copy. Updated versions of this code produce a different errors, but the answers still contain valuable information.
I'm trying to write a function to split a string into clumps of letters and numbers; for example, "test123test" would turn into [ "test", "123", "test" ]. Here's my attempt so far:
pub fn split(input: &str) -> Vec<String> {
let mut bits: Vec<String> = vec![];
let mut iter = input.chars().peekable();
loop {
match iter.peek() {
None => return bits,
Some(c) => if c.is_digit() {
bits.push(iter.take_while(|c| c.is_digit()).collect());
} else {
bits.push(iter.take_while(|c| !c.is_digit()).collect());
}
}
}
return bits;
}
However, this doesn't work, looping forever. It seems that it is using a clone of iter each time I call take_while, starting from the same position over and over again. I would like it to use the same iter each time, advancing the same iterator over all the each_times. Is this possible?
As you identified, each take_while call is duplicating iter, since take_while takes self and the Peekable chars iterator is Copy. (Only true before Rust 1.0 — editor)
You want to be modifying the iterator each time, that is, for take_while to be operating on an &mut to your iterator. Which is exactly what the .by_ref adaptor is for:
pub fn split(input: &str) -> Vec<String> {
let mut bits: Vec<String> = vec![];
let mut iter = input.chars().peekable();
loop {
match iter.peek().map(|c| *c) {
None => return bits,
Some(c) => if c.is_digit(10) {
bits.push(iter.by_ref().take_while(|c| c.is_digit(10)).collect());
} else {
bits.push(iter.by_ref().take_while(|c| !c.is_digit(10)).collect());
},
}
}
}
fn main() {
println!("{:?}", split("123abc456def"))
}
Prints
["123", "bc", "56", "ef"]
However, I imagine this is not correct.
I would actually recommend writing this as a normal for loop, using the char_indices iterator:
pub fn split(input: &str) -> Vec<String> {
let mut bits: Vec<String> = vec![];
if input.is_empty() {
return bits;
}
let mut is_digit = input.chars().next().unwrap().is_digit(10);
let mut start = 0;
for (i, c) in input.char_indices() {
let this_is_digit = c.is_digit(10);
if is_digit != this_is_digit {
bits.push(input[start..i].to_string());
is_digit = this_is_digit;
start = i;
}
}
bits.push(input[start..].to_string());
bits
}
This form also allows for doing this with much fewer allocations (that is, the Strings are not required), because each returned value is just a slice into the input, and we can use lifetimes to state this:
pub fn split<'a>(input: &'a str) -> Vec<&'a str> {
let mut bits = vec![];
if input.is_empty() {
return bits;
}
let mut is_digit = input.chars().next().unwrap().is_digit(10);
let mut start = 0;
for (i, c) in input.char_indices() {
let this_is_digit = c.is_digit(10);
if is_digit != this_is_digit {
bits.push(&input[start..i]);
is_digit = this_is_digit;
start = i;
}
}
bits.push(&input[start..]);
bits
}
All that changed was the type signature, removing the Vec<String> type hint and the .to_string calls.
One could even write an iterator like this, to avoid having to allocate the Vec. Something like fn split<'a>(input: &'a str) -> Splits<'a> { /* construct a Splits */ } where Splits is a struct that implements Iterator<&'a str>.
take_while takes self by value: it consumes the iterator. Before Rust 1.0 it also was unfortunately able to be implicitly copied, leading to the surprising behaviour that you are observing.
You cannot use take_while for what you are wanting for these reasons. You will need to manually unroll your take_while invocations.
Here is one of many possible ways of dealing with this:
pub fn split(input: &str) -> Vec<String> {
let mut bits: Vec<String> = vec![];
let mut iter = input.chars().peekable();
loop {
let seeking_digits = match iter.peek() {
None => return bits,
Some(c) => c.is_digit(10),
};
if seeking_digits {
bits.push(take_while(&mut iter, |c| c.is_digit(10)));
} else {
bits.push(take_while(&mut iter, |c| !c.is_digit(10)));
}
}
}
fn take_while<I, F>(iter: &mut std::iter::Peekable<I>, predicate: F) -> String
where
I: Iterator<Item = char>,
F: Fn(&char) -> bool,
{
let mut out = String::new();
loop {
match iter.peek() {
Some(c) if predicate(c) => out.push(*c),
_ => return out,
}
let _ = iter.next();
}
}
fn main() {
println!("{:?}", split("test123test"));
}
This yields a solution with two levels of looping; another valid approach would be to model it as a state machine one level deep only. Ask if you aren’t sure what I mean and I’ll demonstrate.

Resources