Eine Terminalapplkation um Fortschritt (oder anderes) zu tracken.

Das ist mein erstes Rust projekt. Ich habe versucht mit den Grundprinzipen vertraut zu werden.

Updates hier

main.rs

// The GPLv3 License (GPLv3)

// Copyright (c) 2023 Lovis Rentsch

// This program is free software: you can redistribute it and/or modify
// it under the terms of the GNU General Public License as published by
// the Free Software Foundation, either version 3 of the License, or
// (at your option) any later version.

// This program is distributed in the hope that it will be useful,
// but WITHOUT ANY WARRANTY; without even the implied warranty of
// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
// GNU General Public License for more details.

// You should have received a copy of the GNU General Public License
// along with this program.  If not, see <http://www.gnu.org/licenses/>.

//import the other file
pub mod read;

use std::io;
use colored::*;
use termsize;
use std::env;

fn main() {
    //prompt
    println!("enter show(1), add(2) or help(3)");
    
        //read the user-input
        let mut input = String::new();
        io::stdin().read_line(&mut input).expect("Failed to read line");

        //comupte the input and call the functions
        match input.trim() {
            //always give the option to also use the number
            "help" | "3" => {
                help();
            }

            "show" | "1" => {
                //check if the parameter is passed
                if chack_param() {
                    //if yes, proceed
                    tagesauswahl();

                //else error
                } else {
                    println!("{}", "The file path must be provided as a parameter!".red());
                }
            }
            "add" | "2" => {
                //same as befor
                if chack_param() {
                    read::add();
                } else {
                    println!("{}", "The file path must be provided as a parameter!".red());
                }
            }
            _ => {
                //not here, the help function should always be accessible and it doesn't need the
                //argument
                help();
            }
        }
    // }
}

//first step of the show option
pub fn tagesauswahl() {
    //prompt
    println!("");
    println!("How many days should be displayed?");   

    //read awnser
    let mut input = String::new();
    io::stdin().read_line(&mut input).expect("Failed to read line");

    //format the awnser (remove the return at the end)
    let trimmed_input = input.trim();

    //check if all the given options are numeric
    let mut count = 0;
    for c in trimmed_input.chars() {
        //of one of them isn't give an error and let the user try again
        if !c.is_numeric() {
            println!("{}", "Input can only be numeric. Try again!".red());
            tagesauswahl();

            //if no error is thrown (no char that isn't a nuber)
        } else {

            let mut breite = 1;
            termsize::get().map(|size| {
                breite = size.cols;
            });

            // (this couner is needed to make sure that all the chars (letters/numbers) are numbers
            // (and not just the first one))
            count = count + 1;
            
            //if ALL the chars are numbers -> checking the counter
            if  count == trimmed_input.len() {

                //format "num" to be an i32 number (for further use)
                let num: i32 = trimmed_input.parse().unwrap();

                //check if the requested number is longer than the possible amount
                if read::main(num).len() < usize::try_from(num).unwrap() {

                    //if that is the case error and let the user try again
                    println!("{}", "Daycount cannot be bigger than total recorded days! Try a smaller value.".red());
                    tagesauswahl();

                    //else go on with the selection of the topic while passing in the number of
                    //days (for further use)
                } else if ((num + 8) * 2) > (breite as i32) {
                    println!("{}", "Value cannot be bigger than the terminal width! Try a smaller value.".red());
                    tagesauswahl();
                } else {
                    //A row to seperate the user input from the graph
                    println!("");

                    //call the graph with the number of values which should be parsed
                    graph(num);
                }
            }
        }
    }
}

//function that draws the output to the terminal
fn graph(tage: i32) {
    
    //variable thaz counts the times that the loop ran (the row we are in)
    let mut lauf = 1;
    
    //giving the output a height of 10 
    //starting at the bottom (rev) so that the highest number has the highest pillar (not upside down)

    open_unten_rahmen(tage);
    for zeile in (1..=10).rev() {
        print!("{}", " |  ".blue());
        
        //numbering the x-Axis
        //making sure that all numbers are 2 chars wide (else issues with the 10)
        let width: usize = 2;
        print!("  {zeile:>width$}  ");
       

        //adding 1 to the global row count
        lauf = lauf + 1;

        //looping through all the numbers in the vector
        //the vector comes from the "read.rs" file (and it's main function)
        for stelle in (0..read::main(tage).len()).rev() {

            //if the value of the current number is bigger than the number of the row
            //then print a red rectangle
            if read::main(tage)[stelle] >= zeile {

                //burrow the value to the color function
                farbe(&lauf);
            } else {

                //if the value is 0 paint the whole collumn in black
                if read::main(tage)[stelle] == 0 {
                    print!("{}", "  ".on_black());

                    //this is so that the top (not used part) is formatted correctly
                } else {
                    print!("  ");
                }
            }
        }

        print!("{}", " |   ".blue());
        //line breaks at the end of each line
        print!("\n");
    }
    open_unten_rahmen(tage); 
}

