I recently ran across a tutorial explaining how to create an md file reader in Rust, using only three dependencies!! I was so excited about this project that I decided to share it with all of you.
So after creating a new rust file with
cargo new app-name
You will need to add clap to your project's cargo.toml file under dependencies. Along with this go ahead and add pulldown-cmark and maud as shown below:
[dependencies]
clap ="2.33.0"
pulldown-cmark = "0.4.1"
maud = "0.20.0"
At this point, I know there are some questions, and I will answer them to the best of my ability. So to cover the first dependency, clap is a library that allows one to parse command-line arguments and subcommands when writing console/terminal applications. This crate allows you to create custom arguments that you can add to the file run command to output various items. It can also format the display information on help queries as shown below:
To get this run
cargo run -- --help
which will output the file's help information. If you look towards the top you can see my name, the crate version, and a simple description; that could literally be ANYTHING I WANT. Along with these customizations, you can add different arguments to run with the file as shown in the help flags above and in my code below:
nameOfApp =>
(version:crate_version!())
(author:"Damon Rocha")
(about:"Renders markdown as you like")
(@arg input: + required "Sets the input file")
(@arg wrap: -w "Wrap in html")
(@arg event: -e "Print event")
(@arg css: --css + takes_value "Link to css")
So what does all of this mean? Well, the input arg allows the user to set the file to be read. The event arg will print out the events as the items are parsed to HTML, and the CSS arg generates a link to a CSS file you input with the file on execution.
Next is pulldown-cmark. From this library, I imported html::push_html, Event, and Parser. These three functions allowed me to take the data read from a file, Parse through it, collect the Events, and then convert the file data into HTML. To accomplish this, first read in the md file the user inputs on runtime
cargo run --filename.md
After doing this the app will save the file data to a variable as a string, and
then create a Parser to hold the HTML and a Vec to hold the file data along with the event information.
let infile =
std::fs::read_to_string(clap.value_of("input").unwrap())
.expect("Could not read file");
let mut res = String::new();
let ps = Parser::new(&infile);
let ps : Vec<Event> = ps.into_iter().collect();
...
push_html(&mut res, ps.into_iter());
With all of the HTML from the file contained in the res String, multiple choices are available. First just running the file will display the html present in the file. Running the file along with event will display data like what is below
Input = Some("test_data/hello.md")
Start(Header(1))
Text(Borrowed("Hello"))
End(Header(1))
Start(Paragraph)
Text(Borrowed("Here is a page of markdown "))
Start(Emphasis)
Text(Borrowed("stuff"))
End(Emphasis)
End(Paragraph)
<h1>Hello</h1>
<p>Here is a page of markdown <em>stuff</em></p>
Lastly, there is the wrap and CSS choice. This is where maud comes in, but don't go running cargo run just yet. First, you need a different version of Rust. So to get maud to work first download Nightly rust, which is a Rust distribution channel where some files are still in the beta stages. If you run into any problems with maud just let those at Rust know. This has not happened to me yet but you never know. Download nightly rust with the command
rustup install nightly
then set this file to run nightly by default with
rustup override set nightly
once this is done you can then use maud. maud is a crate that allows you to wrap your read in HTML in different HTML tags. In my project, I just used a basic DOCTYPE, head, and body tag to hold the information from the file. To do this I created a function called wrap_html that is shown below:
fn wrap_html(s:&str, css:Option<&str>)-> String{
let res = html!{
(maud::DOCTYPE)
html{
head{
meta charset="utf-8";
@if let Some(s) = css {
link rel="stylesheet" type="text/css" href=(s) {}
}
}
body{
(maud::PreEscaped(s))
}
}
};
res.into_string()
}
While using maud to access your variables you must wrap them in () and any control flow needs to be paired with an @ before it as shown above. I set CSS to an Option type so that if a user does not send any CSS to the HTML wrapper the CSS link will not be present. Then once in the body the HTML sent in is put into the wrapper with maud::PreEscaped(s).
With this done all that is left is adding control flow to make those choices mean something. To do this I added two if blocks in the main function to check if certain arguments are present in the clap block
if clap.is_present("event"){
for p in &ps{
println!("{:?}", p);
}
}//prints out events along with html
push_html(&mut res, ps.into_iter());
if clap.is_present("wrap"){
res = wrap_html(&res, clap.value_of("css"));
}//prints out wrapped html
...
Then to actually display something we must print out all of the data that has been accumulated.
println!("{}", res);
You can now get the HTML from an md file in your terminal. So if you enjoyed this project then make changes and improve it. And please send me a link to your project. I know I am going to tinker with this until it is a project I can really be proud of. Repo: https://github.com/dmarcr1997/RustfileParser
Top comments (5)
Thank you, more content
Your welcome. I will post some more rust stuff next week. Anything in particular?
Maybe a simple Machine Learning project, not Google deepmind but a linear regression problem with python bindings? Thanks though for posting Rust content!
I will try to do this, but I do not have a lot of experience using machine learning in python. I have actually wanted to do something similar to this with JS or React. But it will be a challenge to use python so let me see what I can do by next Sunday. Yeah your welcome, I find it cements what I am learning if I write about it afterward and Rust is awesome.
Hey js is fine by me 😄✌️ just a high level language 👍