Translate

2021-04-20

Learning Rust. Part 14. Training project. Editor last blogpost.





 The "completed" editor.
This took 19 days from the day i downloaded Rust (excepting breakfast, dinner, garden work, walks and other everyday chores. One thing happened that I didn't expect at all: a mail came from Rust's org telling me they found my intense interest in the documentation so encouraging that they choose to push me up to "trusted" level. I won't deny it had some effect :0)

This is the last part of seven iterations. The first is in https://a-zproj.blogspot.com/2021/03/learning-rust-part-4-training-project.html
The editor now has the following commands implemented:

Commands:
[[=header, [=item, /*=start comment, */=end comment, a=add line, i=insert comment, l=list, d=delete line(s)

"Completed" means just that, the functions work but no more. There are (some) safeguards for entering wrong things from the keyboard and possibly a number of not existing/bad handling of errors, generally. One more thing one could wish is a way to go back to older versions of the edited file. There is none. But:

It's working, after a fashion. Functions have been added; delete-lines() and add_a_line(). Delete_lines accepts more than one line number to be deleted, they do not have to be sequential. add_a_line() accepts only one. Also check_num() is added as to stop entering text when numbers are expected.

I've learned many things and that was the goal.

Most of the code below has been commented earlier. Have you been following this subject up to now, I'm certain you'll handle the few new things.

The first two lines:

    let args: Vec<String> = env::args().collect();
    println!("{:?}", args);

gets the arguments, which makes it possible to enter the .ini-file name I'd like to process. The first argument (args[0]) is the program's own name, the next is the first argument. 
From that follows that the next line will contain the file name:

    let fname_ini=args[1].to_string();

and during development the program will be started by "cargo run ed.ini".

#![allow(warnings)]
use std::fs;
use std::fs::{File,OpenOptions};
use std::io::{self, Read, Write, ErrorKind};
use std::process::exit;
use std::str;
use std::char;
use termios::{Termios, TCSANOW, ECHO, ICANON, tcsetattr};
use std::string::String;
use std::io::BufWriter;
use std::env;


fn main() {
    let args: Vec<String> = env::args().collect();
    println!("{:?}", args);
    let fname_ini=args[1].to_string();
    let msg = "\nCommands:\n[[=header, [=item, /*=start comment, */=end comment, a=add line, i=insert comment, l=list, d=delete line(s) ".to_string();
    println!("{}", msg);
    let input=rdl("\nEnter command:\n".to_string());
    

// trim_end_matches(' ') returns a string slice (&str) By default there is a
/
/ newline character  at the end of the string produced by rdl() so you need to use trim!

    let input=input.trim_end_matches('\n'); // returns a string slice (&str)

    match input {
        "[[" => header(input.to_string(),fname_ini.to_string()),
        "["  => item(input.to_string(),fname_ini.to_string()),
        "/*" => comment_start(fname_ini.to_string()),
        "*/" => comment_end(input.to_string()),
        "A" | "a" => add_a_line(fname_ini.to_string()),
        "D" | "d" => delete_lines(fname_ini.to_string()),
        "H" | "h" => help(input.to_string()),        // theoretically. No help yet
        "I" | "i" => insert_comment(fname_ini.to_string()),
        "L" | "l" => list(fname_ini.to_string()),
        &_ => ret(input)    // must be str because 
        }
    main();
}


fn ret(input:&str){
    println!("Input is not acceptable {}",&input);
    main();
}

fn header(section_header:String,fname_ini:String) {

    println!("Header was: {}",section_header);
    let f = open_file (fname_ini.to_string());
    let prompt:String = "Section header chosen. Please enter the header text: >> ".to_string();
    let inp:String =rdl(prompt).trim().to_string();
     // .<=to_string()
    let mut header = String::new();                                      // as in the first example. 
    header.push_str("\n[");                   // let's add the first piece, "["
    header.push_str(inp.as_str());               // let's add the second part, 'inp'
    header.push_str("]\n");                    // let's add the third part. "]"

    println!("Complete header is: {} ",header);
    let _res = write_file(header, f);
    main();
}

fn item(section_item:String,fname_ini:String){

    println!("Item was: {}",section_item);
    let f = open_file (fname_ini.to_string());
             // getting the section item & value value
    let prompt:String = "Section item chosen. Please enter the item name: ".to_string();
    let inp_name:String =rdl(prompt).trim().to_string();
    let prompt:String = "Section item chosen. Please enter the item value: ".to_string();
    let inp_value:String =rdl(prompt).trim().to_string();
    // putting together the complete item line

    let mut inp_line = String::new();
    inp_line.push_str(&inp_name.as_str());

    let delim:String=String::from(": ");
    inp_line.push_str(&delim);
    inp_line.push_str(&inp_value);
    let n:String = String::from("\n");
            // let's add a NewLine (needing 2 statements!)
    inp_line = inp_line + &n;
    println!("Complete item line is: {} ",inp_line);
   
    let res = write_file(inp_line.to_string(), f);            // adding the item line to the file
    main();
}

fn comment_end(com_end:String){

   println!("comment_end was: {}",com_end);            // if you start program and immediatly end it
}

fn help(cont_hlp:String){

    println!("Help was: {}",cont_hlp);                            // there ought to be some help text but nooo...
}

// just showing the user what the current file looks like by listing it
fn list(fname_ini:String){

    let  cont_list = std::fs::read_to_string(&fname_ini).expect("Something went wrong reading the file");
    let s_slice: &str = &*cont_list;
    let v: Vec<&str> = s_slice.split('\n').collect();
    let mut l_num:i32 = -1;
    print!("{}","\n");

    for val in v.iter() {
        l_num += 1;
        if l_num > 0 {
            println!("{} {}",l_num,val);
        }
    }
    return;
}

fn rdl(prompt:String) -> std::string::String {

    print!("{}", prompt);
             // print whatever 'prompt is
    io::stdout().flush().unwrap();       // to force printout NOW, otherwise lines will come in wrong order!
    let mut st = String::new();          // a string to stuff my input into
    match  std::io::stdin().read_line(&mut st) {
        Ok(_) => (st),
        Err(e) => (e.to_string()),
    }
}

fn rdl_num(prompt:String,lines:usize) -> usize  {

    print!("{}: ",prompt);
    io::stdout().flush().unwrap();
    let mut lnum_try = String::new();
    std::io::stdin().read_line(&mut lnum_try);
    lnum_try = lnum_try.trim().to_string(); 
    let n = lnum_try.parse::<usize>().unwrap();
    return n;
}


fn open_file (fname:String) -> std::fs::File{


/*
NOTE: Files are automatically closed when they go out of scope.
"The most general way for your function to use files is to take an open file handle as parameter, as this allows it to also use file handles that are not part of the filesystem." (Though, not to forget, file handles can't be copied or de-referenced!) 
*/

    let f = OpenOptions::new().read(true).append(true).open(&fname);
    let mut f = match f {
        Ok(f) => return f,
        Err(error) => match error.kind() {
            ErrorKind::NotFound => match File::create(&fname){
            Ok(f) => return f,
            Err(e) => panic!("Failed creating file 'data' {:?}", e)
            },
                                    // one more is coming, a comma
            other_err => {
            panic!("Failed opening file 'data'  {:?}", other_err)
            }
        }
    };                                                                                            //
END of "let f = match f {", a semicolon!
}


fn copy_file(source:String,target:String) -> std::io::Result<()> {

    fs::copy(source, target)?;                     // Copy foo.txt to bar.txt
    Ok(())
}


fn write_file(contents:String,mut f:std::fs::File)  -> std::io::Result<String> {

    f.write_all(contents.as_bytes())?;
    f.sync_data().expect("write_file: Failed syncing data");
    Ok("OK".to_string())
}


fn delete_file(fname:String)  -> std::io::Result<()> {

    fs::remove_file(fname)?;
    Ok(())
 }

fn create_empty(fname:String) -> std::io::Result<()> {

    File::create(fname)?;
    Ok(())
}

 

// Getting char after char, checking for "*/" which will end it
 fn comment_start(fname_ini:String) {

    let fname_slice: &str = &fname_ini;
    let mut s = String::new();
    let a:&str = "1";
    let b:&str = "1";

    while a == "1" && b == "1" {                    // infinite loop
        s = t_get(s,fname_slice.to_string());        // getting the next char
        if s.len()<1 {
            comment_start(fname_slice.to_string());
        }

        let n1 = s.len()-1;
        let ch1 = s.chars().nth(n1).unwrap();
            // checking the last char
        s=t_get(s,fname_slice.to_string());
        if s.len()<1 {
            comment_start(fname_slice.to_string());

        }

        let n2 = s.len()-1;
        let ch2 = s.chars().nth(n2).unwrap();
        if (ch1 == '*') && (ch2 == '/') {
            let s=add_comment_tags(s);
                      // tags are "/*" and "*/"
            let f = open_file(fname_slice.to_string());
            write_file(s,f);
            print!("\n");
            list(fname_slice.to_string());
            io::stdout().flush().unwrap();
            return;
        }        

        if ch2 == '*' {                                   // if the check before misses...
            s=t_get(s,fname_slice.to_string());
            if s.len()<1 {
                comment_start(fname_slice.to_string());
            }
            let n3 = s.len()-1;
            let ch3 = s.chars().nth(n3).unwrap()
            if ch3 == '/' {
                                 // this will catch it ("*/").

            let s=add_comment_tags(s)
            let f = open_file(fname_slice.to_string());
            write_file(s,f);
            list(fname_slice.to_string());
            return
            }
        }
    }
}


// shortening comment_start() by not repeating this twice tags are "/*" and "*/"
fn add_comment_tags(mut s:String) -> std::string::String {

    let prefix:String = "\n/* ".to_string();
    let suffix:String = "\n".to_string();
    s = prefix + &s + &suffix;
    s
}

// This is the Termios crate (it's name changed for clarity), as it is downloaded
fn getachar() ->  [u8;1]{       // result is an ARRAY!
  let stdin = 0;            // couldn't get std::os::unix::io::FromRawFd to work on /dev/stdin or /dev/tty                                 // 

  let termios = Termios::from_fd(stdin).unwrap();
    let mut new_termios = termios.clone();
          // make a mutable copy of termios                                                // that we will modify
    new_termios.c_lflag &= !(ICANON | ECHO);   // no echo and canonical mode

    tcsetattr(stdin, TCSANOW, &mut new_termios).unwrap();
    let stdout = io::stdout();
    let mut reader = io::stdin();
    let mut buffer = [0;1];
                         // read exactly one byte [into this ARRAY!]
    stdout.lock().flush().unwrap();
    reader.read_exact(&mut buffer).unwrap();
    let c = buffer;
    tcsetattr(stdin, TCSANOW, & termios).unwrap();
  // reset the stdin to nothing

    return c;
}


fn t_get(mut s:String, fname_ini:String) -> String {

  // This is a wrapper for Termios, (backspace editing is possible, 
    // remember, the expanding line is shown to the user, char after char)
    // It gets one character to be added to the s:String

    let mut raw = getachar();   // get a char

    // print!("{}",raw[0]);  // if activated, shows dec # of u8
    if raw[0] > 127 {              // non-ASCII char is beeping
        raw = [7; 1];
    }

    if raw[0] == 127 {               // backspace!
        if s.len()<1 {
            comment_start(fname_ini);
        }
        s.pop();
                 // here the backspace is effected. Remove last char
        print!(" {}",s);       // you'll get the string back minus the last character
        return s;          // here

    };

    let  mybyte = str::from_utf8(&raw).unwrap();
    s.push_str(&mybyte);
    let n1 = s.len()-1;
    let ch1 = s.chars().nth(n1).unwrap();
    io::stdout().flush().unwrap();

    s                           // the String + the new char at the end, returned (if no backspace)
}


/* conceptually this function works like insert_comment() [previously named "comment_start()"]. The file is read in, placed in a vector which is the written back to the original file name. Here, this is done w/o any intermediate file. */

fn add_a_line(fname_ini:String) {

    let fname = fname_ini.as_str();
    list(fname.to_string());
    let  cont = std::fs::read_to_string(&fname_ini).expect("Something went wrong reading the file");
    cont.trim();

    let s_slice: &str = &*cont;
    let v_cont: Vec<&str> = s_slice.split('\n').collect();
  // to be able to index
    let v_cont_lines = v_cont.len()-1;
    if v_cont_lines == 0 {
        println!("The file is empty. Add at least a [section]!");
        main();
    }    

    let mut prompt="Enter line number after which you wish to add a line:
".to_string();
    let mut inp:String =rdl(prompt).trim().to_string();
    let mut inp_num = check_num(fname_ini.to_string(),inp);
    if inp_num =="Bad number".to_string(){
          // inp_num should be a number:String
        add_a_line(fname.to_string());
    }

    if inp_num =="".to_string(){                  // inp_num is (should be) a  a number:String
        println!("Input is empty");
        add_a_line(fname.to_string());

    }

    let inp_ix = inp_num.parse::<usize>().unwrap();     // convert it to an usize!
    let mut x:usize = 0;
    let mut string_cont = String::new();
    while x <= inp_ix {
                               // adding the lines to the output file up                                                                                                         // to and including the input linenumber

        string_cont.push_str(v_cont[x]);
        string_cont.push_str("\n");
        x += 1;
    }

    let my_prompt="Enter the line you wish to add. End with <Enter>: ".to_string();
    let new_line:String = rdl(my_prompt).trim().to_string();
    if new_line =="".to_string() {
        println!("Input string is empty");
        add_a_line(fname.to_string());
    }

    let nl = "\n";
    string_cont = string_cont + &new_line + &nl;
  // by adding a &String it becomes a &str
    while x > inp_ix && x < v_cont_lines && inp_ix > 1 {
        string_cont.push_str(v_cont[x]);
           // which means this will work! All lines after the 
                                                                                    // input line number are added
        string_cont.push_str("\n");
        x += 1;
    }

    let bu = ".bu";
    let target = fname.to_string() + &bu;
     // create target name (="<fname>.bu")
    copy_file(fname.to_string(),target);    // use it for copying
    delete_file(fname.to_string());         // delete the original
    println!("string_cont:{:?}",string_cont);
    let mut f=open_file(fname.to_string());
    let mut f = BufWriter::new(f);
    f.write_all(string_cont.as_bytes()).expect("Unable to write string_cont1");
    f.flush().unwrap(); 
                   // necessary, otherwise no output

    main();
}


fn delete_lines(fname_ini:String) {

  // deletes lines in a file displayed with list()
    // input line numbers are withheld when writing it back
    //  Since all lines corresponding to the input line numbers, are withheld
    // they do not have to be sequential.
    // a backup file is created and will not be removed afterwards

    let v = fname_ini.as_str();
    let  cont = std::fs::read_to_string(&fname_ini).expect("Something went wrong reading the file");
    cont.trim();
    let s_slice: &str = &*cont;
    let v_cont: Vec<&str> = s_slice.split('\n').collect();
// to be able to index
    let v_cont_lines = v_cont.len()-1;              // file is now read and stored in a vector
    list(fname_ini.to_string());

    let prompt="Delete line: Please enter line number(s); Commas in between!: ".to_string();
    let mut inp:String =rdl(prompt).trim().to_string();
   // line numbers are read

    let mut inp_num = check_num(fname_ini.to_string(),inp); // line numbers are rchecked and stored in a vector
    let inp_num_slice:&str = &*inp_num;
    let v_inp_num:Vec<&str> = inp_num_slice.split(',').collect();
    let v_inp_num_len = v_inp_num.len();            

    let mut result = String::new();
    let mut found:bool = false;
    let mut x:usize = 0;
    while x < v_cont_lines {
                        // for each line in the file...                 for i in (0..v_inp_num_len) {                    // the line number is checked against                                 
                                      // each  vector item
     if &*x.to_string() == v_inp_num[i]{         // dereference bcs left side i usize and right side is &str. This was not the first thing to come to my mind...
             found = true;
            }

        }

        if found == false {
            result.push_str(v_cont[x]);
            result.push_str("\n");
        }else{
            found = false;
        }
        x += 1;
    }

    let bu = ".bu";
    let target = fname_ini.to_string() + &bu;
        // create target name (="<fname>.bu")
    copy_file(fname_ini.to_string(),target).expect("Unable to copy file to backup");
    delete_file(fname_ini.to_string());
    let mut file = open_file(fname_ini.to_string());
    file.write_all(result.as_bytes()).expect("Unable to write string_cont1");
    file.sync_data().expect("write_file: Failed syncing data");
    return
}


fn check_num(fname_ini:String,mut st:String) -> std::string::String{

  // checking all characters are numeric:ish. Commas and whitespace are removed.
    // If all's good the string is returned, otherwise err message

    st.retain(|c| !c.is_whitespace());      //"retain all characters c such that f(c) returns false" =' '. |c|
                                                                        // defines whatever is tested, followed by a the test

    st.trim_matches(',');               // trim ev. last ','
    let s_slice: &str = &*st;
    let v_cont: Vec<&str> = s_slice.split(',').collect();
    let v_cont_len = v_cont.len();
    let mut x:usize = 0;
    let mut s_num = String::new();
    let mut p:String = "".to_string();
    while x < v_cont_len {
        if v_cont[x].to_string().chars().all(char::is_numeric) {
            x += 1;
            continue;
       }else{
        print!("This is not a number: {:?}",v_cont[x]);
        let prompt="   <Enter> to continue".to_string();
        let mut inp:String =rdl(prompt).trim().to_string();
        return ("Bad number".to_string());
      }
      x += 1;
    }
    st
}


/*The function makes it possible to enter a comment after a specific line number.
It contains the following actions:
Read the file to edit, store it in a vector (to be able to index it).
Make a backup copy of the file. Create/overwrite an empty temp file where the comment will be saved.
Creating a comment with function start_comment(), storing it in the tmp file.
Moving the vector's string elements/lines up to the specific line from the vector to a string
Moving the remaining vector elements to another string
Writing the fist string, the comment and the last string to the original file
Done
*/

fn insert_comment(fname_ini:String){
    let  cont = std::fs::read_to_string(&fname_ini).expect("Something went wrong reading the file");
    let s_slice: &str = &*cont;
    let v_cont: Vec<&str> = s_slice.split('\n').collect();
// to be able to index
    let lines = v_cont.len()-1;
    let fname = fname_ini.as_str();
                //String can't be copied, make str for copying
    list(fname.to_string());                      // showing the file (w. line numbers)
    let bu = ".bu";
    let tmp = ".tmp";
    let s_orig = String::new();
    let s_new = String::new();
    let n:String = "\n".to_string();    

    let mut lnum:usize = rdl_num("The line number for the start of your comment: ".to_string(),lines);

    if lnum > 0 {
        lnum -= 1;
    }
    let target = fname.to_string() + &bu;
             // create target name (="<fname>.bu")
    let target_slice = target.as_str();               // for using name several times w/o moving

    let tmpfile = fname.to_string() + tmp;          // create tmp file name
    let tmpfile_slice = tmpfile.as_str();             // for using name several times w/o moving
    let mut f = create_empty(tmpfile_slice.to_string());    // Creating empty tmp file
    copy_file(fname.to_string(),target_slice.to_string());   // copy original to; backup
    println!("Backup copy is named {}",target_slice.to_string());
    println!("Created working copy {}",tmpfile_slice.to_string());
    println!("Enter comment. Enter '*/' to end comment");

    comment_start(tmpfile_slice.to_string());    

    let mut x:usize = 0;
    let mut string_cont1 = String::new();

    while x <= lnum {
        string_cont1.push_str(v_cont[x]);
        string_cont1.push_str("\n");
        x += 1;
    }
    let  comment = std::fs::read_to_string(tmpfile_slice.to_string()).expect("Something went wrong reading the file");
    let mut x = lnum + 1;
    let mut string_cont2 = String::new();

    while x > lnum && x < lines {
        string_cont2.push_str(v_cont[x]);
        string_cont2.push_str("\n");
        x += 1;
    }

    string_cont2.trim();
    delete_file(fname.to_string());
    let mut f=open_file(fname.to_string());
    let mut f = BufWriter::new(f);
    f.write_all(string_cont1.as_bytes()).expect("Unable to write string_cont1");
    f.write_all(comment.as_bytes()).expect("Unable to write comment");
    f.write_all(string_cont2.as_bytes()).expect("Unable to write string_cont2");
    f.flush().unwrap(); // BufWriter needs this otherwise there may be no output

    return

}

And that is the end of the training project. For now. It's one thing to get something working, quite another to do it an elegant fashion :0)

2021-04-14

Learning Rust. Part 13. Training project. The infamous editor. Adding a comment block at a certain line number

 


This is a really clunky editor, agreed, but it's just a way to learn how to handle Rust's strangeness.
For example, in PHP, i generally get a handle to a file and then call a function with the the handle as one of the arguments. This was why the write_file() looks like it does. A problem arises immediately if I want to call this function two times (the insolence!). Then I get a message that the handle can't be copied!

This has been solved below. Filenames are the next problem. A string can't be copied either so if I do more than one operation on a file, the same ugliness appear. This also solved. I encountered a gotcha:

error: expected one of `!`, `(`, `+`, `::`, `<`, `>`, or `as`, found `{`   [40/1926]    --> src/main.rs:335:21          |
 335 |     while x =< lnum {
        |                     ^ expected one of 7 possible token    

CORRECT:
        while x <= lnum
Rust assumes that the "<" in "<=" is a type declaration of some sort. (like "<u32>") or whatever...

That sucked before finding it out.
One could object to my use of a file for storing the comment in (below), and rightly so, but it made my debugging easier. You may doubt this but, the fact is, the program didn't work the first time I started it :0). My point is that the more you program, the more mistakes will you make and, as a consequence, learn from. 

Below the function for adding a comment block at a certain line number in the .ini-file
Just to be clear, an entry "I" | "i" => insert_comment(fname_ini.to_string()); is now added to the main() function.

/*
The function makes it possible to enter a comment after a specific line number.
It contains the following actions:
Read the file to edit, store it in a vector (to be able to index it).
Make a backup copy of the file.
Create/overwrite an empty temp file where the comment will be saved.
Creating a comment with function start_comment(), storing it in the tmp file.
Moving the vector's string elements/lines up to the specific line from the vector to a string.
Moving the remaining vector elements to another string.
Writing the fist string, the comment and the last string to the original file.
Done.
*/

fn insert_comment(fname_ini:String){    

    let  cont = std::fs::read_to_string(&fname_ini).expect("Something went wrong reading the file");

    let s_slice: &str = &*cont;
    let v_cont: Vec<&str> = s_slice.split('\n').collect();  // to be able to index
    let lines = v_cont.len()-1;
    let mut fname = fname_ini.as_str();                          //String can't be copied, make str for copying

    list(fname.to_string());                                                // showing the file (w. line numbers)

    let bu = ".bu";
    let tmp = ".tmp";
    let s_orig = String::new();
    let s_new = String::new();
    let n:String = "\n".to_string();

    let lnum:usize = rdl_num("Prefixed by NewLine! Insert after line number: ".to_string(),lines);

    /* Convert String to &str, then use the names wherever, but suffixed with ".to_string()" */
    let target = fname.to_string() + &bu;   // create target name (="<fname>.bu")
    let target_slice = target.as_str();           // for using name several times w/o moving
    let tmpfile = fname.to_string() + tmp;  // create tmp file name 
    let tmpfile_slice = tmpfile.as_str();       // for using name several times w/o moving

    let f = create_empty(tmpfile_slice.to_string());    // Creating empty tmp file

    copy_file(fname.to_string(),target_slice.to_string()).expect("Something went wrong writing the backup");                                                     // copy original to; backup

    println!("Backup copy is named {}",target_slice.to_string());
    println!("Created working copy {}",tmpfile_slice.to_string());
    println!("Enter comment. Enter '*/' to end comment");

    comment_start(tmpfile_slice.to_string());   // getting and writing comment to tmp file

    let mut x:usize = 0;                                          // forced by grumpy compiler to use usize!
    let mut string_cont1 = String::new();            // where i put the first part of the .ini-file
    while x <= lnum {                                             // lnum is the line number the user will insert after
        string_cont1.push_str(v_cont[x]);              // push each 'str' from the vector to string string_cont1
        string_cont1.push_str("\n");                       // the "\n" in each string is lost when s_slice.split('\n')
        x += 1;
    }

    let  comment = std::fs::read_to_string(tmpfile_slice.to_string()).expect("Something went wrong reading the file");                                                 // getting the comment from the tmp file

    let mut x = lnum + 1;                                        // I do this again, now for the strings *after* lnum
    let mut string_cont2 = String::new();
    while x > lnum && x < lines {
        string_cont2.push_str(v_cont[x]);
        string_cont2.push_str("\n");
        x += 1;
    }

    delete_file(fname.to_string()); // remember, I have a backup! The delete_file() function is a new addition to my collection.

    let mut f=open_file(fname.to_string());           // here the .ini-file is recreated, empty

   /* 
problem using file handles more than once is solved, f is now a handle to the BufReader instead.
       The 1:st part of .ini, the comments and the last part of .ini is written to the disk and the
       buffer flushed
*/

    let mut f = BufWriter::new(f);
    f.write_all(string_cont1.as_bytes()).expect("Unable to write string_cont1");
    f.write_all(comment.as_bytes()).expect("Unable to write comment");
    f.write_all(string_cont2.as_bytes()).expect("Unable to write string_cont2");

    f.flush().unwrap();

    return                                                                       // and back again!
}

There are two functions left I'd like to  have: Add line and delete line. Maybe these will appear in the future?

To be continued...

For convenience, the whole program is attached below:

#![allow(warnings)]
use std::fs;
use std::fs::{File,OpenOptions};
use std::io::{self, Read, Write, ErrorKind};
use std::process::exit;
use std::str;
use termios::{Termios, TCSANOW, ECHO, ICANON, tcsetattr};
use std::string::String;
use std::io::BufWriter;


fn main() {
    let fname_own= std::env::current_exe().unwrap();         //
unused. Not implemented yet
    let fname_ini="/home/snkdb/rust/projects/playground/src/ed.ini";
    let msg = "\nCommands:\n[[=header, [=item, /*=start comment, */=end comment, l=list, e=edit               line".to_string();
    println!("{}", msg);
    let input=rdl("\nEnter command:\n".to_string());


    // trim_end_matches(' ') returns a string slice (&str) By default there is a
    // newline character at the end of the string so you need to use trim!
    let input=input.trim_end_matches('\n');                             // returns a string slice (&str)


    match input {
        "[[" => header(input.to_string(),fname_ini.to_string()),
        "[" => item(input.to_string(),fname_ini.to_string()),
        "/*" => comment_start(fname_ini.to_string()),
        "*/" => comment_end(input.to_string()),
        "H" | "h" => help(input.to_string()),
        "I" | "i" => insert_comment(fname_ini.to_string()),
        "L" | "l" => list(fname_ini.to_string()),
        "I" | "i" => insert_comment(fname_ini.to_string()),
        &_ => ret(input) 
    }
    main();
}

fn ret(input:&str){

    println!("Input is not acceptable {}",&input);
    main();
}

fn header(section_header:String,fname_ini:String) {
    println!("Header was: {}",section_header);
    let f = open_file (fname_ini.to_string());

    let prompt:String = "Section header chosen. Please enter the header text: >> ".to_string();
    let inp:String =rdl(prompt).trim().to_string(); 
    let mut header = String::new();
    header.push_str("\n["); // let's add the first piece, "["
    header.push_str(inp.as_str());                                                         // let's add the second part, 'inp'
    header.push_str("]\n"); // let's add the third part. "]"
    println!("Complete header is: {} ",header);                                 // printing this: "Complete header is: [test]:"
    let _res = write_file(header, f);
    main();
}

fn item(section_item:String,fname_ini:String){
    println!("Item was: {}",section_item);
    let f = open_file (fname_ini.to_string());

    //
getting the section item & item value
    let prompt:String = "Section item chosen. Please enter the item name: >>".to_string();
    let inp_name:String =rdl(prompt).trim().to_string();
    let prompt:String = "Section item chosen. Please enter the item text: >>".to_string();
    let inp_value:String =rdl(prompt).trim().to_string();

    // putting together the complete item line
    let mut inp_line = String::new();
    inp_line.push_str(&inp_name.as_str());
    let delim:String=String::from(": ");
    inp_line.push_str(&delim);
    inp_line.push_str(&inp_value);
    let n:String = String::from("\n");                               //
let's add a NewLine (needing 2 statements!)
    inp_line = inp_line + &n;
    println!("Complete item line is: {} ",inp_line);

    let res = write_file(inp_line.to_string(), f);                 
// adding the item line to the file
    main();
}

fn comment_end(com_end:String){
    println!("comment_end was: {}",com_end);
}

fn help(cont_hlp:String){
    println!("Help was: {}",cont_hlp);
}

fn list(fname_ini:String){
    let cont_list = std::fs::read_to_string(&fname_ini).expect("Something went wrong reading the file");
    let s_slice: &str = &*cont_list;
    let v: Vec<&str> = s_slice.split('\n').collect();
    let mut l_num:i32 = -1;
    print!("{}","\n");
    for val in v.iter() {
        l_num += 1;
        if l_num > 0 {
            println!("{} {}",l_num,val);
        }

    }
    return;
}

fn rdl(prompt:String) -> std::string::String {
    print!("{}", prompt);                                               // print whatever 'prompt is
    io::stdout().flush().unwrap();                                  // to force printout NOW, otherwise lines will come in wrong order!
    let mut st = String::new();                                        // a string to stuff my input in
    match std::io::stdin().read_line(&mut st) {
        Ok(_) => (st),
        Err(e) => (e.to_string()),
    }
}

fn rdl_num(prompt:String,lines:usize) -> usize {
    print!("{}: ",prompt);
    io::stdout().flush().unwrap();
    let mut lnum_try = String::new();
    std::io::stdin().read_line(&mut lnum_try);
    lnum_try = lnum_try.trim().to_string();
    let n = lnum_try.parse::<usize>().unwrap();
    return n;
}

fn open_file (fname:String) -> std::fs::File{
// NOTE: Files are automatically closed when they go out of scope.
// But: "The most general way for your function to use files is to take an open file handle as parameter, as this allows it to also use file handles that are not part of the filesystem"
    let f = OpenOptions::new().read(true).append(true).open(&fname);
    let mut f = match f {
        Ok(f) => return f,
        Err(error) => match error.kind() {
            ErrorKind::NotFound => match File::create(&fname){
            Ok(f) => return f,
            Err(e) => panic!("Failed creating file 'data' {:?}", e)
        },                                                                                          // one more is coming, a comma
            other_err => {
                panic!("Failed opening file 'data' {:?}", other_err)
            }
        }
    };                                                                                             // END of "let f = match f {", a semicolon!
}

fn copy_file(source:String,target:String) -> std::io::Result<()> {
    fs::copy(source, target)?; // Copy foo.txt to bar.txt
    Ok(())
}

fn write_file(contents:String,mut f:std::fs::File) -> std::io::Result<String> {
    f.write_all(contents.as_bytes())?;
    f.sync_data().expect("write_file: Failed syncing data");
    Ok("OK".to_string())
}

fn delete_file(fname:String) -> std::io::Result<()> {
    fs::remove_file(fname)?;
    Ok(())
}

fn create_empty(fname:String) -> std::io::Result<()> {
    File::create(fname)?;
    Ok(())
}

// currently not implemented
fn get_exec_name() -> Option<String> {
    std::env::current_exe()
    .ok()
    .and_then(|pb| pb.file_name().map(|s| s.to_os_string()))
    .and_then(|s| s.into_string().ok())
}

    fn comment_start(fname_ini:String) {
    let fname_slice: &str = &fname_ini;
    let mut s = String::new();
    let a:&str = "1";
    let b:&str = "1";

    while a == "1" && b == "1" {
        s = t_get(s,fname_slice.to_string());
        if s.len()<1 {
            comment_start(fname_slice.to_string());
        }
        let n1 = s.len()-1;
       let ch1 = s.chars().nth(n1).unwrap();
        s=t_get(s,fname_slice.to_string());
        if s.len()<1 {
            comment_start(fname_slice.to_string());
        }

    let n2 = s.len()-1;
    let ch2 = s.chars().nth(n2).unwrap();
    if (ch1 == '*') && (ch2 == '/') {
        let s=add_comment_tags(s);
        let f = open_file(fname_slice.to_string());
        write_file(s,f);
        print!("\n");
        list(fname_slice.to_string());
        io::stdout().flush().unwrap();
        return;
    }

    if ch2 == '*' {
        s=t_get(s,fname_slice.to_string());
        if s.len()<1 {
            comment_start(fname_slice.to_string());
        }
        let n3 = s.len()-1;
        let ch3 = s.chars().nth(n3).unwrap();
        // print!("{}",ch3);
        // io::stdout().flush().unwrap();
        if ch3 == '/' {
            let s=add_comment_tags(s);
            let f = open_file(fname_slice.to_string());
            write_file(s,f);
            list(fname_slice.to_string());
            return;
        }

       }

    }

}

//
shortening comment_start() by not repeating the same code twice
fn add_comment_tags(mut s:String) -> std::string::String {
    let prefix:String = "\n/* ".to_string();
    let suffix:String = "\n".to_string();
    s = prefix + &s + &suffix;
    s
}

// This is the Termios crate (with it's name chancged for clarity), as it is downloaded
fn getachar() -> [u8;1]{ // result is an ARRAY!
    let stdin = 0;                                                                                  // couldn't get std::os::unix::io::FromRawFd to work
                                                                                                            // on /dev/stdin or /dev/tty
    let termios = Termios::from_fd(stdin).unwrap();
    let mut new_termios = termios.clone();                                     // make a mutable copy of termios that we will modify
    new_termios.c_lflag &= !(ICANON | ECHO);                         // no echo and canonical mode
    tcsetattr(stdin, TCSANOW, &mut new_termios).unwrap();
    let stdout = io::stdout();
    let mut reader = io::stdin();
    let mut buffer = [0;1];                                                                  // read exactly one byte [into this ARRAY!]
    stdout.lock().flush().unwrap();
    reader.read_exact(&mut buffer).unwrap();
    let c = buffer;
    tcsetattr(stdin, TCSANOW, & termios).unwrap();                  // reset the stdin to
    // original termios data
    return c;
}

fn t_get(mut s:String, fname_ini:String) -> String {
    let mut raw = getachar();
    // print!("{}",raw[0]); // activated, shows dec # of u8
    if raw[0] > 127 {
        raw = [7; 1];
    }

    if raw[0] == 127 {
        if s.len()<1 {
            comment_start(fname_ini);
        }
        s.pop();
        print!(" {}",s);
        return s;
    };
    let mybyte = str::from_utf8(&raw).unwrap();
    s.push_str(&mybyte);
    let n1 = s.len()-1;
    let ch1 = s.chars().nth(n1).unwrap();
    print!("{}",ch1);
    io::stdout().flush().unwrap();
    s
}

/*
The function makes it possible to enter a comment after a specific line number.
It contains the following actions:
Read the file to edit, store it in a vector (to be able to index it).
Make a backup copy of the file.
Create/overwrite an empty temp file where the comment will be saved.
Creating a comment with function start_comment(), storing it in the tmp file.
Moving the vector's string elements/lines up to the specific line from the vector to a string
Moving the remaining vector elements to another string
Writing the fist string, the comment and the last string to the original file
Done
*/

fn insert_comment(fname_ini:String){
    let cont = std::fs::read_to_string(&fname_ini).expect("Something went wrong reading the file");
    let s_slice: &str = &*cont;
    let v_cont: Vec<&str> = s_slice.split('\n').collect();     // to be able to index
    let lines = v_cont.len()-1;
    let fname = fname_ini.as_str();                                     // String can't be copied, make str for copying
    list(fname.to_string()); // showing the file (w. line numbers)

    let bu = ".bu";
    let tmp = ".tmp";
    let s_orig = String::new();
    let s_new = String::new();
    let n:String = "\n".to_string();

    let lnum:usize = rdl_num("Prefixed by NewLine! Insert after line number: ".to_string(),lines);

    let target = fname.to_string() + &bu;                           // create target name (="<fname>.bu")
    let target_slice = target.as_str();                                   // for using name several times w/o moving
    let tmpfile = fname.to_string() + tmp;                          // create tmp file name
    let tmpfile_slice = tmpfile.as_str();                                // for using name several times w/o moving

    let mut f = create_empty(tmpfile_slice.to_string());      // Creating empty tmp file

    copy_file(fname.to_string(),target_slice.to_string());     // copy original to; backup

    println!("Backup copy is named {}",target_slice.to_string());
    println!("Created working copy {}",tmpfile_slice.to_string());
    println!("Enter comment. Enter '*/' to end comment");

    comment_start(tmpfile_slice.to_string());

    let mut x:usize = 0;
    let mut string_cont1 = String::new();
    while x <= lnum {
        println!("1:v_cont[x], x {},{} ",v_cont[x],x);
        string_cont1.push_str(v_cont[x]);
        string_cont1.push_str("\n");
        x += 1;
    }

    let comment = std::fs::read_to_string(tmpfile_slice.to_string()).expect("Something went wrong reading the file");
    let mut x = lnum + 1;
    let mut string_cont2 = String::new();
    while x > lnum && x < lines {
        println!("2:v_cont[x], x {},{} ",v_cont[x],x);
        string_cont2.push_str(v_cont[x]);
        string_cont2.push_str("\n");
        x += 1;
    }

    delete_file(fname.to_string()).expect("Failed deleting original file!");

    let mut f=open_file(fname.to_string());
    let mut f = BufWriter::new(f);
    f.write_all(string_cont1.as_bytes()).expect("Unable to write string_cont1");
    f.write_all(comment.as_bytes()).expect("Unable to write comment");
    f.write_all(string_cont2.as_bytes()).expect("Unable to write string_cont2");
    f.flush().unwrap();

    return
}