fn farbe(lauf: &i32) {

    //check the values and assign diffetent values to achive a vertical gradient
    match lauf {
        1 => print!("{}", "  ".on_truecolor(39, 163, 10)),
        2 => print!("{}", "  ".on_truecolor(62, 154, 9)),
        3 => print!("{}", "  ".on_truecolor(85, 145, 8)),
        4 => print!("{}", "  ".on_truecolor(107, 135, 7)),
        5 => print!("{}", "  ".on_truecolor(130, 126, 6)),
        6 => print!("{}", "  ".on_truecolor(153, 117, 4)),
        7 => print!("{}", "  ".on_truecolor(176, 108, 3)),
        8 => print!("{}", "  ".on_truecolor(198, 98, 2)),
        9 => print!("{}", "  ".on_truecolor(221, 89, 1)),
        _ => print!("{}", "  ".on_truecolor(244, 80, 0)),
    };
}

//telling the user how to use the programm
fn help() {
    println!("");
    println!("{}" ,"This script can be used to track all sorts of progress.".bold());
    println!("");
    println!("It is required to pass the path to the file that should be read as a commandline argument.");
    println!("  {}" ,"./tracker /home/example/log.txt".on_black().yellow());
    println!("Like that it is possible to track multiple different things by distributing them into different files.");
    println!("");
    println!("Form the initial prompt, you can choose to show the log of the progress you tracked in a graph (choose the 1), or to add an entry (choose the 2).");
    println!("");
    println!("Colors: (from good to bad)");
    for n in 1..=10 {
        print!("{n:2} ");
    }
    print!("\n");
    for n in 1..=10 {
        farbe(&n);
        print!("|")
    }
    println!("");
    println!("");
    println!("If a {} is given as an argument the day is marked as «not counted» and displayed as {}.", "0".bold() , "  ".on_black());
}


fn open_unten_rahmen(tage: i32) {
    //add the left margin
    print!("{}"," ----".blue());
    for _n in 0..=tage {
        
        //draw as many as days
        print!("{}", "--".blue());
    }


    //add right margin
    print!("{}", "----".blue());

    //end line
    print!("\n");
}

//function to check if the file path is passed as an argument
fn chack_param() -> bool {
    let args: Vec<String> = env::args().collect();

    if args.len() < 2 {
        false
    } else {
        true
    }
}

read.rs


use std::fs::File;
use std::fs::OpenOptions;
use std::io::BufReader;
use colored::*;
use rev_lines::RevLines;
use std::env;
// use std::io;
use std::io::{stdin, Write};
// use dirs::*;

use crate::tagesauswahl;

pub fn main(tage: i32) -> Vec<i32> {
    
    //read the commandline arument
    let args: Vec<String> = env::args().collect();

    //take the 2nd argument and pass it into the query variable
    let query = &args[1];

    // Open the file and create a buffered BufReader
    let file = File::open(query).unwrap();

    //maka a usize number from the variable that was passed through
    let cap = usize::try_from(tage).unwrap();

    //use said variable to limit the size of the vector that stores the final values
    let mut numbers = Vec::with_capacity(cap);

    //make use of the rev_lines crate, so that the values shown are always the ones at the bottom 
    //(the latest)
    let rev_lines = RevLines::new(BufReader::new(&file)).unwrap();

    //add the individual values to the vector
    for line in rev_lines.take(cap) {
        let number: i32 = line.parse().unwrap();
        numbers.push(number);
    }

    //return the vector
    numbers
}

//function to add values to the file
pub fn add() {
    //read the commandline arument
    let args: Vec<String> = env::args().collect();

    //take the 2nd argument and pass it into the query variable
    let query = &args[1];

    //promt
    println!("");
    println!("How was your day out of 10?");

    //read user input
    let mut user_input = String::new();
    stdin().read_line(&mut user_input).expect("Failed to read user input!");

    //counter for the numeric check
    let mut count = 0;

    //trim the return sign of the user input
    let trimmed = user_input.trim();

    //check if all characters are numeric
    for c in trimmed.chars() {

        //if not numeric send error and let rety
        if !c.is_numeric() {
            println!("{}", "Input not numeric! Try again".red());

            //give the option to retry
            add();
        } else {

            //update count
            count = count + 1;

            //if gone thrigh all characters
            if count == trimmed.len() {

                //write input to file
                    let mut file = OpenOptions::new().append(true).open(query).expect("Unable to open file"); 
                    file.write_all(user_input.as_bytes()).expect("Failed to write user input!");
                    
                    //show the updated graph
                    tagesauswahl();
            }
        }
    }
}