<commit_before>use neb::server::*;
use neb::ram::schema::*;
use neb::server::transactions;
use neb::ram::types::*;
use neb::server::transactions::*;
use std::rc::Rc;
use neb::ram::cell::*;
use super::*;

#[test]
pub fn workspace_wr() {
    let server_addr = String::from("127.0.0.1:5200");
    let server = NebServer::new(ServerOptions {
        chunk_count: 1,
        memory_size: 16 * 1024,
        standalone: false,
        is_meta: true,
        meta_members: vec!(server_addr.clone()),
        address: server_addr.clone(),
        backup_storage: None,
        meta_storage: None,
        group_name: String::from("test"),
    }).unwrap();
    let mut schema = Schema {
        id: 1,
        name: String::from("test"),
        key_field: None,
        fields: default_fields()
    };
    server.meta.schemas.new_schema(&mut schema);
    let txn = transactions::new_client(&server_addr).unwrap();
    let txn_id = txn.begin().unwrap().unwrap();
    let mut data_map = Map::<String, Value>::new();
    data_map.insert(String::from("id"), Value::I64(100));
    data_map.insert(String::from("score"), Value::U64(70));
    data_map.insert(String::from("name"), Value::String(String::from("Jack")));
    let cell_1 = Cell::new(schema.id, &Id::rand(), data_map.clone());
    let cell_1_w_res = txn.write(&txn_id, &cell_1).unwrap().unwrap();
    match cell_1_w_res {
        TxnExecResult::Accepted(()) => {},
        _ => {panic!("write cell 1 not accepted {:?}", cell_1_w_res)}
    }
    let cell_1_r_res = txn.read(&txn_id, &cell_1.id()).unwrap().unwrap();
    match cell_1_r_res {
        TxnExecResult::Accepted(cell) => {
            assert_eq!(cell.id(), cell_1.id());
            assert_eq!(cell.data.Map().unwrap().get("id").unwrap().I64().unwrap(), 100);
            assert_eq!(cell.data.Map().unwrap().get("name").unwrap().String().unwrap(), "Jack");
            assert_eq!(cell.data.Map().unwrap().get("score").unwrap().U64().unwrap(), 70);
        },
        _ => {panic!("read cell 1 not accepted {:?}", cell_1_r_res)}
    }
    data_map.insert(String::from("score"), Value::U64(90));
    let cell_1_w2 = Cell::new(schema.id, &cell_1.id(), data_map.clone());
    let cell_1_w_res = txn.write(&txn_id, &cell_1_w2).unwrap().unwrap();
    match cell_1_w_res {
        TxnExecResult::Accepted(()) => {panic!("Write existed cell should fail")}
        TxnExecResult::Error(WriteError::CellAlreadyExisted) => {}
        _ => {panic!("Wrong feedback {:?}", cell_1_w_res)}
    }
    let cell_1_r_res = txn.read(&txn_id, &cell_1.id()).unwrap().unwrap();
    match cell_1_r_res {
        TxnExecResult::Accepted(cell) => {
            assert_eq!(cell.id(), cell_1.id());
            assert_eq!(cell.data.Map().unwrap().get("score").unwrap().U64().unwrap(), 70);
        },
        _ => {panic!("read cell 1 not accepted {:?}", cell_1_r_res)}
    }
    let cell_1_u_res = txn.update(&txn_id, &cell_1_w2).unwrap().unwrap();
    match cell_1_u_res {
        TxnExecResult::Accepted(()) => {},
        _ => {panic!("update cell 1 not accepted")}
    }
    let cell_1_r_res = txn.read(&txn_id, &cell_1.id()).unwrap().unwrap();
    match cell_1_r_res {
        TxnExecResult::Accepted(cell) => {
            assert_eq!(cell.id(), cell_1.id());
            assert_eq!(cell.data.Map().unwrap().get("score").unwrap().U64().unwrap(), 90);
        },
        _ => {panic!("read cell 1 not accepted {:?}", cell_1_r_res)}
    }
    let cell_1_rm_res = txn.remove(&txn_id, &cell_1.id()).unwrap().unwrap();
    match cell_1_rm_res {
        TxnExecResult::Accepted(()) => {},
        _ => {panic!("remove cell 1 not accepted {:?}", cell_1_rm_res)}
    }
    let cell_1_r_res = txn.read(&txn_id, &cell_1.id()).unwrap().unwrap();
    match cell_1_r_res {
        TxnExecResult::Error(ReadError::CellDoesNotExisted) => {},
        _ => {panic!("read cell 1 not accepted {:?}", cell_1_r_res)}
    }
    assert_eq!(txn.prepare(&txn_id).unwrap().unwrap(), TMPrepareResult::Success);
    assert_eq!(txn.commit(&txn_id).unwrap().unwrap(), EndResult::Success);
    assert_eq!(txn.commit(&txn_id).unwrap(), Err(TMError::TransactionNotFound));
    // committed transaction should have been disposed
}

#[test]
pub fn data_site_wr() {
    let server_addr = String::from("127.0.0.1:5201");
    let server = NebServer::new(ServerOptions {
        chunk_count: 1,
        memory_size: 16 * 1024 * 1024,
        standalone: false,
        is_meta: true,
        meta_members: vec!(server_addr.clone()),
        address: server_addr.clone(),
        backup_storage: None,
        meta_storage: None,
        group_name: String::from("test"),
    }).unwrap();
    let mut schema = Schema {
        id: 1,
        name: String::from("test"),
        key_field: None,
        fields: default_fields()
    };
    server.meta.schemas.new_schema(&mut schema);
    let txn = transactions::new_client(&server_addr).unwrap();
    let txn_id = txn.begin().unwrap().unwrap();
    let mut data_map = Map::<String, Value>::new();
    data_map.insert(String::from("id"), Value::I64(100));
    data_map.insert(String::from("score"), Value::U64(70));
    data_map.insert(String::from("name"), Value::String(String::from("Jack")));
    let cell_1 = Cell::new(schema.id, &Id::rand(), data_map.clone());
    let cell_1_non_exists_read = txn.read(&txn_id, &cell_1.id()).unwrap().unwrap();
    match cell_1_non_exists_read {
        TxnExecResult::Error(ReadError::CellDoesNotExisted) => {},
        _ => {panic!("read non-existed cell should fail but got {:?}", cell_1_non_exists_read)}
    }
    let cell_1_write = txn.write(&txn_id, &cell_1).unwrap().unwrap();
    let cell_1_r_res = txn.read(&txn_id, &cell_1.id()).unwrap().unwrap();
    match cell_1_r_res {
        TxnExecResult::Accepted(cell) => {
            assert_eq!(cell.id(), cell_1.id());
            assert_eq!(cell.data.Map().unwrap().get("id").unwrap().I64().unwrap(), 100);
            assert_eq!(cell.data.Map().unwrap().get("name").unwrap().String().unwrap(), "Jack");
            assert_eq!(cell.data.Map().unwrap().get("score").unwrap().U64().unwrap(), 70);
        },
        _ => {panic!("read cell 1 not accepted {:?}", cell_1_r_res)}
    }
    data_map.insert(String::from("score"), Value::U64(90));
    let cell_1_w2 = Cell::new(schema.id, &cell_1.id(), data_map.clone());
    let cell_1_w_res = txn.update(&txn_id, &cell_1_w2).unwrap().unwrap();
    match cell_1_w_res {
        TxnExecResult::Accepted(()) => {}
        _ => {panic!("Wrong feedback {:?}", cell_1_w_res)}
    }
    assert!(server.chunks.read_cell(&cell_1.id()).is_err()); // isolation test
    assert_eq!(txn.prepare(&txn_id).unwrap().unwrap(), TMPrepareResult::Success);
    assert_eq!(txn.commit(&txn_id).unwrap().unwrap(), EndResult::Success);
    let cell_r2 = server.chunks.read_cell(&cell_1.id()).unwrap();
    assert_eq!(cell_r2.id(), cell_1.id());
    assert_eq!(cell_r2.data.Map().unwrap().get("id").unwrap().I64().unwrap(), 100);
    assert_eq!(cell_r2.data.Map().unwrap().get("name").unwrap().String().unwrap(), "Jack");
    assert_eq!(cell_r2.data.Map().unwrap().get("score").unwrap().U64().unwrap(), 90);
}

#[test]
pub fn multi_transaction() {
    let server_addr = String::from("127.0.0.1:5202");
    let server = NebServer::new(ServerOptions {
        chunk_count: 1,
        memory_size: 16 * 1024 * 1024,
        standalone: false,
        is_meta: true,
        meta_members: vec!(server_addr.clone()),
        address: server_addr.clone(),
        backup_storage: None,
        meta_storage: None,
        group_name: String::from("test"),
    }).unwrap();
    let mut schema = Schema {
        id: 1,
        name: String::from("test"),
        key_field: None,
        fields: default_fields()
    };
    server.meta.schemas.new_schema(&mut schema);
    let txn = transactions::new_client(&server_addr).unwrap();
    let txn_1_id = txn.begin().unwrap().unwrap();
    let txn_2_id = txn.begin().unwrap().unwrap();
    let mut data_map_1 = Map::<String, Value>::new();
    data_map_1.insert(String::from("id"), Value::I64(100));
    data_map_1.insert(String::from("score"), Value::U64(70));
    data_map_1.insert(String::from("name"), Value::String(String::from("Jack")));
    let cell_1 = Cell::new(schema.id, &Id::rand(), data_map_1.clone());
    let cell_1_t1_write = txn.update(&txn_1_id, &cell_1).unwrap().unwrap();
    let data_map_2 = data_map_1.clone();
    data_map_1.insert(String::from("score"), Value::U64(90));
    let cell_2 = Cell::new(schema.id, &cell_1.id(), data_map_2.clone());
    let cell_1_t2_write = txn.write(&txn_2_id, &cell_2).unwrap().unwrap();
    txn.prepare(&txn_2_id).unwrap().unwrap();
    txn.commit(&txn_2_id).unwrap().unwrap();
    assert_eq!(txn.prepare(&txn_1_id).unwrap().unwrap(), TMPrepareResult::Success);
    assert_eq!(txn.commit(&txn_1_id).unwrap().unwrap(), EndResult::Success);
    ///////////////// PHASE 2 //////////////////
    let txn_1_id = txn.begin().unwrap().unwrap();
    let txn_2_id = txn.begin().unwrap().unwrap();
    match txn.read(&txn_2_id, &cell_1.id()).unwrap().unwrap() {
        TxnExecResult::Accepted(_) => {},
        _ => {panic!("Cannot read cell 1 for txn 2");}
    }
    txn.update(&txn_1_id, &cell_1).unwrap().unwrap();
    assert_eq!(txn.prepare(&txn_1_id).unwrap().unwrap(),
    TMPrepareResult::DMPrepareError(DMPrepareResult::NotRealizable));

}<commit_msg>check failed prepare actually garbage collected<commit_after>use neb::server::*;
use neb::ram::schema::*;
use neb::server::transactions;
use neb::ram::types::*;
use neb::server::transactions::*;
use std::rc::Rc;
use neb::ram::cell::*;
use super::*;

