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) {
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();
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 {
// 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
}
/*
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");
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
}
Inga kommentarer:
Skicka en kommentar