- CLIZola2020-09-11T00:00:00+00:00https://www.fullstackstanley.com/tags/cli/atom.xmlMy first (and worst) Rust program2020-09-11T00:00:00+00:002020-09-11T00:00:00+00:00https://www.fullstackstanley.com/articles/my-first-and-worst-rust-program/<p>I've been looking for an excuse to try out Rust for a while and decided to write a simple CLI to help split grocery costs between my partner and myself.</p>
<span id="continue-reading"></span>
<p>![/static/images/]</p>
<p>I've been looking for an excuse to try out Rust for a while and decided to write a simple CLI to help split grocery costs between my partner and myself.</p>
<h2 id="the-current-solution">The Current Solution</h2>
<p>We currently split grocery costs in 3 ways, she pays for her items, I pay for mine, and the food we share we split the cost fifty/fifty. </p>
<p>I receive an email receipt from our grocery store. I then enter the costs into <a href="https://soulver.app/">Soulver</a> to do some Maths and figure out how much we each owe.</p>
<h2 id="the-problem">The Problem</h2>
<p>The current solution is slow and prone to human error (I get distracted). If I miss an item, add an item twice, or type in the wrong price, then one of us could end up owing too much or too little.</p>
<p>Although we share some food, one of us often eats more than the other of <em>some</em> products. In this situation the fifty fifty split is less fair.</p>
<p>Trying to split these items more further would make the calculation more tedious and error prone.</p>
<h2 id="my-rust-solution">My Rust Solution</h2>
<p>Since I receive an email with the receipt I can take the text and run it through a script quite easily.</p>
<p>The below Rust script, although terrible, seems to do the job just fine.</p>
<p>It takes a file and runs through each line. Splits the cost by looking for the £ sign on each line. Then asks who the item is for. Now with the added benefit of being able to do a 25/75 split for items that are used more by one person than the other. It also keeps a running total for each person which is a nice bonus.</p>
<p>Here's an example receipt. This is pasted directly from an email and very little is changed.</p>
<pre style="background-color:#2b303b;color:#c0c5ce;"><code><span>You ordered 1 X HECK 10 Meat-Free Magic 300g £1.75
</span><span>We sent 1 X HECK 10 Vegan Italia Chipolatas 300g £1.75
</span><span>
</span><span>Your order
</span><span>
</span><span>Fridge Quantity Price
</span><span>Curly Kale 150g 1 £0.50
</span><span>Greek Style Yogurt 500g 1 £0.75
</span><span>Soft Cheese 180g 1 £1.95
</span><span>Fresh Whole Milk 1l 2 £2.80
</span><span>Ready Rolled Filo Pastry 200g 1 £1.30
</span><span>Extra Mature Cheddar Cheese 400g 1 £2.00
</span><span>Fresh Quantity Price
</span><span>Sweetclems 600g 1 £1.29
</span><span>Freezer Quantity Price
</span><span>Frozen Strawberries 350g 1 £1.75
</span><span>Vegetarian 2 Cheese & Spring Onion Crispbakes 280g 1 £1.50
</span><span>Straight Cut Chips 1.5kg 1 £2.00
</span><span>Groceries, Health & Beauty and Household Items Quantity Price
</span><span>Green Beans 240g 1 £0.90
</span><span>Thick Bleach Citrus Burst 750ml 2 £0.78
</span><span>Corn Flakes 450g 1 £1.89
</span><span>Wholewheat Penne 500g 1 £0.53
</span><span>Maple Syrup 250g 1 £4.50
</span></code></pre>
<p>And here's the Rust code</p>
<pre data-lang="rust" style="background-color:#2b303b;color:#c0c5ce;" class="language-rust "><code class="language-rust" data-lang="rust"><span style="color:#b48ead;">use </span><span>std::fs::File;
</span><span style="color:#b48ead;">use </span><span>std::io::{</span><span style="color:#bf616a;">self</span><span>, BufRead};
</span><span style="color:#b48ead;">use </span><span>std::path::Path;
</span><span style="color:#b48ead;">use </span><span>read_input::prelude::*;
</span><span style="color:#b48ead;">use </span><span>colored::*;
</span><span style="color:#b48ead;">use </span><span>currency::Currency;
</span><span>
</span><span style="color:#b48ead;">fn </span><span style="color:#8fa1b3;">main</span><span>() {
</span><span> </span><span style="color:#b48ead;">let mut</span><span> one_pays = Currency::from_str("</span><span style="color:#a3be8c;">0.0</span><span>").</span><span style="color:#96b5b4;">unwrap</span><span>();
</span><span> </span><span style="color:#b48ead;">let mut</span><span> two_pays = Currency::from_str("</span><span style="color:#a3be8c;">0.0</span><span>").</span><span style="color:#96b5b4;">unwrap</span><span>();
</span><span> </span><span style="color:#b48ead;">let</span><span> file = std::env::args().</span><span style="color:#96b5b4;">nth</span><span>(</span><span style="color:#d08770;">1</span><span>).</span><span style="color:#96b5b4;">expect</span><span>("</span><span style="color:#a3be8c;">no pattern given</span><span>");
</span><span> </span><span style="color:#b48ead;">if let </span><span>Ok(lines) = </span><span style="color:#96b5b4;">read_lines</span><span>(file) {
</span><span> </span><span style="color:#65737e;">// Consumes the iterator, returns an (Optional) String
</span><span> </span><span style="color:#b48ead;">for</span><span> line in lines {
</span><span> </span><span style="color:#b48ead;">if let </span><span>Ok(ip) = line {
</span><span> </span><span style="color:#b48ead;">if</span><span> ip.</span><span style="color:#96b5b4;">starts_with</span><span>("</span><span style="color:#a3be8c;">You ordered</span><span>") {
</span><span> </span><span style="color:#65737e;">// do nothing
</span><span> } </span><span style="color:#b48ead;">else if</span><span> ip.</span><span style="color:#96b5b4;">starts_with</span><span>("</span><span style="color:#a3be8c;">We sent</span><span>") {
</span><span> </span><span style="color:#b48ead;">let</span><span> item_text = ip.</span><span style="color:#96b5b4;">replace</span><span>("</span><span style="color:#a3be8c;">We sent</span><span>", "");
</span><span> </span><span style="color:#b48ead;">let</span><span> split_row: Vec<&</span><span style="color:#b48ead;">str</span><span>> = item_text.</span><span style="color:#96b5b4;">trim</span><span>().</span><span style="color:#96b5b4;">split</span><span>("</span><span style="color:#a3be8c;">£</span><span>").</span><span style="color:#96b5b4;">collect</span><span>();
</span><span> </span><span style="color:#b48ead;">let</span><span> length = split_row.</span><span style="color:#96b5b4;">len</span><span>();
</span><span> </span><span style="color:#b48ead;">if</span><span> length > </span><span style="color:#d08770;">1 </span><span>{
</span><span> </span><span style="color:#b48ead;">let</span><span> name = &split_row[</span><span style="color:#d08770;">0</span><span>].</span><span style="color:#96b5b4;">trim</span><span>();
</span><span> </span><span style="color:#b48ead;">let</span><span> cost = Currency::from_str(split_row[</span><span style="color:#d08770;">1</span><span>].</span><span style="color:#96b5b4;">clone</span><span>()).</span><span style="color:#96b5b4;">unwrap</span><span>();
</span><span> </span><span style="color:#b48ead;">let </span><span>(one_item_cost, two_item_cost) = </span><span style="color:#96b5b4;">ask</span><span>(name, cost.</span><span style="color:#96b5b4;">clone</span><span>());
</span><span> one_pays = one_pays + one_item_cost;
</span><span> two_pays = two_pays + two_item_cost;
</span><span> </span><span style="color:#96b5b4;">running_total</span><span>(&one_pays, &two_pays);
</span><span> }
</span><span> } </span><span style="color:#b48ead;">else if</span><span> ip.</span><span style="color:#96b5b4;">ends_with</span><span>("</span><span style="color:#a3be8c;">Price</span><span>") {
</span><span> </span><span style="color:#65737e;">// do nothing
</span><span> } </span><span style="color:#b48ead;">else </span><span>{
</span><span> </span><span style="color:#b48ead;">let</span><span> split_row: Vec<&</span><span style="color:#b48ead;">str</span><span>> = ip.</span><span style="color:#96b5b4;">split</span><span>("</span><span style="color:#a3be8c;">£</span><span>").</span><span style="color:#96b5b4;">collect</span><span>();
</span><span> </span><span style="color:#b48ead;">let</span><span> length = split_row.</span><span style="color:#96b5b4;">len</span><span>();
</span><span> </span><span style="color:#b48ead;">if</span><span> length > </span><span style="color:#d08770;">1 </span><span>{
</span><span> </span><span style="color:#b48ead;">let</span><span> name = &split_row[</span><span style="color:#d08770;">0</span><span>].</span><span style="color:#96b5b4;">trim</span><span>();
</span><span> </span><span style="color:#b48ead;">let</span><span> cost = Currency::from_str(split_row[</span><span style="color:#d08770;">1</span><span>].</span><span style="color:#96b5b4;">clone</span><span>()).</span><span style="color:#96b5b4;">unwrap</span><span>();
</span><span> </span><span style="color:#b48ead;">let</span><span> one_item_cost, two_item_cost) = </span><span style="color:#96b5b4;">ask</span><span>(name, cost);
</span><span> one_pays = one_pays + one_item_cost;
</span><span> two_pays = two_pays + two_item_cost;
</span><span> </span><span style="color:#96b5b4;">running_total</span><span>(&one_pays, &two_pays);
</span><span> }
</span><span> }
</span><span> }
</span><span> }
</span><span> }
</span><span>}
</span><span style="color:#b48ead;">fn </span><span style="color:#8fa1b3;">read_lines</span><span><P>(</span><span style="color:#bf616a;">filename</span><span>: P) -> io::Result<io::Lines<io::BufReader<File>>>
</span><span style="color:#b48ead;">where</span><span> P: AsRef<Path>, {
</span><span> </span><span style="color:#b48ead;">let</span><span> file = File::open(filename)?;
</span><span> Ok(io::BufReader::new(file).</span><span style="color:#96b5b4;">lines</span><span>())
</span><span>}
</span><span>
</span><span style="color:#b48ead;">fn </span><span style="color:#8fa1b3;">ask</span><span>(</span><span style="color:#bf616a;">item</span><span>: &</span><span style="color:#b48ead;">str</span><span>, </span><span style="color:#bf616a;">cost</span><span>: Currency) -> (Currency, Currency) {
</span><span> </span><span style="color:#b48ead;">let mut</span><span> one_pays: Currency = Currency::from_str("</span><span style="color:#a3be8c;">0.0</span><span>").</span><span style="color:#96b5b4;">unwrap</span><span>();
</span><span> </span><span style="color:#b48ead;">let mut</span><span> two_pays: Currency = Currency::from_str("</span><span style="color:#a3be8c;">0.0</span><span>").</span><span style="color:#96b5b4;">unwrap</span><span>();
</span><span> println!("</span><span style="color:#a3be8c;">Who pays for </span><span style="color:#d08770;">{}</span><span style="color:#a3be8c;">? (£</span><span style="color:#d08770;">{}</span><span style="color:#a3be8c;">)</span><span>", item.</span><span style="color:#96b5b4;">bold</span><span>(), cost);
</span><span> </span><span style="color:#b48ead;">let</span><span> answer: String = </span><span style="color:#96b5b4;">input</span><span>().</span><span style="color:#96b5b4;">msg</span><span>("</span><span style="color:#a3be8c;">m/k/b/m3/k3/ignore: </span><span>").</span><span style="color:#96b5b4;">get</span><span>();
</span><span> println!("</span><span style="color:#a3be8c;">You said </span><span style="color:#d08770;">{}</span><span>", answer.</span><span style="color:#96b5b4;">green</span><span>());
</span><span> </span><span style="color:#b48ead;">match</span><span> answer.</span><span style="color:#96b5b4;">as_str</span><span>() {
</span><span> "</span><span style="color:#a3be8c;">m</span><span>" => {
</span><span> one_pays = one_pays + &cost;
</span><span> println!("</span><span style="color:#a3be8c;">one pays an additional £</span><span style="color:#d08770;">{}</span><span>", &cost);
</span><span> },
</span><span> "</span><span style="color:#a3be8c;">k</span><span>" => {
</span><span> two_pays = two_pays + &cost;
</span><span> println!("</span><span style="color:#a3be8c;">two pays an additional £</span><span style="color:#d08770;">{}</span><span>", &cost);
</span><span> },
</span><span> "</span><span style="color:#a3be8c;">b</span><span>" => {
</span><span> </span><span style="color:#b48ead;">let</span><span> split_cost = cost / </span><span style="color:#d08770;">2</span><span>;
</span><span> println!("</span><span style="color:#a3be8c;">Splitting cost: one pays £</span><span style="color:#d08770;">{}</span><span style="color:#a3be8c;">, two pays £</span><span style="color:#d08770;">{}</span><span>", split_cost, split_cost);
</span><span> one_pays = one_pays + &split_cost;
</span><span> two_pays = two_pays + &split_cost;
</span><span> },
</span><span> "</span><span style="color:#a3be8c;">m3</span><span>" => {
</span><span> </span><span style="color:#b48ead;">let</span><span> split_cost = cost / </span><span style="color:#d08770;">4</span><span>;
</span><span> println!("</span><span style="color:#a3be8c;">Splitting cost: one pays £</span><span style="color:#d08770;">{}</span><span style="color:#a3be8c;">, two pays £</span><span style="color:#d08770;">{}</span><span>", &split_cost * </span><span style="color:#d08770;">3</span><span>, &split_cost);
</span><span> one_pays = one_pays + (&split_cost * </span><span style="color:#d08770;">3</span><span>);
</span><span> two_pays = two_pays + &split_cost;
</span><span> },
</span><span> "</span><span style="color:#a3be8c;">k3</span><span>" => {
</span><span> </span><span style="color:#b48ead;">let</span><span> split_cost = cost / </span><span style="color:#d08770;">4</span><span>;
</span><span> println!("</span><span style="color:#a3be8c;">Splitting cost: one pays £</span><span style="color:#d08770;">{}</span><span style="color:#a3be8c;">, two pays £</span><span style="color:#d08770;">{}</span><span>", &split_cost, &split_cost * </span><span style="color:#d08770;">3</span><span>);
</span><span> one_pays = one_pays + &split_cost;
</span><span> two_pays = two_pays + (&split_cost * </span><span style="color:#d08770;">3</span><span>);
</span><span> },
</span><span> _ => {
</span><span> println!("</span><span style="color:#d08770;">{}</span><span>", "</span><span style="color:#a3be8c;">Invalid answer</span><span>".</span><span style="color:#96b5b4;">red</span><span>().</span><span style="color:#96b5b4;">bold</span><span>());
</span><span> println!("</span><span style="color:#d08770;">{}</span><span>", "</span><span style="color:#a3be8c;">Press 'i' to ignore</span><span>".</span><span style="color:#96b5b4;">green</span><span>());
</span><span> println!("");
</span><span> </span><span style="color:#b48ead;">let </span><span>(m, k) = </span><span style="color:#96b5b4;">ask</span><span>(item, cost);
</span><span> one_pays = one_pays + m;
</span><span> two_pays = two_pays + k;
</span><span> }
</span><span> };
</span><span> (one_pays, two_pays)
</span><span>}
</span><span style="color:#b48ead;">fn </span><span style="color:#8fa1b3;">running_total</span><span>(</span><span style="color:#bf616a;">one_pays</span><span>: &Currency, </span><span style="color:#bf616a;">two_pays</span><span>: &Currency) -> () {
</span><span> println!("");
</span><span> println!("</span><span style="color:#d08770;">{}</span><span>", "</span><span style="color:#a3be8c;">Running Total</span><span>".</span><span style="color:#96b5b4;">bold</span><span>());
</span><span> println!("</span><span style="color:#a3be8c;">-------------</span><span>");
</span><span> println!("</span><span style="color:#a3be8c;">one: £</span><span style="color:#d08770;">{}</span><span>", &one_pays);
</span><span> println!("</span><span style="color:#a3be8c;">two: £</span><span style="color:#d08770;">{}</span><span>", &two_pays);
</span><span> println!("</span><span style="color:#a3be8c;">-------------</span><span>");
</span><span> println!("");
</span><span>}
</span></code></pre>
<p>Here's the Cargo.toml</p>
<pre data-lang="toml" style="background-color:#2b303b;color:#c0c5ce;" class="language-toml "><code class="language-toml" data-lang="toml"><span>[package]
</span><span style="color:#bf616a;">name </span><span>= "</span><span style="color:#a3be8c;">cost-splitter</span><span>"
</span><span style="color:#bf616a;">version </span><span>= "</span><span style="color:#a3be8c;">0.1.0</span><span>"
</span><span style="color:#bf616a;">authors </span><span>= ["</span><span style="color:#a3be8c;">Mitch</span><span>"]
</span><span style="color:#bf616a;">edition </span><span>= "</span><span style="color:#a3be8c;">2018</span><span>"
</span><span>
</span><span>[dependencies]
</span><span style="color:#bf616a;">read_input </span><span>= "</span><span style="color:#a3be8c;">0.8</span><span>"
</span><span style="color:#bf616a;">colored </span><span>= "</span><span style="color:#a3be8c;">2</span><span>"
</span><span style="color:#bf616a;">currency </span><span>= "</span><span style="color:#a3be8c;">~0.4.0</span><span>"
</span><span>
</span></code></pre>
<p>There you have it!</p>
<p><img src="https://dev-to-uploads.s3.amazonaws.com/i/tiogpcy9pttr7anx9lhm.png" alt="Alt Text" /></p>
<h1 id="the-dependencies">The Dependencies</h1>
<p>I used three crates for this project.</p>
<h2 id="read-input"><a href="https://docs.rs/read_input">Read Input</a></h2>
<p>From what I can tell there's no easy way to read from stdin with Rust out of the box. This crate makes it really easy.</p>
<pre data-lang="rust" style="background-color:#2b303b;color:#c0c5ce;" class="language-rust "><code class="language-rust" data-lang="rust"><span style="color:#b48ead;">let</span><span> answer: String = </span><span style="color:#96b5b4;">input</span><span>().</span><span style="color:#96b5b4;">msg</span><span>("</span><span style="color:#a3be8c;">m/k/b/m3/k3/ignore: </span><span>").</span><span style="color:#96b5b4;">get</span><span>();
</span></code></pre>
<h2 id="colored"><a href="https://docs.rs/colored">Colored</a></h2>
<p>I added a bit of text formatting and colour with this crate.</p>
<pre data-lang="rust" style="background-color:#2b303b;color:#c0c5ce;" class="language-rust "><code class="language-rust" data-lang="rust"><span>println!("</span><span style="color:#d08770;">{} {} {}</span><span>", "</span><span style="color:#a3be8c;">Text</span><span>".</span><span style="color:#96b5b4;">green</span><span>(), "</span><span style="color:#a3be8c;">Text 2</span><span>".</span><span style="color:#96b5b4;">green</span><span>().</span><span style="color:#96b5b4;">bold</span><span>(), "</span><span style="color:#a3be8c;">Text 3</span><span>".</span><span style="color:#96b5b4;">green</span><span>());
</span></code></pre>
<h2 id="currency"><a href="https://docs.rs/currency">Currency</a></h2>
<p>I used this crate for handling the maths and formatting the output.</p>
<pre data-lang="rust" style="background-color:#2b303b;color:#c0c5ce;" class="language-rust "><code class="language-rust" data-lang="rust"><span style="color:#b48ead;">let</span><span> cost = Currency::from_str(split_row[</span><span style="color:#d08770;">1</span><span>].</span><span style="color:#96b5b4;">clone</span><span>()).</span><span style="color:#96b5b4;">unwrap</span><span>();
</span></code></pre>
<h1 id="overall-experience-with-rust-so-far">Overall experience with Rust so far</h1>
<p>My experience developing this was pretty enjoyable. I was able to get a workable executable quite quickly with the help of the Rust compiler.</p>
<p>I did, however, struggle with a few items.</p>
<ul>
<li>
<p>Using Color with the Currency crate. I gave up on this in the end.</p>
</li>
<li>
<p>Strings are quite confusing. Not so much ownership which is also a new concept to me, but the various types of strings and when to use them. I tried to create an array of ignored items/lines to output at the end of the execution but couldn't get it working. Hopefully, with experience I'll become more familiar with strings and how to handle this.</p>
</li>
</ul>
<h1 id="what-next">What next?</h1>
<p>Aside from working on another Rust application I'm considering comparing this development experience with Go or Crystal. Go has piqued my interest—although I have no experience with it. I've used Crystal quite a lot and I suspect writing this application would be trivial in it. </p>
<p>Let me know if you'd be interested in hearing about me port the application to either of these two languages!</p>
Unix commands: Find2015-01-30T00:00:00+00:002015-01-30T00:00:00+00:00https://www.fullstackstanley.com/articles/unix-commands-find/<p>After watching one of Gary Bernhardt’s Destroy All Software screencasts I thought I’d take some of his advice and learn about some basic Unix commands. I’m going to start with <code>find</code>.</p>
<p>This post will be a small collection of useful commands I’ve found.</p>
<span id="continue-reading"></span><h2 id="basic-directory-searching">Basic directory searching</h2>
<p><code>find .</code> - Searching the current directory recursively.</p>
<p><code>find ..</code> - Search from the parent directory.</p>
<p><code>find $HOME</code> or <code>find ~</code> - Search your user directory</p>
<p><code>find $HOME/projects/*.js</code> - Search for directories and files in your projects directory that end with <code>.js</code> - Not recursive.</p>
<p><code>find $HOME/projects -name *.js</code> Search for .js files in your projects directory - recursive.</p>
<h2 id="print-types">Print types</h2>
<p><code>find . -print0</code> - Print the full filename followed by a null character instead of a newline. Best used with files that have multiline filenames.</p>
<p><code>find . -printf</code> - for formatting output.</p>
<p>Example: Showing filename and access</p>
<p><code>find . -name *.js -printf "%p %M\n”</code></p>
<p>You can use numbers between %<char> (directives) to align text evenly. <code>%100p</code> will right align the filename for 100 characters. <code>%-100p</code> will left align for the same.</p>
<p>Note that this doesn’t work natively on Macs. Instead, you have two options:</p>
<ul>
<li>
<p>pipe to xargs like so: <code>find . -name *.js -print0 | xargs -0 stat -f "%p %k KB”</code> (As found from <a href="http://stackoverflow.com/questions/752818/why-does-macs-find-not-have-the-option-printf">Stackoverflow</a>.</p>
</li>
<li>
<p>Install Findutils. If you use Homebrew you can run <code>brew install findutils</code>. Note that to use find this way you need to run <code>gfind</code>.</p>
</li>
</ul>
<h3 id="fprint">fprint</h3>
<p>Puts the results into a file. This also requires findutils if you’re on a Mac.</p>
<p><code>find . -name *.js -fprint all_files.txt</code></p>
<h2 id="symbolic-links">Symbolic links</h2>
<p><code>find -P .</code> never follow symbolic links</p>
<p><code>find -L .</code> follow symbolic links</p>
<p><code>find -H .</code> don’t follow except when processing command line arguments</p>
<h2 id="filter-by-time">Filter by time</h2>
<p><code>find . -mmin n</code> modified n minutes ago</p>
<p><code>find . -mtime n</code> modified n*24 hours ago.</p>
<p><code>find . -newer ./composer.json</code> find files modified after composer.json</p>
<h2 id="other-filters">Other filters</h2>
<p><code>find . -size n[cwbkMG]</code> find files that use n units of space. where c = bytes, k = kilobytes, M = megabytes, G = gigabytes</p>
<p><code>find . -maxdepth 1</code> - Only go one directory deep.</p>
<p><code>find . -mindepth 1</code> - Ignore the first directory</p>
<h2 id="actions">Actions</h2>
<h3 id="delete">Delete</h3>
<p><code>find . -delete</code> - delete files in directory</p>
<p>Example - deleting all .js files.</p>
<p><code>find . -name *.js -delete</code></p>
<h3 id="exec">Exec</h3>
<p>Removing all .bak files:</p>
<p><code>find . -name \*.bak -exec rm {} \;</code></p>
<p>The curly braces will get replaced with each filename that's found.</p>
<p>As seen <a href="http://nixcraft.com/showthread.php/16297-find-exec-bash-example">here</a>.</p>
<h2 id="windows-alternative-to-find">Windows alternative to find</h2>
<p>For powershell use <code>Find-ChildItem</code>. <a href="http://superuser.com/questions/401495/equivalent-of-unix-find-command-on-windows">Read more here</a></p>
<h2 id="useful-resources">Useful resources</h2>
<ul>
<li>
<p><a href="http://unixhelp.ed.ac.uk/CGI/man-cgi?find">find documentation</a></p>
</li>
<li>
<p><a href="http://linux.die.net/man/3/printf">printf documentation</a></p>
</li>
</ul>