wellington 0.0.1

A lightweight blogging engine using markdown and supporting sidenotes
Documentation
extern crate getopts;
extern crate wellington;
extern crate handlebars;

use std::env;
use std::fs;
use std::path::PathBuf;
use getopts::Options;
use handlebars::no_escape;

use wellington::{html_from_markdown, Blog, PostData, IndexedBlogPost};
use wellington::templates::{AllTemplates, POST_TEMPLATE};
use wellington::rss::CoreData;


fn usage(program: &str, init_opts: &str) -> String {
    format!(r#"Usage: {} [command]

Where command is one of:
    convert <input> <output>    Convert input markdown file to output html file

    sync [-f]                   Sync all blog posts in the current blog directory, 
                                refreshing the table of contents. 
                                
                                If no posts were updated, the index and posts 
                                won't be re-rendered, unless you use the -f flag. 
                                Use this flag when changing templates, for example.

    init <options>              Initialise the current directory as a blog. You must 
                                provide the following options:{}
"#, program, init_opts)
}


fn convert(input_filename: &str, output_filename: &str) {
    let input = fs::read_to_string(input_filename).expect("Error reading input file");
    let post_template = String::from_utf8_lossy(POST_TEMPLATE);
    let mut template = match AllTemplates::make_template(&post_template, "default-template") {
        Ok(t) => t,
        Err(e) => {
            eprintln!("{}", e);
            std::process::exit(1);
        }
    };
    let article = "some article";
    template.register_escape_fn(no_escape);
    match AllTemplates::validate::<PostData<'static>>(&template, 
                                                      &PostData::new(&article),
                                                      "default-path") {
        Ok(t) => t,
        Err(e) => {
            eprintln!("{}", e);
            std::process::exit(1);
        }
    };
    let output = match html_from_markdown(&input, "".to_string()) {
        Ok(ht) => ht,
        Err(err) => {
            println!("{}", err);
            std::process::exit(1);
        }
    };
    let mut bp = IndexedBlogPost::example();
    bp.set_title(&output.title);

    let data = PostData::from((output.html.as_str(), &mut bp, "", "".to_string(),
        output.sidenotes));
    let rendered = match data.render(&template) {
        Ok(ht) => ht,
        Err(err) => {
            println!("{}", err);
            std::process::exit(1);
        }
    };
    fs::write(output_filename, rendered).expect("Error writing result");
}


fn current_dir() -> PathBuf {
    match env::current_dir() {
        Ok(p) => p,
        _ => {
            println!("Couldn't access the current directory. Do you have sufficient permissions?");
            std::process::exit(1);
        }
    }
}


fn init(title: &str,
        home_url: &str,
        description: &str,
        author: &str,
        post: Option<String>, 
        index: Option<String>) {
    let mut blog = match Blog::new(current_dir()) {
        Ok(b) => b,
        Err(e) => {
            println!("{}", e);
            std::process::exit(1);
        }
    };
    let core_data = match CoreData::new(
        title, home_url, description, author,
        &blog.index_url) {
        Ok(d) => d,
        Err(err) => {
            println!("{}", err);
            std::process::exit(1);
        }
    };
    match blog.init(core_data, post, index) {
        Ok(_) => println!("Initialised new empty blog"),
        Err(e)  => println!("{}", e)
    }
}


fn sync(force: bool) {
    let mut blog = match Blog::new(current_dir()) {
        Ok(b) => b,
        Err(e) => {
            println!("{}", e);
            std::process::exit(1);
        }
    };
    match blog.sync(force) {
        Ok(i) => println!("Updated {} posts", i),
        Err(err) => {
            println!("Couldn't sync: {}", err);
            std::process::exit(1);
        }
    }
}


fn main() {
    let args :Vec<String> = env::args().collect();
    let mut init_opts = Options::new();
    init_opts.reqopt("t", "title", "Blog title: give your blog a name!", "BLOG_TITLE");
    init_opts.reqopt("u", "home_url", "The home URL where your blog will be hosted,
    for example https://myblog.com", "HOME_URL");
    init_opts.reqopt("d", "desc", "Describe your blog", "BLOG_DESCRIPTION");
    init_opts.reqopt("a", "author", "Who are you? Please give your name. This will be make public in the RSS feed", "BLOG_AUTHOR");
    init_opts.optopt("p", "post", "(Optional) Template for rendering individual posts", 
                     "POST_TEMPLATE");
    init_opts.optopt("i", "index", "(Optional) Template for rendering the table of contents", 
                     "INDEX_TEMPLATE");

    if args.len() == 1 {
        eprintln!("{}", usage(&args[0], &init_opts.usage("")));
        std::process::exit(1);
    }
    
    let command = &args[1];
    if command == "convert" {
        if args.len() < 4 {
            eprintln!("Please give me 2 arguments: input and output");
            std::process::exit(1);
        } 
        convert(&args[2], &args[3]);
    } else if command == "sync" {
        if args.len() == 3 && args[2] == "-f" {
            sync(true);
        } else {
            sync(false);
        }
    } else if command == "init" {
        let matches = match init_opts.parse(&args[1..]) {
            Ok(m) => m,
            Err(e) => {
                eprintln!("Error: {}", e.to_string());
                std::process::exit(1);
            }
        };
    init(&matches.opt_str("title").unwrap(),
         &matches.opt_str("home_url").unwrap(),
         &matches.opt_str("desc").unwrap(),
         &matches.opt_str("author").unwrap(),
         matches.opt_str("post"), 
         matches.opt_str("index"));
    } else {
        eprintln!("I don't recognise this command :(");
    }
}