Formating Utilities
To create a better user experience, data will be formatted before it is displayed to the command line on stdout
.
Overview of Code Formatting
Create a utils.rs
file which will hold the utilities and add the following code blocks:
File: src/utils.rs
use crate::MyTodosModel;
use async_std::sync::Mutex;
use std::collections::HashMap;
pub(crate) const TITLE: &str = "FRUITS AVAILABLE";
pub(crate) const NUMBER: &str = "No.";
pub(crate) const ADD_COMMAND: &str = "ADD";
pub(crate) const DONE_COMMAND: &str = "DONE";
pub(crate) const UNDO_COMMAND: &str = "UNDO";
pub(crate) const EDIT_COMMAND: &str = "EDIT";
pub(crate) const EXIT_COMMAND: &str = "EXIT";
const DONE: &str = "DONE TODOS";
const NOT_DONE: &str = "NOT DONE";
const QUANTITY: &str = "QUANTITY";
pub(crate) type MemDB = Mutex<HashMap<String, MyTodosModel>>;
pub fn clear_terminal() {
print!("\x1B[2J\x1B[1;1H");
}
pub fn synching() {
clear_terminal();
println!("SYNCING TO DATABASE...");
}
pub fn synching_to_server() {
println!("SYNCING TO SERVER...");
}
pub fn loading() {
clear_terminal();
println!("LOADING FROM DATABASE...");
}
pub fn convert_case(word: &str) -> String {
let word = word.to_lowercase();
let mut chars = word
.chars()
.map(|character| character.to_string())
.collect::<Vec<String>>();
chars[0] = chars[0].to_uppercase().to_string();
chars.into_iter().collect::<String>()
}
pub fn split_words(user_input: String) -> Vec<String> {
user_input
.split(" ")
.map(|word| word.to_owned())
.collect::<Vec<String>>()
}
The TITLE
and NUMBER
constants are used to format the headings for the fruits
table which displays the list of fruits on the command-line interface. The constants DONE
, NOT_DONE
and QUANTITY
are used as the headings of the TODO list.
Interaction Commands
To interact with the client, a user will input a command, similar to pressing a button in a GUI or any other GUI event that performs an operation based on user input. The current list of commands are:
The ADD_COMMAND
constant holds the ADD
command. This command allows a user to queue
a task in the TODO list. The format is ADD QUANTITY_IN_KG FRUIT_NAME
.
The DONE_COMMAND
constant holds the DONE
command. This command allows a user to mark a task as completed
in the TODO list. The format is DONE FRUIT_NAME
.
The UNDO_COMMAND
constant holds the UNDO
command. This command allows a user to move a completed task back into the queue
in the TODO list. The format is UNDO FRUIT_NAME
.
The EDIT_COMMAND
constant holds the EDIT
command. This command allows a user to modify
a task in the TODO list by changing it's quantity
. The format is EDIT QUANTITY_IN_KG FRUIT_NAME
.
The EXIT_COMMAND
constant holds the EXIT
command. This command allows a user to exit
the client gracefully and sync the local database cache with the remote PostgreSQL server. The format is EXIT
.
Word formating
A number of functions are presented in the code block above:
clear_terminal()
is used to clear the terminal using the command line specific flags \x1B[2J\x1B[1;1H
synching()
is used to show that the TODO list is being synced to the local SQLite database cache.
synching_to_server()
is used to show that the TODO list is being synced to the remote PostgreSQL database using the TCP API built in the previous chapter.
loading()
is used to show that information about the user is being fetched from the remote PostgreSQL database.
convert_case()
is used to format the fruit
name to Title Case
, for example, a user can enter a fruit named Apple
as apple
, Apple
, aPPLe
, ApplE
, etc... This makes the user experience much smoother.
split_words()
is used to split the text buffer from the user input into individual parts that correspond with the format specified in the Commands
like COMMAND QUANTITY_IN_KG FRUIT_NAME
.
In-memory Database
Instead of doing database I/O by querying SQLite database every time we need to check the existence of data, we will use an in-memory database described by MemDB
which contains a Mutex<HashMap<String, MyTodosModel>>
scoped to the internals of the crate. This is a HashMap
indexed using a String
which is the name of the todo in the Model
and the value of the indexing key set to the MyTodosModel
. The HashMap is protected by a Mutex
for thread-safety.
Formatting the TODO List
To format the list of TODOs in local cache and display them to the command-line interface, add the following to the
File: src/utils.rs
pub async fn format_todos(todo_models: &MemDB) {
println!("\n\n\n");
if todo_models.lock().await.is_empty() {
println!("Oh My! There are no TODOs");
} else {
let mut done = Vec::<MyTodosModel>::default();
let mut not_done = Vec::<MyTodosModel>::default();
todo_models.lock().await.iter().for_each(|todo| {
if todo.1.status == 0 {
not_done.push(todo.1.to_owned());
} else {
done.push(todo.1.to_owned());
}
});
if not_done.is_empty() {
println!("Wohooo! All TODOs are Completed.")
} else {
println!("{QUANTITY:9}| {NOT_DONE:10}");
println!("----------------");
not_done.iter().for_each(|todo| {
println!("{:>8} | {:10}", todo.quantity, todo.todo_name);
});
println!("----------------\n");
}
if done.is_empty() {
println!("----------------");
println!("Bummer :( You Have Not Completed Any TODOs!");
println!("----------------\n\n");
} else {
println!("{QUANTITY:9}| {DONE:10}");
println!("----------------");
done.iter().for_each(|todo| {
println!("{:>8} | {:10}", todo.quantity, todo.todo_name);
});
println!("----------------\n");
}
}
}
format_todos()
functions takes the in-memory database and loops through it, first checking if there are no TODOs and prints "Oh My! There are no TODOs"
. If TODOs are found, it iterates through them and sorts the completed
todos into the done
Vector declared by let mut done = Vec::<MyTodosModel>::default();
or the queued
into the not_done
declared by let mut not_done = Vec::<MyTodosModel>::default();
There are no completed TODOs but there are queued ones, it prints "Bummer :( You Have Not Completed Any TODOs!"
and if there are no queued TODOs but completed ones, it prints "Wohooo! All TODOs are Completed."
.
The MyTodosModel
is the Model
for the Entity
table todo_list
in the local SQLite database cache.
Import the utils
module in the src/main.rs
file
mod common;
mod db_ops;
mod todo_list_table;
+ mod utils;
pub use common::*;
pub use db_ops::*;
pub use todo_list_table::prelude::*;
+ pub use utils::*;
#[async_std::main]
async fn main() -> anyhow::Result<()> {
let db = database_config().await?;
create_todo_table(&db).await?;
Ok(())
}