#[test]
pub fn workspace_wr() {
    let server_addr = String::from("127.0.0.1:5200");
    let server = NebServer::new(ServerOptions {
        chunk_count: 1,
        memory_size: 16 * 1024,
        standalone: false,
        is_meta: true,
        meta_members: vec!(server_addr.clone()),
        address: server_addr.clone(),
        backup_storage: None,
        meta_storage: None,
        group_name: String::from("test"),
    }).unwrap();
    let mut schema = Schema {
        id: 1,
        name: String::from("test"),
        key_field: None,
        fields: default_fields()
    };
    server.meta.schemas.new_schema(&mut schema);
    let txn = transactions::new_client(&server_addr).unwrap();
    let txn_id = txn.begin().unwrap().unwrap();
    let mut data_map = Map::<String, Value>::new();
    data_map.insert(String::from("id"), Value::I64(100));
    data_map.insert(String::from("score"), Value::U64(70));
    data_map.insert(String::from("name"), Value::String(String::from("Jack")));
    let cell_1 = Cell::new(schema.id, &Id::rand(), data_map.clone());
    let cell_1_w_res = txn.write(&txn_id, &cell_1).unwrap().unwrap();
    match cell_1_w_res {
        TxnExecResult::Accepted(()) => {},
        _ => {panic!("write cell 1 not accepted {:?}", cell_1_w_res)}
    }
    let cell_1_r_res = txn.read(&txn_id, &cell_1.id()).unwrap().unwrap();
    match cell_1_r_res {
        TxnExecResult::Accepted(cell) => {
            assert_eq!(cell.id(), cell_1.id());
            assert_eq!(cell.data.Map().unwrap().get("id").unwrap().I64().unwrap(), 100);
            assert_eq!(cell.data.Map().unwrap().get("name").unwrap().String().unwrap(), "Jack");
            assert_eq!(cell.data.Map().unwrap().get("score").unwrap().U64().unwrap(), 70);
        },
        _ => {panic!("read cell 1 not accepted {:?}", cell_1_r_res)}
    }
    data_map.insert(String::from("score"), Value::U64(90));
    let cell_1_w2 = Cell::new(schema.id, &cell_1.id(), data_map.clone());
    let cell_1_w_res = txn.write(&txn_id, &cell_1_w2).unwrap().unwrap();
    match cell_1_w_res {
        TxnExecResult::Accepted(()) => {panic!("Write existed cell should fail")}
        TxnExecResult::Error(WriteError::CellAlreadyExisted) => {}
        _ => {panic!("Wrong feedback {:?}", cell_1_w_res)}
    }
    let cell_1_r_res = txn.read(&txn_id, &cell_1.id()).unwrap().unwrap();
    match cell_1_r_res {
        TxnExecResult::Accepted(cell) => {
            assert_eq!(cell.id(), cell_1.id());
            assert_eq!(cell.data.Map().unwrap().get("score").unwrap().U64().unwrap(), 70);
        },
        _ => {panic!("read cell 1 not accepted {:?}", cell_1_r_res)}
    }
    let cell_1_u_res = txn.update(&txn_id, &cell_1_w2).unwrap().unwrap();
    match cell_1_u_res {
        TxnExecResult::Accepted(()) => {},
        _ => {panic!("update cell 1 not accepted")}
    }
    let cell_1_r_res = txn.read(&txn_id, &cell_1.id()).unwrap().unwrap();
    match cell_1_r_res {
        TxnExecResult::Accepted(cell) => {
            assert_eq!(cell.id(), cell_1.id());
            assert_eq!(cell.data.Map().unwrap().get("score").unwrap().U64().unwrap(), 90);
        },
        _ => {panic!("read cell 1 not accepted {:?}", cell_1_r_res)}
    }
    let cell_1_rm_res = txn.remove(&txn_id, &cell_1.id()).unwrap().unwrap();
    match cell_1_rm_res {
        TxnExecResult::Accepted(()) => {},
        _ => {panic!("remove cell 1 not accepted {:?}", cell_1_rm_res)}
    }
    let cell_1_r_res = txn.read(&txn_id, &cell_1.id()).unwrap().unwrap();
    match cell_1_r_res {
        TxnExecResult::Error(ReadError::CellDoesNotExisted) => {},
        _ => {panic!("read cell 1 not accepted {:?}", cell_1_r_res)}
    }
    assert_eq!(txn.prepare(&txn_id).unwrap().unwrap(), TMPrepareResult::Success);
    assert_eq!(txn.commit(&txn_id).unwrap().unwrap(), EndResult::Success);
    assert_eq!(txn.commit(&txn_id).unwrap(), Err(TMError::TransactionNotFound));
    // committed transaction should have been disposed
}

#[test]
pub fn data_site_wr() {
    let server_addr = String::from("127.0.0.1:5201");
    let server = NebServer::new(ServerOptions {
        chunk_count: 1,
        memory_size: 16 * 1024 * 1024,
        standalone: false,
        is_meta: true,
        meta_members: vec!(server_addr.clone()),
        address: server_addr.clone(),
        backup_storage: None,
        meta_storage: None,
        group_name: String::from("test"),
    }).unwrap();
    let mut schema = Schema {
        id: 1,
        name: String::from("test"),
        key_field: None,
        fields: default_fields()
    };
    server.meta.schemas.new_schema(&mut schema);
    let txn = transactions::new_client(&server_addr).unwrap();
    let txn_id = txn.begin().unwrap().unwrap();
    let mut data_map = Map::<String, Value>::new();
    data_map.insert(String::from("id"), Value::I64(100));
    data_map.insert(String::from("score"), Value::U64(70));
    data_map.insert(String::from("name"), Value::String(String::from("Jack")));
    let cell_1 = Cell::new(schema.id, &Id::rand(), data_map.clone());
    let cell_1_non_exists_read = txn.read(&txn_id, &cell_1.id()).unwrap().unwrap();
    match cell_1_non_exists_read {
        TxnExecResult::Error(ReadError::CellDoesNotExisted) => {},
        _ => {panic!("read non-existed cell should fail but got {:?}", cell_1_non_exists_read)}
    }
    let cell_1_write = txn.write(&txn_id, &cell_1).unwrap().unwrap();
    let cell_1_r_res = txn.read(&txn_id, &cell_1.id()).unwrap().unwrap();
    match cell_1_r_res {
        TxnExecResult::Accepted(cell) => {
            assert_eq!(cell.id(), cell_1.id());
            assert_eq!(cell.data.Map().unwrap().get("id").unwrap().I64().unwrap(), 100);
            assert_eq!(cell.data.Map().unwrap().get("name").unwrap().String().unwrap(), "Jack");
            assert_eq!(cell.data.Map().unwrap().get("score").unwrap().U64().unwrap(), 70);
        },
        _ => {panic!("read cell 1 not accepted {:?}", cell_1_r_res)}
    }
    data_map.insert(String::from("score"), Value::U64(90));
    let cell_1_w2 = Cell::new(schema.id, &cell_1.id(), data_map.clone());
    let cell_1_w_res = txn.update(&txn_id, &cell_1_w2).unwrap().unwrap();
    match cell_1_w_res {
        TxnExecResult::Accepted(()) => {}
        _ => {panic!("Wrong feedback {:?}", cell_1_w_res)}
    }
    assert!(server.chunks.read_cell(&cell_1.id()).is_err()); // isolation test
    assert_eq!(txn.prepare(&txn_id).unwrap().unwrap(), TMPrepareResult::Success);
    assert_eq!(txn.commit(&txn_id).unwrap().unwrap(), EndResult::Success);
    let cell_r2 = server.chunks.read_cell(&cell_1.id()).unwrap();
    assert_eq!(cell_r2.id(), cell_1.id());
    assert_eq!(cell_r2.data.Map().unwrap().get("id").unwrap().I64().unwrap(), 100);
    assert_eq!(cell_r2.data.Map().unwrap().get("name").unwrap().String().unwrap(), "Jack");
    assert_eq!(cell_r2.data.Map().unwrap().get("score").unwrap().U64().unwrap(), 90);
}

#[test]
pub fn multi_transaction() {
    let server_addr = String::from("127.0.0.1:5202");
    let server = NebServer::new(ServerOptions {
        chunk_count: 1,
        memory_size: 16 * 1024 * 1024,
        standalone: false,
        is_meta: true,
        meta_members: vec!(server_addr.clone()),
        address: server_addr.clone(),
        backup_storage: None,
        meta_storage: None,
        group_name: String::from("test"),
    }).unwrap();
    let mut schema = Schema {
        id: 1,
        name: String::from("test"),
        key_field: None,
        fields: default_fields()
    };
    server.meta.schemas.new_schema(&mut schema);
    let txn = transactions::new_client(&server_addr).unwrap();
    let txn_1_id = txn.begin().unwrap().unwrap();
    let txn_2_id = txn.begin().unwrap().unwrap();
    let mut data_map_1 = Map::<String, Value>::new();
    data_map_1.insert(String::from("id"), Value::I64(100));
    data_map_1.insert(String::from("score"), Value::U64(70));
    data_map_1.insert(String::from("name"), Value::String(String::from("Jack")));
    let cell_1 = Cell::new(schema.id, &Id::rand(), data_map_1.clone());
    let cell_1_t1_write = txn.update(&txn_1_id, &cell_1).unwrap().unwrap();
    let data_map_2 = data_map_1.clone();
    data_map_1.insert(String::from("score"), Value::U64(90));
    let cell_2 = Cell::new(schema.id, &cell_1.id(), data_map_2.clone());
    let cell_1_t2_write = txn.write(&txn_2_id, &cell_2).unwrap().unwrap();
    txn.prepare(&txn_2_id).unwrap().unwrap();
    txn.commit(&txn_2_id).unwrap().unwrap();
    assert_eq!(txn.prepare(&txn_1_id).unwrap().unwrap(), TMPrepareResult::Success);
    assert_eq!(txn.commit(&txn_1_id).unwrap().unwrap(), EndResult::Success);
    ///////////////// PHASE 2 //////////////////
    let txn_1_id = txn.begin().unwrap().unwrap();
    let txn_2_id = txn.begin().unwrap().unwrap();
    match txn.read(&txn_2_id, &cell_1.id()).unwrap().unwrap() {
        TxnExecResult::Accepted(_) => {},
        _ => {panic!("Cannot read cell 1 for txn 2");}
    }
    txn.update(&txn_1_id, &cell_1).unwrap().unwrap();
    assert_eq!(txn.prepare(&txn_1_id).unwrap().unwrap(),
    TMPrepareResult::DMPrepareError(DMPrepareResult::NotRealizable));
    assert_eq!(txn.commit(&txn_1_id).unwrap().err().unwrap(), TMError::TransactionNotFound);
}<|endoftext|>


<commit_before>#![no_std]
#![allow(dead_code)]

#[macro_use]
extern crate derive_more;

#[derive(
    AddAssign,
    MulAssign,
    Add,
    Mul,
    Not,
    Index,
    Display,
    FromStr,
    Into,
    IntoRef,
    IntoRefMut,
    From,
    IndexMut,
    Sum,
    DerefToInner,
    DerefMutToInner,
    Constructor
)]
struct MyInts(u64);

#[derive(Iterator)]
struct MyVec<'a>(::core::slice::Iter<'a, i32>);

#[derive(
    From,
    FromStr,
    Display,
    Index,
    Not,
    Add,
    Mul,
    Sum,
    IndexMut,
    AddAssign,
    DerefToInner,
    DerefMutToInner,
    IntoIterator,
    IntoIteratorRef,
    IntoIteratorRefMut,
    Constructor
)]
struct Wrapped<T: Clone>(T);

#[derive(Iterator, Deref, DerefMut)]
struct Wrapped2<T: Clone>(T);

#[derive(From, Not, Add, Mul, AddAssign, Constructor, Sum)]
struct WrappedDouble<T: Clone, U: Clone>(T, U);

#[derive(Add, Not, TryInto)]
enum MixedInts {
    SmallInt(i32),
    BigInt(i64),
    TwoSmallInts(i32, i32),
    NamedSmallInts { x: i32, y: i32 },
    UnsignedOne(u32),
    UnsignedTwo(u32),
}

#[derive(Not, Add)]
enum EnumWithUnit {
    SmallInt(i32),
    Unit,
}
<commit_msg>Test TryIntoRef(Mut) in no_std test<commit_after>#![no_std]
#![allow(dead_code)]

#[macro_use]
extern crate derive_more;

#[derive(
    AddAssign,
    MulAssign,
    Add,
    Mul,
    Not,
    Index,
    Display,
    FromStr,
    Into,
    IntoRef,
    IntoRefMut,
    From,
    IndexMut,
    Sum,
    DerefToInner,
    DerefMutToInner,
    Constructor
)]
struct MyInts(u64);

#[derive(Iterator)]
struct MyVec<'a>(::core::slice::Iter<'a, i32>);

#[derive(
    From,
    FromStr,
    Display,
    Index,
    Not,
    Add,
    Mul,
    Sum,
    IndexMut,
    AddAssign,
    DerefToInner,
    DerefMutToInner,
    IntoIterator,
    IntoIteratorRef,
    IntoIteratorRefMut,
    Constructor
)]
struct Wrapped<T: Clone>(T);

#[derive(Iterator, Deref, DerefMut)]
struct Wrapped2<T: Clone>(T);

#[derive(From, Not, Add, Mul, AddAssign, Constructor, Sum)]
struct WrappedDouble<T: Clone, U: Clone>(T, U);

#[derive(Add, Not, TryInto, TryIntoRef, TryIntoRefMut)]
enum MixedInts {
    SmallInt(i32),
    BigInt(i64),
    TwoSmallInts(i32, i32),
    NamedSmallInts { x: i32, y: i32 },
    UnsignedOne(u32),
    UnsignedTwo(u32),
}

#[derive(Not, Add)]
enum EnumWithUnit {
    SmallInt(i32),
    Unit,
}
<|endoftext|>


<commit_before>#[cfg(feature = "ndarray_volumes")]
extern crate ndarray;
#[cfg(feature = "ndarray_volumes")]
extern crate nifti;
#[cfg(feature = "ndarray_volumes")]
extern crate tempfile;

#[cfg(feature = "ndarray_volumes")]
mod tests {
    use std::{
        fs,
        ops::{Add, Mul},
        path::{Path, PathBuf},
    };

    use ndarray::{s, Array, Array2, Axis, Dimension, IxDyn, ShapeBuilder};
    use tempfile::tempdir;

    use nifti::{
        header::{MAGIC_CODE_NI1, MAGIC_CODE_NIP1},
        object::NiftiObject,
        writer::{write_nifti, write_rgb_nifti},
        InMemNiftiObject, IntoNdArray, NiftiHeader, NiftiType,
    };

