about summary refs log blame commit diff stats
path: root/src/posts.rs
blob: 1dfd167678ddbe5e7da3bd0ad0f95ca114a6163a (plain) (tree)
1
2
3
4
5
6
7
8
9

            


                   
                
              
                 
                
 


                 












                                               
                                      

                                        
 



                                           
                                  

                     
                                       
 







                                          
 
                     
                                              

                     

                                        
            
                 
      
 



                                         










                                               

               
          


                               
                                       






                                
                                           

                                 
                                                          



                       









                                                      




                                      
                

                              






                                                                                  
 












                                                            
          

 
                                 

                                                       


                                                    

                                               



            
                   
 
                     
                            
 

                                               
 


                            


                                                          
                   
                              

     



                                               
                                      


                                            
                                              

                                 

                     
                                           
 



                                             
                                                      
 







                                        







                                           
 
                
 
               


          
                                 

                                                       


                                              
                                               
                   
                                 



            
                   
 

                                               
 
                                   
                   
                                                          
                   
                              

     

                          
 
          
 




























                                                         














                                                         
 
use std::io;

#[cfg(not(test))]
use std::{env, fs};

use crate::conf;
use crate::db;
use crate::error;
use crate::user;

#[cfg(not(test))]
use crate::ed;

// Make sure nobody encodes narsty characters
// into a message to negatively affect other
// users
fn str_to_utf8(str: &str) -> String {
    str.chars()
        .map(|c| {
            let mut buf = [0; 4];
            c.encode_utf8(&mut buf).to_string()
        })
        .collect::<String>()
}

// First handler for creating a new post.
pub fn create() -> error::Result<()> {
    println!();
    println!("Title of the new post: ");

    #[cfg(test)]
    let title = String::from("TEST_TITLE");

    #[cfg(not(test))]
    let mut title = String::new();

    #[cfg(not(test))]
    io::stdin().read_line(&mut title)?;

    let title = str_to_utf8(title.trim());
    let title = if title.len() > 30 {
        &title[..30]
    } else {
        &title
    };

    println!();

    #[cfg(not(test))]
    let body_raw = str_to_utf8(&ed::call(""));

    #[cfg(not(test))]
    let body = if body_raw.len() > 500 {
        &body_raw[..500]
    } else {
        &body_raw
    };

    #[cfg(test)]
    let body = String::from("TEST_BODY");

    let trimmed_body = body.trim();
    let user = &*user::NAME;

    let mut all = db::Posts::get_all(db::PATH);
    let new = db::Post {
        author: user.into(),
        title: title.to_string(),
        body: trimmed_body.to_string(),
    };

    all.append(new);
    all.write();

    println!();
    Ok(())
}

// Shows the most recent posts.
pub fn display() -> error::Result<()> {
    let args = &*conf::ARGS;
    let line_len = args
        .value_of("line")
        .unwrap_or_else(|| "80")
        .parse::<usize>()
        .unwrap_or_else(|_| 80);

    let all = db::Posts::get_all(db::PATH);

    let mut postvec = Vec::new();
    all.posts().iter().enumerate().for_each(|(id, post)| {
        let body = post
            .body
            .trim()
            .chars()
            .enumerate()
            .map(|(i, e)| {
                let i = i + 1;
                if line_len > 9 && i % line_len == 0 {
                    return format!("{}\n", e);
                }
                e.to_string()
            })
            .collect::<String>();

        let newpost = format!(
            "{}. {} -> by {}\n{}\n\n",
            id + 1,
            post.title.trim(),
            post.author,
            body
        );
        postvec.push(newpost);
    });

    for (i, e) in postvec.iter().enumerate() {
        if (postvec.len() > 14 && i >= postvec.len() - 15) || postvec.len() < 15 {
            print!("{}", e);
        }
    }

    // copy the json file to the user's home directory
    // so it can be compared to the global file to check
    // for new posts.
    // previously, I'd hashed it, but I feel like it's
    // better to leave the hashing to UNIX utilities
    // than to add a heavy-ish dependency.
    #[cfg(not(test))]
    {
        let homedir = env::var("HOME")?;
        let localdest = format!("{}/.clinte.json", homedir);
        fs::copy(db::PATH, localdest)?;
    }

    Ok(())
}