    fn get_temporary_path(ext: &str) -> PathBuf {
        let dir = tempdir().unwrap();
        let mut path = dir.into_path();
        if !ext.is_empty() {
            path.push(ext);
        }
        path
    }

    pub fn generate_nifti_header(
        dim: [u16; 8],
        scl_slope: f32,
        scl_inter: f32,
        datatype: NiftiType,
    ) -> NiftiHeader {
        NiftiHeader {
            dim,
            datatype: datatype as i16,
            bitpix: (datatype.size_of() * 8) as i16,
            magic: *MAGIC_CODE_NIP1,
            scl_slope,
            scl_inter,
            ..NiftiHeader::default()
        }
    }

    fn read_as_ndarray<P, D>(path: P) -> (NiftiHeader, Array<f32, D>)
    where
        P: AsRef<Path>,
        D: Dimension,
    {
        let nifti_object = InMemNiftiObject::from_file(path).expect("Nifti file is unreadable.");
        let header = nifti_object.header().clone();
        let volume = nifti_object.into_volume();
        let dyn_data = volume.into_ndarray().unwrap();
        (header, dyn_data.into_dimensionality::<D>().unwrap())
    }

    fn test_write_read(arr: &Array<f32, IxDyn>, path: &str) {
        let path = get_temporary_path(path);
        let mut dim = [1; 8];
        dim[0] = arr.ndim() as u16;
        for (i, s) in arr.shape().iter().enumerate() {
            dim[i + 1] = *s as u16;
        }
        let header = generate_nifti_header(dim, 1.0, 0.0, NiftiType::Float32);
        write_nifti(&path, &arr, Some(&header)).unwrap();

        let read_nifti: Array2<f32> = read_as_ndarray(path).1;
        assert!(read_nifti.all_close(&arr, 1e-10));
    }

    fn f_order_array() -> Array<f32, IxDyn> {
        let dim = vec![4, 4];
        let vec = (0..16).map(|x| x as f32).collect();
        Array::from_shape_vec(IxDyn(&dim).f(), vec).unwrap()
    }

    fn c_order_array() -> Array<f32, IxDyn> {
        let dim = vec![4, 4];
        let vec = (0..16).map(|x| x as f32).collect();
        Array::from_shape_vec(IxDyn(&dim), vec).unwrap()
    }

    #[test]
    fn fortran_writing() {
        // Test .nii
        let arr = f_order_array();
        test_write_read(&arr, "test.nii");
        let mut arr = f_order_array();
        arr.invert_axis(Axis(1));
        test_write_read(&arr, "test_non_contiguous.nii");

        // Test .nii.gz
        let arr = f_order_array();
        test_write_read(&arr, "test.nii.gz");
        let mut arr = f_order_array();
        arr.invert_axis(Axis(1));
        test_write_read(&arr, "test_non_contiguous.nii.gz");
    }

    #[test]
    fn c_writing() {
        // Test .nii
        let arr = c_order_array();
        test_write_read(&arr, "test.nii");

        let mut arr = c_order_array();
        arr.invert_axis(Axis(1));
        test_write_read(&arr, "test_non_contiguous.nii");

        // Test .nii.gz
        let arr = c_order_array();
        test_write_read(&arr, "test.nii.gz");

        let mut arr = c_order_array();
        arr.invert_axis(Axis(1));
        test_write_read(&arr, "test_non_contiguous.nii.gz");
    }

    #[test]
    fn header_slope_inter() {
        let arr = f_order_array();
        let slope = 2.2;
        let inter = 101.1;

        let path = get_temporary_path("test_slope_inter.nii");
        let mut dim = [1; 8];
        dim[0] = arr.ndim() as u16;
        for (i, s) in arr.shape().iter().enumerate() {
            dim[i + 1] = *s as u16;
        }
        let header = generate_nifti_header(dim, slope, inter, NiftiType::Float32);
        let transformed_data = arr.mul(slope).add(inter);
        write_nifti(&path, &transformed_data, Some(&header)).unwrap();

        let read_nifti: Array2<f32> = read_as_ndarray(path).1;
        assert!(read_nifti.all_close(&transformed_data, 1e-10));
    }

    #[test]
    fn write_hdr_standard() {
        let mut data = Array::zeros((10, 11, 12));
        data[(5, 0, 0)] = 1.0;
        data[(6, 0, 0)] = 2.0;

        for fname in &["3d.hdr", "3d.hdr.gz"] {
            let path = get_temporary_path(fname);
            write_nifti(&path, &data, None).unwrap();
            let data_read = read_as_ndarray(path).1;
            assert_eq!(data, data_read);
        }
    }

    #[test]
    fn write_non_contiguous() {
        let mut data = Array::from_elem((3, 4, 11), 1.5);
        data.slice_mut(s![.., .., ..;2]).fill(42.0);

        let path = get_temporary_path("non_contiguous_0.nii.gz");
        write_nifti(&path, &data.slice(s![.., .., ..;2]), None).unwrap();
        assert_eq!(read_as_ndarray(path).1, Array::from_elem((3, 4, 6), 42.0));

        let path = get_temporary_path("non_contiguous_1.nii.gz");
        write_nifti(&path, &data.slice(s![.., .., 1..;2]), None).unwrap();
        assert_eq!(read_as_ndarray(path).1, Array::from_elem((3, 4, 5), 1.5));
    }

    #[test]
    fn write_wrong_description() {
        let dim = [3, 3, 4, 5, 1, 1, 1, 1];
        let mut header = generate_nifti_header(dim, 1.0, 0.0, NiftiType::Float32);
        let path = get_temporary_path("error_description.nii");
        let data = Array::from_elem((3, 4, 5), 1.5);

        // Manual descrip. The original header won't be "repaired", but the written description
        // should be right. To compare the header, we must fix it ourselves.
        let v = "äbcdé".as_bytes();
        header.descrip = v.to_vec();
        write_nifti(&path, &data, Some(&header)).unwrap();
        let (new_header, new_data) = read_as_ndarray(&path);
        header.set_description(v).unwrap(); // Manual fix
        assert_eq!(new_header, header);
        assert_eq!(new_data, data);

        // set_description
        header.set_description("ひらがな".as_bytes()).unwrap();
        write_nifti(&path, &data, Some(&header)).unwrap();
        let (new_header, new_data) = read_as_ndarray(&path);
        assert_eq!(new_header, header);
        assert_eq!(new_data, data);

        // set_description_str
        header.set_description_str("русский").unwrap();
        write_nifti(&path, &data, Some(&header)).unwrap();
        let (new_header, new_data) = read_as_ndarray(&path);
        assert_eq!(new_header, header);
        assert_eq!(new_data, data);
    }

    #[test]
    fn write_descrip_panic() {
        let dim = [3, 3, 4, 5, 1, 1, 1, 1];
        let mut header = generate_nifti_header(dim, 1.0, 0.0, NiftiType::Float32);
        header.descrip = (0..84).into_iter().collect();
        let path = get_temporary_path("error_description.nii");
        let data = Array::from_elem((3, 4, 5), 1.5);
        assert!(write_nifti(&path, &data, Some(&header)).is_err());
    }

    #[test]
    fn write_set_description_panic() {
        let dim = [3, 3, 4, 5, 1, 1, 1, 1];
        let mut header = generate_nifti_header(dim, 1.0, 0.0, NiftiType::Float32);
        assert!(header
            .set_description((0..81).into_iter().collect::<Vec<_>>())
            .is_err());
    }

    #[test]
    fn write_set_description_str_panic() {
        let dim = [3, 3, 4, 5, 1, 1, 1, 1];
        let mut header = generate_nifti_header(dim, 1.0, 0.0, NiftiType::Float32);
        let description: String = std::iter::repeat('é').take(41).collect();
        assert!(header.set_description_str(description).is_err());
    }

    #[test]
    fn write_3d_rgb_hdr() {
        let mut data = Array::from_elem((3, 3, 3), [0u8, 0u8, 0u8]);
        data[(0, 0, 0)] = [55, 55, 0];
        data[(0, 0, 1)] = [55, 0, 55];
        data[(0, 1, 0)] = [0, 55, 55];

        let header_path = get_temporary_path("3d.hdr");
        let data_path = header_path.with_extension("img");
        write_rgb_nifti(&header_path, &data, None).unwrap();

        // Until we are able to read RGB images, we simply compare the bytes of the newly created
        // image to the bytes of the prepared 3D RGB image in ressources/rgb/. However, we need to
        // set the bytes of vox_offset to 0.0 and of magic to MAGIC_CODE_NI1. The data bytes should
        // be identical though.
        let mut gt_bytes = fs::read("resources/rgb/3D.nii").unwrap();
        for i in 110..114 {
            gt_bytes[i] = 0;
        }
        for i in 0..4 {
            gt_bytes[344 + i] = MAGIC_CODE_NI1[i];
        }
        assert_eq!(fs::read(header_path).unwrap(), &gt_bytes[..352]);
        assert_eq!(fs::read(data_path).unwrap(), &gt_bytes[352..]);
    }

    #[test]
    fn write_3d_rgb() {
        let mut data = Array::from_elem((3, 3, 3), [0u8, 0u8, 0u8]);
        data[(0, 0, 0)] = [55, 55, 0];
        data[(0, 0, 1)] = [55, 0, 55];
        data[(0, 1, 0)] = [0, 55, 55];

        let path = get_temporary_path("rgb.nii");
        write_rgb_nifti(&path, &data, None).unwrap();

        // Until we are able to read RGB images, we simply compare the bytes of the newly created
        // image to the bytes of the prepared 3D RGB image in ressources/rgb/.
        assert_eq!(
            fs::read(path).unwrap(),
            fs::read("resources/rgb/3D.nii").unwrap()
        );
    }

    #[test]
    fn write_4d_rgb() {
        let mut data = Array::from_elem((3, 3, 3, 2), [0u8, 0u8, 0u8]);
        data[(0, 0, 0, 0)] = [55, 55, 0];
        data[(0, 0, 1, 0)] = [55, 0, 55];
        data[(0, 1, 0, 0)] = [0, 55, 55];
        data[(0, 0, 0, 1)] = [55, 55, 0];
        data[(0, 1, 0, 1)] = [55, 0, 55];
        data[(1, 0, 0, 1)] = [0, 55, 55];

        let path = get_temporary_path("rgb.nii");
        write_rgb_nifti(&path, &data, None).unwrap();

        // Until we are able to read RGB images, we simply compare the bytes of the newly created
        // image to the bytes of the prepared 4D RGB image in ressources/rgb/.
        assert_eq!(
            fs::read(path).unwrap(),
            fs::read("resources/rgb/4D.nii").unwrap()
        );
    }
}
<commit_msg>Force RGB tests to write in little endian<commit_after>#[cfg(feature = "ndarray_volumes")]
extern crate ndarray;
#[cfg(feature = "ndarray_volumes")]
extern crate nifti;
#[cfg(feature = "ndarray_volumes")]
extern crate tempfile;

#[cfg(feature = "ndarray_volumes")]
mod tests {
    use std::{
        fs,
        ops::{Add, Mul},
        path::{Path, PathBuf},
    };

    use ndarray::{s, Array, Array2, Axis, Dimension, IxDyn, ShapeBuilder};
    use tempfile::tempdir;

    use nifti::{
        header::{MAGIC_CODE_NI1, MAGIC_CODE_NIP1},
        object::NiftiObject,
        writer::{write_nifti, write_rgb_nifti},
        Endianness, InMemNiftiObject, IntoNdArray, NiftiHeader, NiftiType,
    };

    fn get_temporary_path(ext: &str) -> PathBuf {
        let dir = tempdir().unwrap();
        let mut path = dir.into_path();
        if !ext.is_empty() {
            path.push(ext);
        }
        path
    }

    pub fn generate_nifti_header(
        dim: [u16; 8],
        scl_slope: f32,
        scl_inter: f32,
        datatype: NiftiType,
    ) -> NiftiHeader {
        NiftiHeader {
            dim,
            datatype: datatype as i16,
            bitpix: (datatype.size_of() * 8) as i16,
            magic: *MAGIC_CODE_NIP1,
            scl_slope,
            scl_inter,
            ..NiftiHeader::default()
        }
    }

    fn read_as_ndarray<P, D>(path: P) -> (NiftiHeader, Array<f32, D>)
    where
        P: AsRef<Path>,
        D: Dimension,
    {
        let nifti_object = InMemNiftiObject::from_file(path).expect("Nifti file is unreadable.");
        let header = nifti_object.header().clone();
        let volume = nifti_object.into_volume();
        let dyn_data = volume.into_ndarray().unwrap();
        (header, dyn_data.into_dimensionality::<D>().unwrap())
    }

    fn test_write_read(arr: &Array<f32, IxDyn>, path: &str) {
        let path = get_temporary_path(path);
        let mut dim = [1; 8];
        dim[0] = arr.ndim() as u16;
        for (i, s) in arr.shape().iter().enumerate() {
            dim[i + 1] = *s as u16;
        }
        let header = generate_nifti_header(dim, 1.0, 0.0, NiftiType::Float32);
        write_nifti(&path, &arr, Some(&header)).unwrap();

        let read_nifti: Array2<f32> = read_as_ndarray(path).1;
        assert!(read_nifti.all_close(&arr, 1e-10));
    }

    fn f_order_array() -> Array<f32, IxDyn> {
        let dim = vec![4, 4];
        let vec = (0..16).map(|x| x as f32).collect();
        Array::from_shape_vec(IxDyn(&dim).f(), vec).unwrap()
    }

    fn c_order_array() -> Array<f32, IxDyn> {
        let dim = vec![4, 4];
        let vec = (0..16).map(|x| x as f32).collect();
        Array::from_shape_vec(IxDyn(&dim), vec).unwrap()
    }

    #[test]
    fn fortran_writing() {
        // Test .nii
        let arr = f_order_array();
        test_write_read(&arr, "test.nii");
        let mut arr = f_order_array();
        arr.invert_axis(Axis(1));
        test_write_read(&arr, "test_non_contiguous.nii");

        // Test .nii.gz
        let arr = f_order_array();
        test_write_read(&arr, "test.nii.gz");
        let mut arr = f_order_array();
        arr.invert_axis(Axis(1));
        test_write_read(&arr, "test_non_contiguous.nii.gz");
    }

    #[test]
    fn c_writing() {
        // Test .nii
        let arr = c_order_array();
        test_write_read(&arr, "test.nii");

        let mut arr = c_order_array();
        arr.invert_axis(Axis(1));
        test_write_read(&arr, "test_non_contiguous.nii");

        // Test .nii.gz
        let arr = c_order_array();
        test_write_read(&arr, "test.nii.gz");

        let mut arr = c_order_array();
        arr.invert_axis(Axis(1));
        test_write_read(&arr, "test_non_contiguous.nii.gz");
    }

    #[test]
    fn header_slope_inter() {
        let arr = f_order_array();
        let slope = 2.2;
        let inter = 101.1;

        let path = get_temporary_path("test_slope_inter.nii");
        let mut dim = [1; 8];
        dim[0] = arr.ndim() as u16;
        for (i, s) in arr.shape().iter().enumerate() {
            dim[i + 1] = *s as u16;
        }
        let header = generate_nifti_header(dim, slope, inter, NiftiType::Float32);
        let transformed_data = arr.mul(slope).add(inter);
        write_nifti(&path, &transformed_data, Some(&header)).unwrap();

        let read_nifti: Array2<f32> = read_as_ndarray(path).1;
        assert!(read_nifti.all_close(&transformed_data, 1e-10));
    }

    #[test]
    fn write_hdr_standard() {
        let mut data = Array::zeros((10, 11, 12));
        data[(5, 0, 0)] = 1.0;
        data[(6, 0, 0)] = 2.0;

        for fname in &["3d.hdr", "3d.hdr.gz"] {
            let path = get_temporary_path(fname);
            write_nifti(&path, &data, None).unwrap();
            let data_read = read_as_ndarray(path).1;
            assert_eq!(data, data_read);
        }
    }

    #[test]
    fn write_non_contiguous() {
        let mut data = Array::from_elem((3, 4, 11), 1.5);
        data.slice_mut(s![.., .., ..;2]).fill(42.0);

        let path = get_temporary_path("non_contiguous_0.nii.gz");
        write_nifti(&path, &data.slice(s![.., .., ..;2]), None).unwrap();
        assert_eq!(read_as_ndarray(path).1, Array::from_elem((3, 4, 6), 42.0));

        let path = get_temporary_path("non_contiguous_1.nii.gz");
        write_nifti(&path, &data.slice(s![.., .., 1..;2]), None).unwrap();
        assert_eq!(read_as_ndarray(path).1, Array::from_elem((3, 4, 5), 1.5));
    }

    #[test]
    fn write_wrong_description() {
        let dim = [3, 3, 4, 5, 1, 1, 1, 1];
        let mut header = generate_nifti_header(dim, 1.0, 0.0, NiftiType::Float32);
        let path = get_temporary_path("error_description.nii");
        let data = Array::from_elem((3, 4, 5), 1.5);

        // Manual descrip. The original header won't be "repaired", but the written description
        // should be right. To compare the header, we must fix it ourselves.
        let v = "äbcdé".as_bytes();
        header.descrip = v.to_vec();
        write_nifti(&path, &data, Some(&header)).unwrap();
        let (new_header, new_data) = read_as_ndarray(&path);
        header.set_description(v).unwrap(); // Manual fix
        assert_eq!(new_header, header);
        assert_eq!(new_data, data);

        // set_description
        header.set_description("ひらがな".as_bytes()).unwrap();
        write_nifti(&path, &data, Some(&header)).unwrap();
        let (new_header, new_data) = read_as_ndarray(&path);
        assert_eq!(new_header, header);
        assert_eq!(new_data, data);

        // set_description_str
        header.set_description_str("русский").unwrap();
        write_nifti(&path, &data, Some(&header)).unwrap();
        let (new_header, new_data) = read_as_ndarray(&path);
        assert_eq!(new_header, header);
        assert_eq!(new_data, data);
    }

    #[test]
    fn write_descrip_panic() {
        let dim = [3, 3, 4, 5, 1, 1, 1, 1];
        let mut header = generate_nifti_header(dim, 1.0, 0.0, NiftiType::Float32);
        header.descrip = (0..84).into_iter().collect();
        let path = get_temporary_path("error_description.nii");
        let data = Array::from_elem((3, 4, 5), 1.5);
        assert!(write_nifti(&path, &data, Some(&header)).is_err());
    }

    #[test]
    fn write_set_description_panic() {
        let dim = [3, 3, 4, 5, 1, 1, 1, 1];
        let mut header = generate_nifti_header(dim, 1.0, 0.0, NiftiType::Float32);
        assert!(header
            .set_description((0..81).into_iter().collect::<Vec<_>>())
            .is_err());
    }

    #[test]
    fn write_set_description_str_panic() {
        let dim = [3, 3, 4, 5, 1, 1, 1, 1];
        let mut header = generate_nifti_header(dim, 1.0, 0.0, NiftiType::Float32);
        let description: String = std::iter::repeat('é').take(41).collect();
        assert!(header.set_description_str(description).is_err());
    }

    #[test]
    fn write_3d_rgb_hdr() {
        let mut data = Array::from_elem((3, 3, 3), [0u8, 0u8, 0u8]);
        data[(0, 0, 0)] = [55, 55, 0];
        data[(0, 0, 1)] = [55, 0, 55];
        data[(0, 1, 0)] = [0, 55, 55];

        let header_path = get_temporary_path("3d.hdr");
        let data_path = header_path.with_extension("img");
        let header = NiftiHeader {
            datatype: NiftiType::Rgb24 as i16,
            pixdim: [1.0; 8],
            sform_code: 2,
            srow_x: [1.0, 0.0, 0.0, 0.0],
            srow_y: [0.0, 1.0, 0.0, 0.0],
            srow_z: [0.0, 0.0, 1.0, 0.0],
            endianness: Endianness::Little,
            .. NiftiHeader::default()
        };
        write_rgb_nifti(&header_path, &data, Some(&header)).unwrap();

        // Until we are able to read RGB images, we simply compare the bytes of the newly created
        // image to the bytes of the prepared 3D RGB image in ressources/rgb/. However, we need to
        // set the bytes of vox_offset to 0.0 and of magic to MAGIC_CODE_NI1. The data bytes should
        // be identical though.
        let mut gt_bytes = fs::read("resources/rgb/3D.nii").unwrap();
        for i in 110..114 {
            gt_bytes[i] = 0;
        }
        for i in 0..4 {
            gt_bytes[344 + i] = MAGIC_CODE_NI1[i];
        }
        assert_eq!(fs::read(header_path).unwrap(), &gt_bytes[..352]);
        assert_eq!(fs::read(data_path).unwrap(), &gt_bytes[352..]);
    }

    #[test]
    fn write_3d_rgb() {
        let mut data = Array::from_elem((3, 3, 3), [0u8, 0u8, 0u8]);
        data[(0, 0, 0)] = [55, 55, 0];
        data[(0, 0, 1)] = [55, 0, 55];
        data[(0, 1, 0)] = [0, 55, 55];

        let path = get_temporary_path("rgb.nii");
        let header = NiftiHeader {
            datatype: NiftiType::Rgb24 as i16,
            pixdim: [1.0; 8],
            sform_code: 2,
            srow_x: [1.0, 0.0, 0.0, 0.0],
            srow_y: [0.0, 1.0, 0.0, 0.0],
            srow_z: [0.0, 0.0, 1.0, 0.0],
            endianness: Endianness::Little,
            .. NiftiHeader::default()
        };
        write_rgb_nifti(&path, &data, Some(&header)).unwrap();

        // Until we are able to read RGB images, we simply compare the bytes of the newly created
        // image to the bytes of the prepared 3D RGB image in ressources/rgb/.
        assert_eq!(
            fs::read(path).unwrap(),
            fs::read("resources/rgb/3D.nii").unwrap()
        );
    }

    #[test]
    fn write_4d_rgb() {
        let mut data = Array::from_elem((3, 3, 3, 2), [0u8, 0u8, 0u8]);
        data[(0, 0, 0, 0)] = [55, 55, 0];
        data[(0, 0, 1, 0)] = [55, 0, 55];
        data[(0, 1, 0, 0)] = [0, 55, 55];
        data[(0, 0, 0, 1)] = [55, 55, 0];
        data[(0, 1, 0, 1)] = [55, 0, 55];
        data[(1, 0, 0, 1)] = [0, 55, 55];

        let path = get_temporary_path("rgb.nii");
        let header = NiftiHeader {
            datatype: NiftiType::Rgb24 as i16,
            pixdim: [1.0; 8],
            sform_code: 2,
            srow_x: [1.0, 0.0, 0.0, 0.0],
            srow_y: [0.0, 1.0, 0.0, 0.0],
            srow_z: [0.0, 0.0, 1.0, 0.0],
            endianness: Endianness::Little,
            .. NiftiHeader::default()
        };
        write_rgb_nifti(&path, &data, Some(&header)).unwrap();

        // Until we are able to read RGB images, we simply compare the bytes of the newly created
        // image to the bytes of the prepared 4D RGB image in ressources/rgb/.
        assert_eq!(
            fs::read(path).unwrap(),
            fs::read("resources/rgb/4D.nii").unwrap()
        );
    }
}
<|endoftext|>


<commit_before>
use std::collections::BTreeMap;
use std::error;
use std::fmt;
use std::mem;

use twelve_bit::u12::*;

use assembler::data_range::DataRange;
use assembler::directive::Directive;
use assembler::instruction::Instruction;
use assembler::parser::Node;
use assembler::source_file_location::SourceFileLocation;

// MARK: - Constants

lazy_static! {
  static ref BYTES_PER_INSTRUCTION: U12 = u12![2];
}

// MARK: - Semantic Error