// First handler to update posts.
pub fn update_handler(id: usize) -> error::Result<()> {
    let mut id_num_in = if id == 0 {
        println!();
        println!("ID number of your post to edit?");
        let mut id_num_in = String::new();
        io::stdin().read_line(&mut id_num_in)?;
        id_num_in.trim().parse()?
    } else {
        id
    };

    id_num_in -= 1;

    #[cfg(not(test))]
    let user = &*user::NAME;

    let mut all = db::Posts::get_all(db::PATH);
    let post = all.get(id_num_in);

    #[cfg(test)]
    let user = &post.author;

    if *user != post.author {
        println!();
        println!("Users don't match. Can't update post!");
        println!();
        std::process::exit(1);
    }

    #[cfg(test)]
    let new_title = String::from("TEST_TITLE");

    #[cfg(not(test))]
    let mut new_title = String::new();

    println!("Updating post {}", id_num_in);
    println!();
    println!("Current Title: {}", post.title);
    println!();
    println!("Enter new title:");

    #[cfg(not(test))]
    io::stdin().read_line(&mut new_title)?;

    #[cfg(test)]
    let body_raw = String::from("TEST_BODY");

    #[cfg(not(test))]
    let body_raw = str_to_utf8(&ed::call(&post.body));

    let body = if body_raw.len() > 500 {
        &body_raw[..500]
    } else {
        &body_raw
    };

    let trimmed_body = body.trim();

    all.replace(
        id_num_in,
        db::Post {
            author: user.into(),
            title: new_title,
            body: trimmed_body.to_string(),
        },
    );

    all.write();

    println!();
    Ok(())
}

// First handler to remove a post
pub fn delete_handler(id: usize) -> error::Result<()> {
    let mut id_num_in = if id == 0 {
        println!();
        println!("ID of the post to delete?");
        let mut id_num_in = String::new();
        io::stdin().read_line(&mut id_num_in)?;
        println!();
        id_num_in.trim().parse()?
    } else {
        id
    };

    id_num_in -= 1;

    let mut all = db::Posts::get_all(db::PATH);
    let post = all.get(id_num_in);

    if *user::NAME != post.author {
        println!();
        println!("Users don't match. Can't delete post!");
        println!();
        std::process::exit(1);
    }

    all.delete(id_num_in);
    all.write();

    Ok(())
}

#[cfg(test)]
mod tests {
    use super::*;

    use std::fs;

    #[test]
    fn test_str_to_utf8() {
        let lhs = "foobar";
        let rhs = str_to_utf8(lhs);
        assert_eq!(lhs, rhs);
    }

    #[test]
    fn display_doesnt_explode() {
        assert!(display().is_ok());
    }

    #[test]
    fn test_update_handler() {
        fs::copy(db::PATH, "clinte_bak.json").unwrap();
        update_handler(1).unwrap();
        let all = db::Posts::get_all(db::PATH);
        let post = all.get(0);
        assert_eq!(post.title, "TEST_TITLE");
        assert_eq!(post.body, "TEST_BODY");
        fs::rename("clinte_bak.json", db::PATH).unwrap();
    }

    #[test]
    fn test_create_delete() {
        fs::copy(db::PATH, "clinte_bak.json").unwrap();
        create().unwrap();
        let all = db::Posts::get_all(db::PATH);
        let post = all.get(1);

        assert_eq!(post.title, "TEST_TITLE");
        assert_eq!(post.body, "TEST_BODY");

        delete_handler(2).unwrap();

        fs::rename("clinte_bak.json", db::PATH).unwrap();
    }
}