#[derive(PartialEq, Eq, Clone, Debug)]
pub enum SemanticError<'a> {
  /// Unable to process an invalid directive.
  DirectiveInvalid(SourceFileLocation<'a>, String),
  /// The directive processed exceeds the size limit of 4096 bytes.
  DirectiveTooLarge(SourceFileLocation<'a>, Directive),
  /// The directive at the specified location would cause the address counter to wrap.
  DirectiveWouldOverflow(SourceFileLocation<'a>, Directive),
  /// The instruction at the specified location would cause the addresss counter to wrap.
  InstructionWouldOverlow(SourceFileLocation<'a>),
  /// The label specified has already been defined.
  RedefinitionOfLabel(SourceFileLocation<'a>, String),
  /// Unable to assemble the specified instruction.
  AssemblyFailed(SourceFileLocation<'a>, String)
}

impl<'a> error::Error for SemanticError<'a> {
  /// Returns a string slice with a general description of a semantic error.
  /// No specific information is contained. To obtain a printable representation,
  /// use the `fmt::Display` attribute.
  fn description(&self) -> &str {
    match self {
      &SemanticError::DirectiveInvalid(_,_)       => "Directive invalid",
      &SemanticError::DirectiveTooLarge(_,_)      => "Directive size exceeds 4096 bytes",
      &SemanticError::DirectiveWouldOverflow(_,_) => "Directive would cause the 12-bit address counter to overflow",
      &SemanticError::InstructionWouldOverlow(_)  => "Instruction would cause address counter to overflow $FFF",
      &SemanticError::RedefinitionOfLabel(_,_)    => "Attempted re-definition of label",
      &SemanticError::AssemblyFailed(_,_)         => "Unable to assemble instruction",
    }
  }
}

impl<'a> fmt::Display for SemanticError<'a> {
  /// Formats the receiver for display purposes. Incorporates specific information
  /// relating to this particular error instance where applicable.
  fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result {
    match self {
      &SemanticError::DirectiveInvalid(ref loc, ref reason) =>
        write!(f, "{}: error: {}", loc, reason),
      &SemanticError::DirectiveTooLarge(ref loc, ref directive) =>
        write!(f, "{}: error: Directive ({}) size {} exceeds 4096 bytes.", loc, directive, directive.size()),
      &SemanticError::DirectiveWouldOverflow(ref loc, ref directive) =>
        write!(f, "{}: error: Directive ({}) would cause the 12-bit address counter to overflow.", loc, directive),
      &SemanticError::RedefinitionOfLabel(ref loc, ref label) =>
        write!(f, "{}: error: Attempted re-definition of label '{}'.", loc, label),
      &SemanticError::InstructionWouldOverlow(ref loc) =>
        write!(f, "{}: error: {}.", loc, (self as &error::Error).description()),
      &SemanticError::AssemblyFailed(ref loc, ref reason) =>
        write!(f, "{}: error: {}.", loc, reason),
    }
  }
}

// MARK: - Pass 1: Define Labels

/**
 In a 2-pass assembler, the first pass is used to resolve the address of all labels
 declared in the assembly. This involves walking the ASL, computing the size of instructions
 and directives in the output stream and using that information to establish the label-address map.
 @param syntax_list The un-filtered syntax list.
 @return The map of labels to their corresponding addresses on success or a string describing
     the reason the first pass failed.
 */
fn define_labels<'a>(syntax_list: &Vec<Node<'a>>) -> Result<BTreeMap<&'a str, U12>, SemanticError<'a>> {
  let mut label_address_map = BTreeMap::new();
  let mut current_address = u12![0];

  // Define all labels and process '.org' directives.
  for node in syntax_list.iter() {
    match *node {
      Node::Directive(ref data) => {
        // Validate the directive and that the arguments match the identifier.
        let directive = try!(
          Directive::from_identifier_and_parameters(data.identifier, &data.arguments)
            .map_err(|reason| {
              SemanticError::DirectiveInvalid(data.location.clone(), reason)
            })
        );

        // Ensure the directive is not too large to emit.
        let directive_size: U12 = try!(
          directive
            .size()
            .failable_into()
            .ok_or(SemanticError::DirectiveTooLarge(data.location.clone(), directive.clone()))
        );

        // Offset the current address by the size of the directive being processed.
        current_address = try!(
          current_address
            .checked_add(directive_size)
            .ok_or(SemanticError::DirectiveWouldOverflow(data.location.clone(), directive.clone()))
        );

        // The origin directive changes the current address.
        if let Directive::Org(address) = directive {
          current_address = address;
        }
      }

      Node::Label(ref data) => {
        // Map the label to the current address and remove from the ASL.
        if label_address_map.contains_key(&data.identifier) {
          return Err(SemanticError::RedefinitionOfLabel(data.location.clone(), String::from(data.identifier)));
        } else {
          label_address_map.insert(data.identifier, current_address);
        }
      }

      Node::Instruction(ref data) => {
        // All Chip8 instructions are the same length.
        current_address = try!(
          current_address
            .checked_add(*BYTES_PER_INSTRUCTION)
            .ok_or(SemanticError::InstructionWouldOverlow(data.location.clone()))
        );
      }
    }
  }

  // Semantic analysis for directives and labels is complete.
  Ok(label_address_map)
}

// MARK: - Pass 2: Emit Bytes

fn emit_data_ranges<'a>(syntax_list: Vec<Node<'a>>, label_address_map: &BTreeMap<&'a str, U12>) -> Result<Vec<DataRange>, SemanticError<'a>> {
  let mut data_ranges = Vec::new();
  let mut current_range = DataRange::new(u12![0]);

  for node in syntax_list {
    match node {

      Node::Directive(data) => {
        match Directive::from_identifier_and_parameters(data.identifier, &data.arguments).unwrap() {

          // Process the origin directive to build a new data range.
          Directive::Org(address) => {
            let new_range = DataRange::new(address);
            let previous_range = mem::replace(&mut current_range, new_range);
            if previous_range.len() > 0 {
              data_ranges.push(previous_range);
            }
          }

          // Emit the directive into the range.
          directive @ _ => {
            let bytes = directive.into_vec();
            assert!(current_range.append(&bytes));
          }

        }
      }

      Node::Instruction(data) => {
        // Verify the semantics and append the instruction to the output buffer.
        match Instruction::from_mnemonic_and_parameters(data.mnemonic, &data.fields, label_address_map) {
          Ok(instruction) => {
            let bytes = instruction.into_vec();
            assert!(current_range.append(&bytes));
          }

          Err(reason) => {
            return Err(SemanticError::AssemblyFailed(data.location.clone(), reason));
          }
        }
      }

      Node::Label(_) => {
        // Processed in Pass 1.
      }

    }
  }

  // Push the last range into the list.
  if current_range.len() > 0 {
    data_ranges.push(current_range);
  }

  Ok(data_ranges)
}

// MARK: - Public API

/**
 Analyze the ASL for the assembly and convert it into an output byte stream.
 */
pub fn assemble<'a>(syntax_list: Vec<Node<'a>>) -> Result<Vec<DataRange>, String> {
  let label_address_map = try!(
    define_labels(&syntax_list).map_err(|error|
      format!("{}", error)
    )
  );

  emit_data_ranges(syntax_list, &label_address_map).map_err(|error|
    format!("{}", error)
  )
}

// MARK: - Tests

#[cfg(test)]
mod tests {

  use twelve_bit::u12::*;

  use assembler::parser::*;
  use assembler::source_file_location::SourceFileLocation;

  use super::define_labels;

  // MARK: - Helpers

  /// Creates a Label type node with associated data at location "-:seq:1-name.len()".
  fn make_label_node<'a>(seq: usize, name: &'a str) -> Node<'a> {
    Node::Label (
      LabelData {
        location: SourceFileLocation::new("-", seq, 1, name.len()),
        identifier: name
      }
    )
  }

  /// Creates a Directive type node with associated data at location "-:seq:2-name.len()+1".
  fn make_directive_node<'a>(seq: usize, name: &'a str, args: Vec<Literal<'a>>) -> Node<'a> {
    Node::Directive (
      DirectiveData {
        location: SourceFileLocation::new("-", seq, 2, name.len()),
        identifier: name,
        arguments: args
      }
    )
  }

  /// Creates an Instruction type node with associated data at location "-:seq:1-name.len()".
  fn make_instruction_node<'a>(seq: usize, name: &'a str, fields: Vec<InstructionField<'a>>) -> Node<'a> {
    Node::Instruction (
      InstructionData {
        location: SourceFileLocation::new("-", seq, 1, name.len()),
        mnemonic: name,
        fields: fields
      }
    )
  }

  // MARK: - Pass 1 Tests

  #[test]
  fn test_define_labels() {
    let program = vec![
      make_directive_node   (1, "org", vec![Literal::Numeric(0x100)]),
      make_label_node       (2, "label1"),
      make_directive_node   (3, "db", vec![Literal::Numeric(0xFF)]),
      make_label_node       (4, "label2"),
      make_instruction_node (5, "trap", vec![]),
      make_label_node       (6, "label3"),
    ];

    let result = define_labels(&program);

    // Assert that semantic analysis passed.
    if let Err(reason) = result {
      assert!(false, "Unexpected failure in assembler pass 1: {}", reason);
      return;
    }

    // Verify that the labels are defined as expected.
    let label_map = result.unwrap();
    assert_eq!(label_map.len(), 3);
    assert_eq!(label_map.get("label1").unwrap(), &(u12![0x100]));
    assert_eq!(label_map.get("label2").unwrap(), &(u12![0x101]));
    assert_eq!(label_map.get("label3").unwrap(), &(u12![0x103]));
  }

  #[test]
  fn test_define_labels_does_semantic_analysis_on_org() {
    let program = vec![
      make_directive_node(1, "org", vec![])
    ];

    let result = define_labels(&program);
    assert_eq!(format!("{}", result.unwrap_err()), "-:1:2-4: error: Incorrect number of parameters (0) for directive .org, expecting 1.");
  }

  #[test]
  fn test_define_labels_does_semantic_analysis_on_db() {
    let program = vec![
      make_directive_node(1, "db", vec![])
    ];

    let result = define_labels(&program);
    assert_eq!(format!("{}", result.unwrap_err()), "-:1:2-3: error: Incorrect number of parameters (0) for directive .db, expecting 1 or more.");
  }

  #[test]
  fn test_define_labels_fails_on_redefinition() {
    let program = vec![
      make_label_node(1, "L1"),
      make_label_node(2, "L1"),
    ];

    let result = define_labels(&program);
    assert_eq!(format!("{}", result.unwrap_err()), "-:2:1-2: error: Attempted re-definition of label 'L1'.");
  }

}
<commit_msg>Easier-to-understand assembly.<commit_after>
use std::collections::BTreeMap;
use std::error;
use std::fmt;
use std::mem;

use twelve_bit::u12::*;

use assembler::data_range::DataRange;
use assembler::directive::Directive;
use assembler::instruction::Instruction;
use assembler::parser::Node;
use assembler::source_file_location::SourceFileLocation;

// MARK: - Constants

lazy_static! {
  static ref BYTES_PER_INSTRUCTION: U12 = u12![2];
}

// MARK: - Semantic Error

#[derive(PartialEq, Eq, Clone, Debug)]
pub enum SemanticError<'a> {
  /// Unable to process an invalid directive.
  DirectiveInvalid(SourceFileLocation<'a>, String),
  /// The directive processed exceeds the size limit of 4096 bytes.
  DirectiveTooLarge(SourceFileLocation<'a>, Directive),
  /// The directive at the specified location would cause the address counter to wrap.
  DirectiveWouldOverflow(SourceFileLocation<'a>, Directive),
  /// The instruction at the specified location would cause the addresss counter to wrap.
  InstructionWouldOverlow(SourceFileLocation<'a>),
  /// The label specified has already been defined.
  RedefinitionOfLabel(SourceFileLocation<'a>, String),
  /// Unable to assemble the specified instruction.
  AssemblyFailed(SourceFileLocation<'a>, String)
}

impl<'a> error::Error for SemanticError<'a> {
  /// Returns a string slice with a general description of a semantic error.
  /// No specific information is contained. To obtain a printable representation,
  /// use the `fmt::Display` attribute.
  fn description(&self) -> &str {
    match self {
      &SemanticError::DirectiveInvalid(_,_)       => "Directive invalid",
      &SemanticError::DirectiveTooLarge(_,_)      => "Directive size exceeds 4096 bytes",
      &SemanticError::DirectiveWouldOverflow(_,_) => "Directive would cause the 12-bit address counter to overflow",
      &SemanticError::InstructionWouldOverlow(_)  => "Instruction would cause address counter to overflow $FFF",
      &SemanticError::RedefinitionOfLabel(_,_)    => "Attempted re-definition of label",
      &SemanticError::AssemblyFailed(_,_)         => "Unable to assemble instruction",
    }
  }
}

impl<'a> fmt::Display for SemanticError<'a> {
  /// Formats the receiver for display purposes. Incorporates specific information
  /// relating to this particular error instance where applicable.
  fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result {
    match self {
      &SemanticError::DirectiveInvalid(ref loc, ref reason) =>
        write!(f, "{}: error: {}", loc, reason),
      &SemanticError::DirectiveTooLarge(ref loc, ref directive) =>
        write!(f, "{}: error: Directive ({}) size {} exceeds 4096 bytes.", loc, directive, directive.size()),
      &SemanticError::DirectiveWouldOverflow(ref loc, ref directive) =>
        write!(f, "{}: error: Directive ({}) would cause the 12-bit address counter to overflow.", loc, directive),
      &SemanticError::RedefinitionOfLabel(ref loc, ref label) =>
        write!(f, "{}: error: Attempted re-definition of label '{}'.", loc, label),
      &SemanticError::InstructionWouldOverlow(ref loc) =>
        write!(f, "{}: error: {}.", loc, (self as &error::Error).description()),
      &SemanticError::AssemblyFailed(ref loc, ref reason) =>
        write!(f, "{}: error: {}.", loc, reason),
    }
  }
}

// MARK: - Pass 1: Define Labels

/**
 In a 2-pass assembler, the first pass is used to resolve the address of all labels
 declared in the assembly. This involves walking the ASL, computing the size of instructions
 and directives in the output stream and using that information to establish the label-address map.
 @param syntax_list The un-filtered syntax list.
 @return The map of labels to their corresponding addresses on success or a string describing
     the reason the first pass failed.
 */
fn define_labels<'a>(syntax_list: &Vec<Node<'a>>) -> Result<BTreeMap<&'a str, U12>, SemanticError<'a>> {
  let mut label_address_map = BTreeMap::new();
  let mut current_address = u12![0];

  // Define all labels and process '.org' directives.
  for node in syntax_list.iter() {
    match *node {
      Node::Directive(ref data) => {
        // Validate the directive and that the arguments match the identifier.
        let directive = try!(
          Directive::from_identifier_and_parameters(data.identifier, &data.arguments)
            .map_err(|reason| {
              SemanticError::DirectiveInvalid(data.location.clone(), reason)
            })
        );

        // Ensure the directive is not too large to emit.
        let directive_size: U12 = try!(
          directive
            .size()
            .failable_into()
            .ok_or(SemanticError::DirectiveTooLarge(data.location.clone(), directive.clone()))
        );

        // Offset the current address by the size of the directive being processed.
        current_address = try!(
          current_address
            .checked_add(directive_size)
            .ok_or(SemanticError::DirectiveWouldOverflow(data.location.clone(), directive.clone()))
        );

        // The origin directive changes the current address.
        if let Directive::Org(address) = directive {
          current_address = address;
        }
      }

      Node::Label(ref data) => {
        // Map the label to the current address and remove from the ASL.
        if label_address_map.contains_key(&data.identifier) {
          return Err(SemanticError::RedefinitionOfLabel(data.location.clone(), String::from(data.identifier)));
        } else {
          label_address_map.insert(data.identifier, current_address);
        }
      }

      Node::Instruction(ref data) => {
        // All Chip8 instructions are the same length.
        current_address = try!(
          current_address
            .checked_add(*BYTES_PER_INSTRUCTION)
            .ok_or(SemanticError::InstructionWouldOverlow(data.location.clone()))
        );
      }
    }
  }

  // Semantic analysis for directives and labels is complete.
  Ok(label_address_map)
}

// MARK: - Pass 2: Emit Bytes

fn emit_data_ranges<'a>(syntax_list: Vec<Node<'a>>, label_address_map: &BTreeMap<&'a str, U12>) -> Result<Vec<DataRange>, SemanticError<'a>> {
  let mut data_ranges = Vec::new();
  let mut current_range = DataRange::new(u12![0]);

  for node in syntax_list {
    match node {

      Node::Directive(data) => {
        match Directive::from_identifier_and_parameters(data.identifier, &data.arguments).unwrap() {

          // Process the origin directive to build a new data range.
          Directive::Org(address) => {
            let new_range = DataRange::new(address);
            let previous_range = mem::replace(&mut current_range, new_range);
            if previous_range.len() > 0 {
              data_ranges.push(previous_range);
            }
          }

          // Emit the directive into the range.
          directive @ _ => {
            let bytes = directive.into_vec();
            assert!(current_range.append(&bytes));
          }

        }
      }

      Node::Instruction(data) => {
        // Verify the semantics and append the instruction to the output buffer.
        let instruction = try!(
          Instruction::from_mnemonic_and_parameters(data.mnemonic, &data.fields, label_address_map)
            .map_err(|reason| {
              SemanticError::AssemblyFailed(data.location.clone(), reason)
            })
        );

        let bytes = instruction.into_vec();
        assert!(current_range.append(&bytes));
      }

      Node::Label(_) => {
        // Processed in Pass 1.
      }

    }
  }

  // Push the last range into the list.
  if current_range.len() > 0 {
    data_ranges.push(current_range);
  }

  Ok(data_ranges)
}

// MARK: - Public API

/**
 Analyze the ASL for the assembly and convert it into an output byte stream.
 */
pub fn assemble<'a>(syntax_list: Vec<Node<'a>>) -> Result<Vec<DataRange>, String> {
  let label_address_map = try!(
    define_labels(&syntax_list).map_err(|error|
      format!("{}", error)
    )
  );

  emit_data_ranges(syntax_list, &label_address_map).map_err(|error|
    format!("{}", error)
  )
}

// MARK: - Tests

#[cfg(test)]
mod tests {

  use twelve_bit::u12::*;

  use assembler::parser::*;
  use assembler::source_file_location::SourceFileLocation;

  use super::define_labels;

  // MARK: - Helpers

  /// Creates a Label type node with associated data at location "-:seq:1-name.len()".
  fn make_label_node<'a>(seq: usize, name: &'a str) -> Node<'a> {
    Node::Label (
      LabelData {
        location: SourceFileLocation::new("-", seq, 1, name.len()),
        identifier: name
      }
    )
  }

  /// Creates a Directive type node with associated data at location "-:seq:2-name.len()+1".
  fn make_directive_node<'a>(seq: usize, name: &'a str, args: Vec<Literal<'a>>) -> Node<'a> {
    Node::Directive (
      DirectiveData {
        location: SourceFileLocation::new("-", seq, 2, name.len()),
        identifier: name,
        arguments: args
      }
    )
  }

  /// Creates an Instruction type node with associated data at location "-:seq:1-name.len()".
  fn make_instruction_node<'a>(seq: usize, name: &'a str, fields: Vec<InstructionField<'a>>) -> Node<'a> {
    Node::Instruction (
      InstructionData {
        location: SourceFileLocation::new("-", seq, 1, name.len()),
        mnemonic: name,
        fields: fields
      }
    )
  }

  // MARK: - Pass 1 Tests

  #[test]
  fn test_define_labels() {
    let program = vec![
      make_directive_node   (1, "org", vec![Literal::Numeric(0x100)]),
      make_label_node       (2, "label1"),
      make_directive_node   (3, "db", vec![Literal::Numeric(0xFF)]),
      make_label_node       (4, "label2"),
      make_instruction_node (5, "trap", vec![]),
      make_label_node       (6, "label3"),
    ];

    let result = define_labels(&program);

    // Assert that semantic analysis passed.
    if let Err(reason) = result {
      assert!(false, "Unexpected failure in assembler pass 1: {}", reason);
      return;
    }

    // Verify that the labels are defined as expected.
    let label_map = result.unwrap();
    assert_eq!(label_map.len(), 3);
    assert_eq!(label_map.get("label1").unwrap(), &(u12![0x100]));
    assert_eq!(label_map.get("label2").unwrap(), &(u12![0x101]));
    assert_eq!(label_map.get("label3").unwrap(), &(u12![0x103]));
  }

  #[test]
  fn test_define_labels_does_semantic_analysis_on_org() {
    let program = vec![
      make_directive_node(1, "org", vec![])
    ];

    let result = define_labels(&program);
    assert_eq!(format!("{}", result.unwrap_err()), "-:1:2-4: error: Incorrect number of parameters (0) for directive .org, expecting 1.");
  }

  #[test]
  fn test_define_labels_does_semantic_analysis_on_db() {
    let program = vec![
      make_directive_node(1, "db", vec![])
    ];

    let result = define_labels(&program);
    assert_eq!(format!("{}", result.unwrap_err()), "-:1:2-3: error: Incorrect number of parameters (0) for directive .db, expecting 1 or more.");
  }

  #[test]
  fn test_define_labels_fails_on_redefinition() {
    let program = vec![
      make_label_node(1, "L1"),
      make_label_node(2, "L1"),
    ];

    let result = define_labels(&program);
    assert_eq!(format!("{}", result.unwrap_err()), "-:2:1-2: error: Attempted re-definition of label 'L1'.");
  }

}
<|endoftext|>


<commit_before>//! JSON Errors
//!
//! This module is centered around the `Error` and `ErrorCode` types, which represents all possible
//! `serde_json` errors.

use std::error;
use std::fmt;
use std::io;
use std::result;
use std::string::FromUtf8Error;

use serde::de;
use serde::ser;

/// The errors that can arise while parsing a JSON stream.
#[derive(Clone, PartialEq)]
pub enum ErrorCode {
    /// Catchall for syntax error messages
    Custom(String),

    /// Incorrect type from value
    InvalidType(de::Type),

    /// Incorrect value
    InvalidValue(String),

    /// Invalid length
    InvalidLength(usize),

    /// Unknown variant in an enum.
    UnknownVariant(String),

    /// Unknown field in struct.
    UnknownField(String),

    /// Struct is missing a field.
    MissingField(&'static str),

    /// EOF while parsing a list.
    EOFWhileParsingList,

    /// EOF while parsing an object.
    EOFWhileParsingObject,

    /// EOF while parsing a string.
    EOFWhileParsingString,

    /// EOF while parsing a JSON value.
    EOFWhileParsingValue,

    /// Expected this character to be a `':'`.
    ExpectedColon,

    /// Expected this character to be either a `','` or a `]`.
    ExpectedListCommaOrEnd,

    /// Expected this character to be either a `','` or a `}`.
    ExpectedObjectCommaOrEnd,

    /// Expected to parse either a `true`, `false`, or a `null`.
    ExpectedSomeIdent,

    /// Expected this character to start a JSON value.
    ExpectedSomeValue,

    /// Invalid hex escape code.
    InvalidEscape,

    /// Invalid number.
    InvalidNumber,

    /// Invalid unicode code point.
    InvalidUnicodeCodePoint,

    /// Object key is not a string.
    KeyMustBeAString,

    /// Lone leading surrogate in hex escape.
    LoneLeadingSurrogateInHexEscape,

    /// JSON has non-whitespace trailing characters after the value.
    TrailingCharacters,

    /// Unexpected end of hex excape.
    UnexpectedEndOfHexEscape,
}

impl fmt::Debug for ErrorCode {
    fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result {
        use std::fmt::Debug;

        match *self {
            ErrorCode::Custom(ref msg) => write!(f, "{}", msg),
            ErrorCode::InvalidType(ref ty) => write!(f, "invalid value type: {:?}", ty),
            ErrorCode::InvalidValue(ref msg) => write!(f, "invalid value: {}", msg),
            ErrorCode::InvalidLength(ref len) => write!(f, "invalid value length {}", len),
            ErrorCode::UnknownVariant(ref variant) => write!(f, "unknown variant \"{}\"", variant),
            ErrorCode::UnknownField(ref field) => write!(f, "unknown field \"{}\"", field),
            ErrorCode::MissingField(ref field) => write!(f, "missing field \"{}\"", field),
            ErrorCode::EOFWhileParsingList => "EOF while parsing a list".fmt(f),
            ErrorCode::EOFWhileParsingObject => "EOF while parsing an object".fmt(f),
            ErrorCode::EOFWhileParsingString => "EOF while parsing a string".fmt(f),
            ErrorCode::EOFWhileParsingValue => "EOF while parsing a value".fmt(f),
            ErrorCode::ExpectedColon => "expected `:`".fmt(f),
            ErrorCode::ExpectedListCommaOrEnd => "expected `,` or `]`".fmt(f),
            ErrorCode::ExpectedObjectCommaOrEnd => "expected `,` or `}`".fmt(f),
            ErrorCode::ExpectedSomeIdent => "expected ident".fmt(f),
            ErrorCode::ExpectedSomeValue => "expected value".fmt(f),
            ErrorCode::InvalidEscape => "invalid escape".fmt(f),
            ErrorCode::InvalidNumber => "invalid number".fmt(f),
            ErrorCode::InvalidUnicodeCodePoint => "invalid unicode code point".fmt(f),
            ErrorCode::KeyMustBeAString => "key must be a string".fmt(f),
            ErrorCode::LoneLeadingSurrogateInHexEscape => "lone leading surrogate in hex escape".fmt(f),
            ErrorCode::TrailingCharacters => "trailing characters".fmt(f),
            ErrorCode::UnexpectedEndOfHexEscape => "unexpected end of hex escape".fmt(f),
        }
    }
}

/// This type represents all possible errors that can occur when serializing or deserializing a
/// value into JSON.
#[derive(Debug)]
pub enum Error {
    /// The JSON value had some syntatic error.
    Syntax(ErrorCode, usize, usize),

    /// Some IO error occurred when serializing or deserializing a value.
    Io(io::Error),

    /// Some UTF8 error occurred while serializing or deserializing a value.
    FromUtf8(FromUtf8Error),
}

impl error::Error for Error {
    fn description(&self) -> &str {
        match *self {
            Error::Syntax(..) => "syntax error",
            Error::Io(ref error) => error::Error::description(error),
            Error::FromUtf8(ref error) => error.description(),
        }
    }

    fn cause(&self) -> Option<&error::Error> {
        match *self {
            Error::Io(ref error) => Some(error),
            Error::FromUtf8(ref error) => Some(error),
            _ => None,
        }
    }

}

impl fmt::Display for Error {
    fn fmt(&self, fmt: &mut fmt::Formatter) -> fmt::Result {
        match *self {
            Error::Syntax(ref code, line, col) => {
                write!(fmt, "{:?} at line {} column {}", code, line, col)
            }
            Error::Io(ref error) => fmt::Display::fmt(error, fmt),
            Error::FromUtf8(ref error) => fmt::Display::fmt(error, fmt),
        }
    }
}

impl From<io::Error> for Error {
    fn from(error: io::Error) -> Error {
        Error::Io(error)
    }
}

impl From<FromUtf8Error> for Error {
    fn from(error: FromUtf8Error) -> Error {
        Error::FromUtf8(error)
    }
}

impl From<de::value::Error> for Error {
    fn from(error: de::value::Error) -> Error {
        match error {
            de::value::Error::Custom(e) => {
                Error::Syntax(ErrorCode::Custom(e), 0, 0)
            }
            de::value::Error::EndOfStream => {
                de::Error::end_of_stream()
            }
            de::value::Error::InvalidType(ty) => {
                Error::Syntax(ErrorCode::InvalidType(ty), 0, 0)
            }
            de::value::Error::InvalidValue(msg) => {
                Error::Syntax(ErrorCode::InvalidValue(msg), 0, 0)
            }
            de::value::Error::InvalidLength(len) => {
                Error::Syntax(ErrorCode::InvalidLength(len), 0, 0)
            }
            de::value::Error::UnknownVariant(variant) => {
                Error::Syntax(ErrorCode::UnknownVariant(variant), 0, 0)
            }
            de::value::Error::UnknownField(field) => {
                Error::Syntax(ErrorCode::UnknownField(field), 0, 0)
            }
            de::value::Error::MissingField(field) => {
                Error::Syntax(ErrorCode::MissingField(field), 0, 0)
            }
        }
    }
}

impl de::Error for Error {
    fn custom(msg: String) -> Error {
        Error::Syntax(ErrorCode::Custom(msg), 0, 0)
    }

    fn end_of_stream() -> Error {
        Error::Syntax(ErrorCode::EOFWhileParsingValue, 0, 0)
    }

    fn invalid_type(ty: de::Type) -> Error {
        Error::Syntax(ErrorCode::InvalidType(ty), 0, 0)
    }

    fn invalid_value(msg: &str) -> Error {
        Error::Syntax(ErrorCode::InvalidValue(msg.to_owned()), 0, 0)
    }

    fn invalid_length(len: usize) -> Error {
        Error::Syntax(ErrorCode::InvalidLength(len), 0, 0)
    }

    fn unknown_variant(variant: &str) -> Error {
        Error::Syntax(ErrorCode::UnknownVariant(String::from(variant)), 0, 0)
    }

    fn unknown_field(field: &str) -> Error {
        Error::Syntax(ErrorCode::UnknownField(String::from(field)), 0, 0)
    }

    fn missing_field(field: &'static str) -> Error {
        Error::Syntax(ErrorCode::MissingField(field), 0, 0)
    }
}

impl ser::Error for Error {
    /// Raised when there is general error when deserializing a type.
    fn custom(msg: String) -> Self {
        Error::Syntax(ErrorCode::Custom(msg), 0, 0)
    }
}

/// Helper alias for `Result` objects that return a JSON `Error`.
pub type Result<T> = result::Result<T, Error>;
<commit_msg>feat(error): Update to latest Error::custom signature<commit_after>//! JSON Errors
//!
//! This module is centered around the `Error` and `ErrorCode` types, which represents all possible
//! `serde_json` errors.

use std::error;
use std::fmt;
use std::io;
use std::result;
use std::string::FromUtf8Error;

use serde::de;
use serde::ser;

/// The errors that can arise while parsing a JSON stream.
#[derive(Clone, PartialEq)]
pub enum ErrorCode {
    /// Catchall for syntax error messages
    Custom(String),

    /// Incorrect type from value
    InvalidType(de::Type),

    /// Incorrect value
    InvalidValue(String),

    /// Invalid length
    InvalidLength(usize),

    /// Unknown variant in an enum.
    UnknownVariant(String),

    /// Unknown field in struct.
    UnknownField(String),

    /// Struct is missing a field.
    MissingField(&'static str),

    /// EOF while parsing a list.
    EOFWhileParsingList,

    /// EOF while parsing an object.
    EOFWhileParsingObject,

    /// EOF while parsing a string.
    EOFWhileParsingString,

    /// EOF while parsing a JSON value.
    EOFWhileParsingValue,

    /// Expected this character to be a `':'`.
    ExpectedColon,

    /// Expected this character to be either a `','` or a `]`.
    ExpectedListCommaOrEnd,

    /// Expected this character to be either a `','` or a `}`.
    ExpectedObjectCommaOrEnd,

    /// Expected to parse either a `true`, `false`, or a `null`.
    ExpectedSomeIdent,

    /// Expected this character to start a JSON value.
    ExpectedSomeValue,

    /// Invalid hex escape code.
    InvalidEscape,

    /// Invalid number.
    InvalidNumber,

    /// Invalid unicode code point.
    InvalidUnicodeCodePoint,

    /// Object key is not a string.
    KeyMustBeAString,

    /// Lone leading surrogate in hex escape.
    LoneLeadingSurrogateInHexEscape,

    /// JSON has non-whitespace trailing characters after the value.
    TrailingCharacters,

    /// Unexpected end of hex excape.
    UnexpectedEndOfHexEscape,
}

impl fmt::Debug for ErrorCode {
    fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result {
        use std::fmt::Debug;

        match *self {
            ErrorCode::Custom(ref msg) => write!(f, "{}", msg),
            ErrorCode::InvalidType(ref ty) => write!(f, "invalid value type: {:?}", ty),
            ErrorCode::InvalidValue(ref msg) => write!(f, "invalid value: {}", msg),
            ErrorCode::InvalidLength(ref len) => write!(f, "invalid value length {}", len),
            ErrorCode::UnknownVariant(ref variant) => write!(f, "unknown variant \"{}\"", variant),
            ErrorCode::UnknownField(ref field) => write!(f, "unknown field \"{}\"", field),
            ErrorCode::MissingField(ref field) => write!(f, "missing field \"{}\"", field),
            ErrorCode::EOFWhileParsingList => "EOF while parsing a list".fmt(f),
            ErrorCode::EOFWhileParsingObject => "EOF while parsing an object".fmt(f),
            ErrorCode::EOFWhileParsingString => "EOF while parsing a string".fmt(f),
            ErrorCode::EOFWhileParsingValue => "EOF while parsing a value".fmt(f),
            ErrorCode::ExpectedColon => "expected `:`".fmt(f),
            ErrorCode::ExpectedListCommaOrEnd => "expected `,` or `]`".fmt(f),
            ErrorCode::ExpectedObjectCommaOrEnd => "expected `,` or `}`".fmt(f),
            ErrorCode::ExpectedSomeIdent => "expected ident".fmt(f),
            ErrorCode::ExpectedSomeValue => "expected value".fmt(f),
            ErrorCode::InvalidEscape => "invalid escape".fmt(f),
            ErrorCode::InvalidNumber => "invalid number".fmt(f),
            ErrorCode::InvalidUnicodeCodePoint => "invalid unicode code point".fmt(f),
            ErrorCode::KeyMustBeAString => "key must be a string".fmt(f),
            ErrorCode::LoneLeadingSurrogateInHexEscape => "lone leading surrogate in hex escape".fmt(f),
            ErrorCode::TrailingCharacters => "trailing characters".fmt(f),
            ErrorCode::UnexpectedEndOfHexEscape => "unexpected end of hex escape".fmt(f),
        }
    }
}

/// This type represents all possible errors that can occur when serializing or deserializing a
/// value into JSON.
#[derive(Debug)]
pub enum Error {
    /// The JSON value had some syntatic error.
    Syntax(ErrorCode, usize, usize),

    /// Some IO error occurred when serializing or deserializing a value.
    Io(io::Error),

    /// Some UTF8 error occurred while serializing or deserializing a value.
    FromUtf8(FromUtf8Error),
}

impl error::Error for Error {
    fn description(&self) -> &str {
        match *self {
            Error::Syntax(..) => "syntax error",
            Error::Io(ref error) => error::Error::description(error),
            Error::FromUtf8(ref error) => error.description(),
        }
    }

    fn cause(&self) -> Option<&error::Error> {
        match *self {
            Error::Io(ref error) => Some(error),
            Error::FromUtf8(ref error) => Some(error),
            _ => None,
        }
    }

}

impl fmt::Display for Error {
    fn fmt(&self, fmt: &mut fmt::Formatter) -> fmt::Result {
        match *self {
            Error::Syntax(ref code, line, col) => {
                write!(fmt, "{:?} at line {} column {}", code, line, col)
            }
            Error::Io(ref error) => fmt::Display::fmt(error, fmt),
            Error::FromUtf8(ref error) => fmt::Display::fmt(error, fmt),
        }
    }
}

impl From<io::Error> for Error {
    fn from(error: io::Error) -> Error {
        Error::Io(error)
    }
}

impl From<FromUtf8Error> for Error {
    fn from(error: FromUtf8Error) -> Error {
        Error::FromUtf8(error)
    }
}

impl From<de::value::Error> for Error {
    fn from(error: de::value::Error) -> Error {
        match error {
            de::value::Error::Custom(e) => {
                Error::Syntax(ErrorCode::Custom(e), 0, 0)
            }
            de::value::Error::EndOfStream => {
                de::Error::end_of_stream()
            }
            de::value::Error::InvalidType(ty) => {
                Error::Syntax(ErrorCode::InvalidType(ty), 0, 0)
            }
            de::value::Error::InvalidValue(msg) => {
                Error::Syntax(ErrorCode::InvalidValue(msg), 0, 0)
            }
            de::value::Error::InvalidLength(len) => {
                Error::Syntax(ErrorCode::InvalidLength(len), 0, 0)
            }
            de::value::Error::UnknownVariant(variant) => {
                Error::Syntax(ErrorCode::UnknownVariant(variant), 0, 0)
            }
            de::value::Error::UnknownField(field) => {
                Error::Syntax(ErrorCode::UnknownField(field), 0, 0)
            }
            de::value::Error::MissingField(field) => {
                Error::Syntax(ErrorCode::MissingField(field), 0, 0)
            }
        }
    }
}

impl de::Error for Error {
    fn custom<T: Into<String>>(msg: T) -> Error {
        Error::Syntax(ErrorCode::Custom(msg.into()), 0, 0)
    }

    fn end_of_stream() -> Error {
        Error::Syntax(ErrorCode::EOFWhileParsingValue, 0, 0)
    }

    fn invalid_type(ty: de::Type) -> Error {
        Error::Syntax(ErrorCode::InvalidType(ty), 0, 0)
    }

    fn invalid_value(msg: &str) -> Error {
        Error::Syntax(ErrorCode::InvalidValue(msg.to_owned()), 0, 0)
    }

    fn invalid_length(len: usize) -> Error {
        Error::Syntax(ErrorCode::InvalidLength(len), 0, 0)
    }

    fn unknown_variant(variant: &str) -> Error {
        Error::Syntax(ErrorCode::UnknownVariant(String::from(variant)), 0, 0)
    }

    fn unknown_field(field: &str) -> Error {
        Error::Syntax(ErrorCode::UnknownField(String::from(field)), 0, 0)
    }

    fn missing_field(field: &'static str) -> Error {
        Error::Syntax(ErrorCode::MissingField(field), 0, 0)
    }
}

impl ser::Error for Error {
    /// Raised when there is general error when deserializing a type.
    fn custom<T: Into<String>>(msg: T) -> Error {
        Error::Syntax(ErrorCode::Custom(msg.into()), 0, 0)
    }
}

/// Helper alias for `Result` objects that return a JSON `Error`.
pub type Result<T> = result::Result<T, Error>;
<|endoftext|>


<commit_before>use std::collections;
use std::env;
use std::fs;
use std::io::Write;
use std::path;

use clap;
use cobalt::cobalt_model;

use args;
use error::*;

pub fn init_command_args() -> clap::App<'static, 'static> {
    clap::SubCommand::with_name("init")
        .about("create a new cobalt project")
        .arg(
            clap::Arg::with_name("DIRECTORY")
                .help("Target directory")
                .default_value("./")
                .index(1),
        )
}

pub fn init_command(matches: &clap::ArgMatches) -> Result<()> {
    let directory = matches.value_of("DIRECTORY").unwrap();

    create_new_project(&directory.to_string())
        .chain_err(|| "Could not create a new cobalt project")?;
    info!("Created new project at {}", directory);

    Ok(())
}

pub fn new_command_args() -> clap::App<'static, 'static> {
    clap::SubCommand::with_name("new")
        .about("Create a document")
        .args(&args::get_config_args())
        .arg(
            clap::Arg::with_name("TITLE")
                .required(true)
                .help("Title of the post")
                .takes_value(true),
        )
        .arg(
            clap::Arg::with_name("file")
                .short("f")
                .long("file")
                .value_name("DIR_OR_FILE")
                .help("New document's parent directory or file (default: `<CWD>/title.ext`)")
                .takes_value(true),
        )
}

pub fn new_command(matches: &clap::ArgMatches) -> Result<()> {
    let config = args::get_config(matches)?;
    let config = config.build()?;

    let title = matches.value_of("TITLE").unwrap();

    let mut file = env::current_dir().expect("How does this fail?");
    if let Some(rel_file) = matches.value_of("file") {
        file.push(path::Path::new(rel_file))
    }

    create_new_document(&config, title, file)
        .chain_err(|| format!("Could not create `{}`", title))?;

    Ok(())
}

pub fn publish_command_args() -> clap::App<'static, 'static> {
    clap::SubCommand::with_name("publish")
        .about("Publish a document")
        .arg(
            clap::Arg::with_name("FILENAME")
                .required(true)
                .help("Document path to publish")
                .takes_value(true),
        )
}

pub fn publish_command(matches: &clap::ArgMatches) -> Result<()> {
    let file = matches
        .value_of("FILENAME")
        .expect("required parameters are present");
    let file = path::Path::new(file);

    publish_document(file).chain_err(|| format!("Could not publish `{:?}`", file))?;

    Ok(())
}

const COBALT_YML: &str = "
site:
  title: cobalt blog
  description: Blog Posts Go Here
  base_url: http://example.com
posts:
  rss: rss.xml
";

const DEFAULT_LAYOUT: &str = "<!DOCTYPE html>

        <meta charset=\"utf-8\">
        <title>{{ page.title }}</title>

{{ page.title }}

      {{ page.content }}

";

const POST_MD: &str = "layout: default.liquid

title: First Post
is_draft: true
---

# This is our first Post!

Welcome to the first post ever on cobalt.rs!
";

const INDEX_MD: &str = "layout: default.liquid
---
## Blog!

{% for post in collections.posts.pages %}
#### {{post.title}}

[{{ post.title }}]({{ post.permalink }})
{% endfor %}
";

lazy_static! {
    static ref DEFAULT: collections::HashMap<&'static str, &'static str> = [("pages", INDEX_MD), ("posts", POST_MD)].iter().map(|e| e.clone()).collect();
}

pub fn create_new_project<P: AsRef<path::Path>>(dest: P) -> Result<()> {
    create_new_project_for_path(dest.as_ref())
}

pub fn create_new_project_for_path(dest: &path::Path) -> Result<()> {
    fs::create_dir_all(dest)?;

    create_file(&dest.join("_cobalt.yml"), COBALT_YML)?;
    create_file(&dest.join("index.md"), INDEX_MD)?;

    fs::create_dir_all(&dest.join("_layouts"))?;
    create_file(&dest.join("_layouts/default.liquid"), DEFAULT_LAYOUT)?;

    fs::create_dir_all(&dest.join("posts"))?;
    create_file(&dest.join("posts/post-1.md"), POST_MD)?;

    Ok(())
}

pub fn create_new_document(
    config: &cobalt_model::Config,
    title: &str,
    file: path::PathBuf,
) -> Result<()> {
    let file = if file.extension().is_none() || file.is_dir() {
        let file_name = format!("{}.md", cobalt_model::slug::slugify(title));
        let mut file = file;
        file.push(path::Path::new(&file_name));
        file
    } else {
        file
    };

    let rel_file = file.strip_prefix(&config.source).map_err(|_| {
        format!(
            "New file {:?} not project directory ({:?})",
            file, config.source
        )
    })?;

    let pages = config.pages.clone().build()?;
    let posts = config.posts.clone().build()?;
    let file_type = if posts.pages.includes_file(&file)
        || posts
            .drafts
            .map(|d| d.includes_file(&file))
            .unwrap_or_default()
    {
        posts.slug.as_str()
    } else if pages.pages.includes_file(&file)
        || pages
            .drafts
            .map(|d| d.includes_file(&file))
            .unwrap_or_default()
    {
        pages.slug.as_str()
    } else {
        bail!(
            "Target file wouldn't be a member of any collection: {:?}",
            file
        );
    };

    // For custom collections, use a post default.
    let doc = DEFAULT.get(file_type).unwrap_or(&POST_MD);

    let doc = cobalt_model::DocumentBuilder::<cobalt_model::FrontmatterBuilder>::parse(doc)?;
    let (front, content) = doc.parts();
    let front = front.set_title(title.to_owned());
    let doc =
        cobalt_model::DocumentBuilder::<cobalt_model::FrontmatterBuilder>::new(front, content);
    let doc = doc.to_string();

    create_file(&file, &doc)?;
    info!("Created new {} {:?}", file_type, rel_file);

    Ok(())
}

fn create_file<P: AsRef<path::Path>>(path: P, content: &str) -> Result<()> {
    create_file_for_path(path.as_ref(), content)
}

fn create_file_for_path(path: &path::Path, content: &str) -> Result<()> {
    trace!("Creating file {:?}", path);

    let mut file = fs::OpenOptions::new()
        .write(true)
        .create_new(true)
        .open(path)
        .chain_err(|| format!("Failed to create file {:?}", path))?;

    file.write_all(content.as_bytes())?;

    Ok(())
}

pub fn publish_document(file: &path::Path) -> Result<()> {
    let doc = cobalt_model::files::read_file(file)?;
    let doc = cobalt_model::DocumentBuilder::<cobalt_model::FrontmatterBuilder>::parse(&doc)?;
    let (front, content) = doc.parts();

    let date = cobalt_model::DateTime::now();
    let front = front.set_draft(false).set_published_date(date);

    let doc =
        cobalt_model::DocumentBuilder::<cobalt_model::FrontmatterBuilder>::new(front, content);
    let doc = doc.to_string();

    cobalt_model::files::write_document_file(doc, file)?;

    Ok(())
}
<commit_msg>feat(new): Load default files from disk<commit_after>use std::collections;
use std::env;
use std::fs;
use std::io::Write;
use std::path;

use clap;
use cobalt::cobalt_model;

use args;
use error::*;

pub fn init_command_args() -> clap::App<'static, 'static> {
    clap::SubCommand::with_name("init")
        .about("create a new cobalt project")
        .arg(
            clap::Arg::with_name("DIRECTORY")
                .help("Target directory")
                .default_value("./")
                .index(1),
        )
}

pub fn init_command(matches: &clap::ArgMatches) -> Result<()> {
    let directory = matches.value_of("DIRECTORY").unwrap();

    create_new_project(&directory.to_string())
        .chain_err(|| "Could not create a new cobalt project")?;
    info!("Created new project at {}", directory);

    Ok(())
}

pub fn new_command_args() -> clap::App<'static, 'static> {
    clap::SubCommand::with_name("new")
        .about("Create a document")
        .args(&args::get_config_args())
        .arg(
            clap::Arg::with_name("TITLE")
                .required(true)
                .help("Title of the post")
                .takes_value(true),
        )
        .arg(
            clap::Arg::with_name("file")
                .short("f")
                .long("file")
                .value_name("DIR_OR_FILE")
                .help("New document's parent directory or file (default: `<CWD>/title.ext`)")
                .takes_value(true),
        )
}

pub fn new_command(matches: &clap::ArgMatches) -> Result<()> {
    let config = args::get_config(matches)?;
    let config = config.build()?;

    let title = matches.value_of("TITLE").unwrap();

    let mut file = env::current_dir().expect("How does this fail?");
    if let Some(rel_file) = matches.value_of("file") {
        file.push(path::Path::new(rel_file))
    }

    create_new_document(&config, title, file)
        .chain_err(|| format!("Could not create `{}`", title))?;

    Ok(())
}

pub fn publish_command_args() -> clap::App<'static, 'static> {
    clap::SubCommand::with_name("publish")
        .about("Publish a document")
        .arg(
            clap::Arg::with_name("FILENAME")
                .required(true)
                .help("Document path to publish")
                .takes_value(true),
        )
}

pub fn publish_command(matches: &clap::ArgMatches) -> Result<()> {
    let file = matches
        .value_of("FILENAME")
        .expect("required parameters are present");
    let file = path::Path::new(file);

    publish_document(file).chain_err(|| format!("Could not publish `{:?}`", file))?;

    Ok(())
}

const COBALT_YML: &str = "
site:
  title: cobalt blog
  description: Blog Posts Go Here
  base_url: http://example.com
posts:
  rss: rss.xml
";

const DEFAULT_LAYOUT: &str = "<!DOCTYPE html>

        <meta charset=\"utf-8\">
        <title>{{ page.title }}</title>

{{ page.title }}

      {{ page.content }}

";

const POST_MD: &str = "layout: default.liquid

title: First Post
is_draft: true
---

# This is our first Post!

Welcome to the first post ever on cobalt.rs!
";

const INDEX_MD: &str = "layout: default.liquid
---
## Blog!

{% for post in collections.posts.pages %}
#### {{post.title}}

[{{ post.title }}]({{ post.permalink }})
{% endfor %}
";

lazy_static! {
    static ref DEFAULT: collections::HashMap<&'static str, &'static str> = [("pages", INDEX_MD), ("posts", POST_MD)].iter().map(|e| e.clone()).collect();
}

pub fn create_new_project<P: AsRef<path::Path>>(dest: P) -> Result<()> {
    create_new_project_for_path(dest.as_ref())
}

pub fn create_new_project_for_path(dest: &path::Path) -> Result<()> {
    fs::create_dir_all(dest)?;

    create_file(&dest.join("_cobalt.yml"), COBALT_YML)?;
    create_file(&dest.join("index.md"), INDEX_MD)?;

    fs::create_dir_all(&dest.join("_layouts"))?;
    create_file(&dest.join("_layouts/default.liquid"), DEFAULT_LAYOUT)?;

    fs::create_dir_all(&dest.join("posts"))?;
    create_file(&dest.join("posts/post-1.md"), POST_MD)?;

    fs::create_dir_all(&dest.join("_defaults"))?;
    create_file(&dest.join("_defaults/pages.md"), INDEX_MD)?;
    create_file(&dest.join("_defaults/posts.md"), POST_MD)?;

    Ok(())
}

pub fn create_new_document(
    config: &cobalt_model::Config,
    title: &str,
    file: path::PathBuf,
) -> Result<()> {
    let file = if file.extension().is_none() || file.is_dir() {
        let file_name = format!("{}.md", cobalt_model::slug::slugify(title));
        let mut file = file;
        file.push(path::Path::new(&file_name));
        file
    } else {
        file
    };

    let rel_file = file.strip_prefix(&config.source).map_err(|_| {
        format!(
            "New file {:?} not project directory ({:?})",
            file, config.source
        )
    })?;

    let pages = config.pages.clone().build()?;
    let posts = config.posts.clone().build()?;
    let file_type = if posts.pages.includes_file(&file)
        || posts
            .drafts
            .map(|d| d.includes_file(&file))
            .unwrap_or_default()
    {
        posts.slug.as_str()
    } else if pages.pages.includes_file(&file)
        || pages
            .drafts
            .map(|d| d.includes_file(&file))
            .unwrap_or_default()
    {
        pages.slug.as_str()
    } else {
        bail!(
            "Target file wouldn't be a member of any collection: {:?}",
            file
        );
    };

    let extension = file.extension()
        .unwrap_or_default()
        .to_str()
        .unwrap_or_default();
    let source_path = config
        .source
        .join(format!("_defaults/{}.{}", file_type, extension));
    let source = if source_path.is_file() {
        cobalt_model::files::read_file(&source_path)
            .chain_err(|| format!("Failed to read default: {:?}", source_path))?
    } else {
        debug!(
            "No custom default provided ({:?}), falling back to built-in",
            source_path
        );
        if extension != "md" {
            bail!(
                "No builtin default for `{}` files, only `md`: {:?}",
                extension,
                file
            );
        }
        // For custom collections, use a post default.
        DEFAULT.get(file_type).unwrap_or(&POST_MD).to_string()
    };

    let doc = cobalt_model::DocumentBuilder::<cobalt_model::FrontmatterBuilder>::parse(&source)?;
    let (front, content) = doc.parts();
    let front = front.set_title(title.to_owned());
    let doc =
        cobalt_model::DocumentBuilder::<cobalt_model::FrontmatterBuilder>::new(front, content);
    let doc = doc.to_string();

    create_file(&file, &doc)?;
    info!("Created new {} {:?}", file_type, rel_file);

    Ok(())
}

fn create_file<P: AsRef<path::Path>>(path: P, content: &str) -> Result<()> {
    create_file_for_path(path.as_ref(), content)
}

fn create_file_for_path(path: &path::Path, content: &str) -> Result<()> {
    trace!("Creating file {:?}", path);

    let mut file = fs::OpenOptions::new()
        .write(true)
        .create_new(true)
        .open(path)
        .chain_err(|| format!("Failed to create file {:?}", path))?;

    file.write_all(content.as_bytes())?;

    Ok(())
}

pub fn publish_document(file: &path::Path) -> Result<()> {
    let doc = cobalt_model::files::read_file(file)?;
    let doc = cobalt_model::DocumentBuilder::<cobalt_model::FrontmatterBuilder>::parse(&doc)?;
    let (front, content) = doc.parts();

    let date = cobalt_model::DateTime::now();
    let front = front.set_draft(false).set_published_date(date);

    let doc =
        cobalt_model::DocumentBuilder::<cobalt_model::FrontmatterBuilder>::new(front, content);
    let doc = doc.to_string();

    cobalt_model::files::write_document_file(doc, file)?;

    Ok(())
}
<|endoftext|>