<commit_before>//! Diagnostic emitted for files that aren't part of any crate.

use hir::db::DefDatabase;
use ide_db::{
    base_db::{FileId, FileLoader, SourceDatabase, SourceDatabaseExt},
    source_change::SourceChange,
    RootDatabase,
};
use syntax::{
    ast::{self, ModuleItemOwner, NameOwner},
    AstNode, TextRange, TextSize,
};
use text_edit::TextEdit;

use crate::{fix, Assist, Diagnostic, DiagnosticsContext, Severity};

// Diagnostic: unlinked-file
//
// This diagnostic is shown for files that are not included in any crate, or files that are part of
// crates rust-analyzer failed to discover. The file will not have IDE features available.
pub(crate) fn unlinked_file(ctx: &DiagnosticsContext, acc: &mut Vec<Diagnostic>, file_id: FileId) {
    // Limit diagnostic to the first few characters in the file. This matches how VS Code
    // renders it with the full span, but on other editors, and is less invasive.
    let range = ctx.sema.db.parse(file_id).syntax_node().text_range();
    // FIXME: This is wrong if one of the first three characters is not ascii: `//Ы`.
    let range = range.intersect(TextRange::up_to(TextSize::of("..."))).unwrap_or(range);

    acc.push(
        Diagnostic::new("unlinked-file", "file not included in module tree", range)
            .severity(Severity::WeakWarning)
            .with_fixes(fixes(ctx, file_id)),
    );
}

fn fixes(ctx: &DiagnosticsContext, file_id: FileId) -> Option<Vec<Assist>> {
    // If there's an existing module that could add `mod` or `pub mod` items to include the unlinked file,
    // suggest that as a fix.

    let source_root = ctx.sema.db.source_root(ctx.sema.db.file_source_root(file_id));
    let our_path = source_root.path_for_file(&file_id)?;
    let (module_name, _) = our_path.name_and_extension()?;

    // Candidates to look for:
    // - `mod.rs` in the same folder
    //   - we also check `main.rs` and `lib.rs`
    // - `$dir.rs` in the parent folder, where `$dir` is the directory containing `self.file_id`
    let parent = our_path.parent()?;
    let mut paths = vec![parent.join("mod.rs")?, parent.join("lib.rs")?, parent.join("main.rs")?];

    // `submod/bla.rs` -> `submod.rs`
    let parent_mod = (|| {
        let (name, _) = parent.name_and_extension()?;
        parent.parent()?.join(&format!("{}.rs", name))
    })();
    paths.extend(parent_mod);

    for &parent_id in paths.iter().filter_map(|path| source_root.file_for_path(path)) {
        for &krate in ctx.sema.db.relevant_crates(parent_id).iter() {
            let crate_def_map = ctx.sema.db.crate_def_map(krate);
            for (_, module) in crate_def_map.modules() {
                if module.origin.is_inline() {
                    // We don't handle inline `mod parent {}`s, they use different paths.
                    continue;
                }

                if module.origin.file_id() == Some(parent_id) {
                    return make_fixes(ctx.sema.db, parent_id, module_name, file_id);
                }
            }
        }
    }

    None
}

fn make_fixes(
    db: &RootDatabase,
    parent_file_id: FileId,
    new_mod_name: &str,
    added_file_id: FileId,
) -> Option<Vec<Assist>> {
    fn is_outline_mod(item: &ast::Item) -> bool {
        matches!(item, ast::Item::Module(m) if m.item_list().is_none())
    }

    let mod_decl = format!("mod {};", new_mod_name);
    let pub_mod_decl = format!("pub mod {};", new_mod_name);

    let ast: ast::SourceFile = db.parse(parent_file_id).tree();

    let mut mod_decl_builder = TextEdit::builder();
    let mut pub_mod_decl_builder = TextEdit::builder();

    // If there's an existing `mod m;` statement matching the new one, don't emit a fix (it's
    // probably `#[cfg]`d out).
    for item in ast.items() {
        if let ast::Item::Module(m) = item {
            if let Some(name) = m.name() {
                if m.item_list().is_none() && name.to_string() == new_mod_name {
                    cov_mark::hit!(unlinked_file_skip_fix_when_mod_already_exists);
                    return None;
                }
            }
        }
    }

    // If there are existing `mod m;` items, append after them (after the first group of them, rather).
    match ast
        .items()
        .skip_while(|item| !is_outline_mod(item))
        .take_while(|item| is_outline_mod(item))
        .last()
    {
        Some(last) => {
            cov_mark::hit!(unlinked_file_append_to_existing_mods);
            let offset = last.syntax().text_range().end();
            mod_decl_builder.insert(offset, format!("\n{}", mod_decl));
            pub_mod_decl_builder.insert(offset, format!("\n{}", pub_mod_decl));
        }
        None => {
            // Prepend before the first item in the file.
            match ast.items().next() {
                Some(item) => {
                    cov_mark::hit!(unlinked_file_prepend_before_first_item);
                    let offset = item.syntax().text_range().start();
                    mod_decl_builder.insert(offset, format!("{}\n\n", mod_decl));
                    pub_mod_decl_builder.insert(offset, format!("{}\n\n", pub_mod_decl));
                }
                None => {
                    // No items in the file, so just append at the end.
                    cov_mark::hit!(unlinked_file_empty_file);
                    let offset = ast.syntax().text_range().end();
                    mod_decl_builder.insert(offset, format!("{}\n", mod_decl));
                    pub_mod_decl_builder.insert(offset, format!("{}\n", pub_mod_decl));
                }
            }
        }
    }

    let trigger_range = db.parse(added_file_id).tree().syntax().text_range();
    Some(vec![
        fix(
            "add_mod_declaration",
            &format!("Insert `{}`", mod_decl),
            SourceChange::from_text_edit(parent_file_id, mod_decl_builder.finish()),
            trigger_range,
        ),
        fix(
            "add_pub_mod_declaration",
            &format!("Insert `{}`", pub_mod_decl),
            SourceChange::from_text_edit(parent_file_id, pub_mod_decl_builder.finish()),
            trigger_range,
        ),
    ])
}

#[cfg(test)]
mod tests {
    use crate::tests::{check_diagnostics, check_fix, check_fixes, check_no_fix};

    #[test]
    fn unlinked_file_prepend_first_item() {
        cov_mark::check!(unlinked_file_prepend_before_first_item);
        // Only tests the first one for `pub mod` since the rest are the same
        check_fixes(
            r#"
//- /main.rs
fn f() {}
//- /foo.rs
$0
"#,
            vec![
                r#"
mod foo;

fn f() {}
"#,
                r#"
pub mod foo;

fn f() {}
"#,
            ],
        );
    }

    #[test]
    fn unlinked_file_append_mod() {
        cov_mark::check!(unlinked_file_append_to_existing_mods);
        check_fix(
            r#"
//- /main.rs
//! Comment on top

mod preexisting;

mod preexisting2;

struct S;

mod preexisting_bottom;)
//- /foo.rs
$0
"#,
            r#"
//! Comment on top

mod preexisting;

mod preexisting2;
mod foo;

struct S;

mod preexisting_bottom;)
"#,
        );
    }

    #[test]
    fn unlinked_file_insert_in_empty_file() {
        cov_mark::check!(unlinked_file_empty_file);
        check_fix(
            r#"
//- /main.rs
//- /foo.rs
$0
"#,
            r#"
mod foo;
"#,
        );
    }

    #[test]
    fn unlinked_file_old_style_modrs() {
        check_fix(
            r#"
//- /main.rs
mod submod;
//- /submod/mod.rs
// in mod.rs
//- /submod/foo.rs
$0
"#,
            r#"
// in mod.rs
mod foo;
"#,
        );
    }

    #[test]
    fn unlinked_file_new_style_mod() {
        check_fix(
            r#"
//- /main.rs
mod submod;
//- /submod.rs
//- /submod/foo.rs
$0
"#,
            r#"
mod foo;
"#,
        );
    }

    #[test]
    fn unlinked_file_with_cfg_off() {
        cov_mark::check!(unlinked_file_skip_fix_when_mod_already_exists);
        check_no_fix(
            r#"
//- /main.rs
#[cfg(never)]
mod foo;

//- /foo.rs
$0
"#,
        );
    }

    #[test]
    fn unlinked_file_with_cfg_on() {
        check_diagnostics(
            r#"
//- /main.rs
#[cfg(not(never))]
mod foo;

//- /foo.rs
"#,
        );
    }
}
<commit_msg>Fix incorrect mod.rs handling in unlinked_file fixes<commit_after>//! Diagnostic emitted for files that aren't part of any crate.

use hir::db::DefDatabase;
use ide_db::{
    base_db::{FileId, FileLoader, SourceDatabase, SourceDatabaseExt},
    source_change::SourceChange,
    RootDatabase,
};
use syntax::{
    ast::{self, ModuleItemOwner, NameOwner},
    AstNode, TextRange, TextSize,
};
use text_edit::TextEdit;

use crate::{fix, Assist, Diagnostic, DiagnosticsContext, Severity};

// Diagnostic: unlinked-file
//
// This diagnostic is shown for files that are not included in any crate, or files that are part of
// crates rust-analyzer failed to discover. The file will not have IDE features available.
pub(crate) fn unlinked_file(ctx: &DiagnosticsContext, acc: &mut Vec<Diagnostic>, file_id: FileId) {
    // Limit diagnostic to the first few characters in the file. This matches how VS Code
    // renders it with the full span, but on other editors, and is less invasive.
    let range = ctx.sema.db.parse(file_id).syntax_node().text_range();
    // FIXME: This is wrong if one of the first three characters is not ascii: `//Ы`.
    let range = range.intersect(TextRange::up_to(TextSize::of("..."))).unwrap_or(range);

    acc.push(
        Diagnostic::new("unlinked-file", "file not included in module tree", range)
            .severity(Severity::WeakWarning)
            .with_fixes(fixes(ctx, file_id)),
    );
}

fn fixes(ctx: &DiagnosticsContext, file_id: FileId) -> Option<Vec<Assist>> {
    // If there's an existing module that could add `mod` or `pub mod` items to include the unlinked file,
    // suggest that as a fix.

    let source_root = ctx.sema.db.source_root(ctx.sema.db.file_source_root(file_id));
    let our_path = source_root.path_for_file(&file_id)?;
    let (mut module_name, _) = our_path.name_and_extension()?;

    // Candidates to look for:
    // - `mod.rs`, `main.rs` and `lib.rs` in the same folder
    // - `$dir.rs` in the parent folder, where `$dir` is the directory containing `self.file_id`
    let parent = our_path.parent()?;
    let paths = {
        let temp;
        let parent = if module_name == "mod" {
            // for mod.rs we need to actually look up one higher
            // and take the parent as our to be module name
            let (name, _) = parent.name_and_extension()?;
            module_name = name;
            temp = parent.parent()?;
            &temp
        } else {
            &parent
        };
        let mut paths =
            vec![parent.join("mod.rs")?, parent.join("lib.rs")?, parent.join("main.rs")?];

        // `submod/bla.rs` -> `submod.rs`
        let parent_mod = (|| {
            let (name, _) = parent.name_and_extension()?;
            parent.parent()?.join(&format!("{}.rs", name))
        })();
        paths.extend(parent_mod);
        paths
    };

    for &parent_id in paths.iter().filter_map(|path| source_root.file_for_path(path)) {
        for &krate in ctx.sema.db.relevant_crates(parent_id).iter() {
            let crate_def_map = ctx.sema.db.crate_def_map(krate);
            for (_, module) in crate_def_map.modules() {
                if module.origin.is_inline() {
                    // We don't handle inline `mod parent {}`s, they use different paths.
                    continue;
                }

                if module.origin.file_id() == Some(parent_id) {
                    return make_fixes(ctx.sema.db, parent_id, module_name, file_id);
                }
            }
        }
    }

    None
}

fn make_fixes(
    db: &RootDatabase,
    parent_file_id: FileId,
    new_mod_name: &str,
    added_file_id: FileId,
) -> Option<Vec<Assist>> {
    fn is_outline_mod(item: &ast::Item) -> bool {
        matches!(item, ast::Item::Module(m) if m.item_list().is_none())
    }

    let mod_decl = format!("mod {};", new_mod_name);
    let pub_mod_decl = format!("pub mod {};", new_mod_name);

    let ast: ast::SourceFile = db.parse(parent_file_id).tree();

    let mut mod_decl_builder = TextEdit::builder();
    let mut pub_mod_decl_builder = TextEdit::builder();

    // If there's an existing `mod m;` statement matching the new one, don't emit a fix (it's
    // probably `#[cfg]`d out).
    for item in ast.items() {
        if let ast::Item::Module(m) = item {
            if let Some(name) = m.name() {
                if m.item_list().is_none() && name.to_string() == new_mod_name {
                    cov_mark::hit!(unlinked_file_skip_fix_when_mod_already_exists);
                    return None;
                }
            }
        }
    }

    // If there are existing `mod m;` items, append after them (after the first group of them, rather).
    match ast
        .items()
        .skip_while(|item| !is_outline_mod(item))
        .take_while(|item| is_outline_mod(item))
        .last()
    {
        Some(last) => {
            cov_mark::hit!(unlinked_file_append_to_existing_mods);
            let offset = last.syntax().text_range().end();
            mod_decl_builder.insert(offset, format!("\n{}", mod_decl));
            pub_mod_decl_builder.insert(offset, format!("\n{}", pub_mod_decl));
        }
        None => {
            // Prepend before the first item in the file.
            match ast.items().next() {
                Some(item) => {
                    cov_mark::hit!(unlinked_file_prepend_before_first_item);
                    let offset = item.syntax().text_range().start();
                    mod_decl_builder.insert(offset, format!("{}\n\n", mod_decl));
                    pub_mod_decl_builder.insert(offset, format!("{}\n\n", pub_mod_decl));
                }
                None => {
                    // No items in the file, so just append at the end.
                    cov_mark::hit!(unlinked_file_empty_file);
                    let offset = ast.syntax().text_range().end();
                    mod_decl_builder.insert(offset, format!("{}\n", mod_decl));
                    pub_mod_decl_builder.insert(offset, format!("{}\n", pub_mod_decl));
                }
            }
        }
    }

    let trigger_range = db.parse(added_file_id).tree().syntax().text_range();
    Some(vec![
        fix(
            "add_mod_declaration",
            &format!("Insert `{}`", mod_decl),
            SourceChange::from_text_edit(parent_file_id, mod_decl_builder.finish()),
            trigger_range,
        ),
        fix(
            "add_pub_mod_declaration",
            &format!("Insert `{}`", pub_mod_decl),
            SourceChange::from_text_edit(parent_file_id, pub_mod_decl_builder.finish()),
            trigger_range,
        ),
    ])
}

#[cfg(test)]
mod tests {

    use crate::tests::{check_diagnostics, check_fix, check_fixes, check_no_fix};

    #[test]
    fn unlinked_file_prepend_first_item() {
        cov_mark::check!(unlinked_file_prepend_before_first_item);
        // Only tests the first one for `pub mod` since the rest are the same
        check_fixes(
            r#"
//- /main.rs
fn f() {}
//- /foo.rs
$0
"#,
            vec![
                r#"
mod foo;

fn f() {}
"#,
                r#"
pub mod foo;

fn f() {}
"#,
            ],
        );
    }

    #[test]
    fn unlinked_file_append_mod() {
        cov_mark::check!(unlinked_file_append_to_existing_mods);
        check_fix(
            r#"
//- /main.rs
//! Comment on top

mod preexisting;

mod preexisting2;

struct S;

mod preexisting_bottom;)
//- /foo.rs
$0
"#,
            r#"
//! Comment on top

mod preexisting;

mod preexisting2;
mod foo;

struct S;

mod preexisting_bottom;)
"#,
        );
    }

    #[test]
    fn unlinked_file_insert_in_empty_file() {
        cov_mark::check!(unlinked_file_empty_file);
        check_fix(
            r#"
//- /main.rs
//- /foo.rs
$0
"#,
            r#"
mod foo;
"#,
        );
    }

    #[test]
    fn unlinked_file_insert_in_empty_file_mod_file() {
        check_fix(
            r#"
//- /main.rs
//- /foo/mod.rs
$0
"#,
            r#"
mod foo;
"#,
        );
        check_fix(
            r#"
//- /main.rs
mod bar;
//- /bar.rs
// bar module
//- /bar/foo/mod.rs
$0
"#,
            r#"
// bar module
mod foo;
"#,
        );
    }

    #[test]
    fn unlinked_file_old_style_modrs() {
        check_fix(
            r#"
//- /main.rs
mod submod;
//- /submod/mod.rs
// in mod.rs
//- /submod/foo.rs
$0
"#,
            r#"
// in mod.rs
mod foo;
"#,
        );
    }

    #[test]
    fn unlinked_file_new_style_mod() {
        check_fix(
            r#"
//- /main.rs
mod submod;
//- /submod.rs
//- /submod/foo.rs
$0
"#,
            r#"
mod foo;
"#,
        );
    }

    #[test]
    fn unlinked_file_with_cfg_off() {
        cov_mark::check!(unlinked_file_skip_fix_when_mod_already_exists);
        check_no_fix(
            r#"
//- /main.rs
#[cfg(never)]
mod foo;

//- /foo.rs
$0
"#,
        );
    }

    #[test]
    fn unlinked_file_with_cfg_on() {
        check_diagnostics(
            r#"
//- /main.rs
#[cfg(not(never))]
mod foo;

//- /foo.rs
"#,
        );
    }
}
<|endoftext|>


<commit_before>use std::io::{BufferedReader, File};
use material::materials::CookTorranceMaterial;
use geometry::{Mesh, Prim};
use geometry::prims::Triangle;
use raytracer::compositor::{Surface, ColorRGBA};
use vec3::Vec3;

/// This is limited to only CookTorranceMaterials, as I couldn't get a Box<Material> to clone
/// a new material for each triangle primitive in the object model.
#[allow(dead_code)]
pub fn from_obj(position: Vec3, scale: f64, material: CookTorranceMaterial /*Box<Material>*/, flip_normals: bool, filename: &str) -> Mesh {
    let path = Path::new(filename);
    let mut file = BufferedReader::new(File::open(&path));

    let mut vertices:   Vec<Vec3> = Vec::new();
    let mut tex_coords: Vec<Vec3> = Vec::new();
    let mut normals:    Vec<Vec3> = Vec::new();
    let mut triangles:  Vec<Box<Prim+Send+Share>> = Vec::new();

    let start_time = ::time::get_time();
    let print_progress_every = 1024u;
    let mut current_line = 0;
    let mut processed_bytes = 0;
    let total_bytes = match path.stat() {
        Ok(stat) => stat.size,
        Err(e) => fail!(e)
    };

    for line_iter in file.lines() {
        let line: String = match line_iter {
            Ok(x) => x,
            Err(e) => fail!("Could not import OBJ file {} (file missing?): {}", filename, e)
        };

        let tokens: Vec<&str> = line.as_slice().words().collect();
        if tokens.len() < 4 { continue }

        match tokens[0].as_slice() {
            "v" => {
                vertices.push(Vec3 {
                    x: parse_coord_str(tokens[1].as_slice(), scale, line.as_slice()),
                    y: parse_coord_str(tokens[2].as_slice(), scale, line.as_slice()),
                    z: parse_coord_str(tokens[3].as_slice(), scale, line.as_slice()),
                });
            },
            "vt" => {
                tex_coords.push(Vec3 {
                    x: parse_coord_str(tokens[1].as_slice(), scale, line.as_slice()),
                    y: parse_coord_str(tokens[2].as_slice(), scale, line.as_slice()),
                    z: 0.0
                });
            },
            "vn" => {
                let normals_flip_scale = if flip_normals { -1.0 } else { 1.0 } * scale;
                normals.push(Vec3 {
                    x: parse_coord_str(tokens[1].as_slice(), scale * normals_flip_scale, line.as_slice()),
                    y: parse_coord_str(tokens[2].as_slice(), scale * normals_flip_scale, line.as_slice()),
                    z: parse_coord_str(tokens[3].as_slice(), scale * normals_flip_scale, line.as_slice()),
                });
            },
            "f" => {
                // ["f", "1/2/3", "2/2/2", "12//4"] => [[1, 2, 3], [2, 2, 2], [12, -1u, 4]]
                let pairs: Vec<Vec<uint>> = tokens.tail().iter().map( |token| {
                    let str_tokens: Vec<&str> = token.as_slice().split('/').collect();
                    str_tokens.iter().map( |str_tok| {
                        match from_str::<uint>(*str_tok) {
                            Some(uint_tok) => uint_tok - 1,
                            None => -1 // No data available/not supplied
                        }
                    }).collect()
                }).collect();

                // If no texture coordinates were supplied, default to zero.
                let mut u = Vec3::zero();
                let mut v = Vec3::zero();

                // We store nothing supplied as -1 (uint=4294967295)
                if pairs[0][1] != 4294967295 {
                    u = Vec3 {
                        x: tex_coords[pairs[0][1]].x,
                        y: tex_coords[pairs[1][1]].x,
                        z: tex_coords[pairs[2][1]].x
                    };

                    v = Vec3 {
                        x: tex_coords[pairs[0][1]].y,
                        y: tex_coords[pairs[1][1]].y,
                        z: tex_coords[pairs[2][1]].y
                    };
                }

                triangles.push(box Triangle {
                    v0: vertices[pairs[0][0]],
                    v1: vertices[pairs[1][0]],
                    v2: vertices[pairs[2][0]],

                    n0: normals[pairs[0][2]],
                    n1: normals[pairs[1][2]],
                    n2: normals[pairs[2][2]],

                    u: u,
                    v: v,
                    material: box material.clone()
                });
            },
            _ => {}
        }

        current_line += 1;
        processed_bytes += line.as_bytes().len();
        if current_line % print_progress_every == 0 {
            ::util::print_progress("Bytes", start_time, processed_bytes, total_bytes as uint);
        }
    }

    // Cheat the progress meter
    ::util::print_progress("Bytes", start_time, total_bytes as uint, total_bytes as uint);

    Mesh {
        position: position,
        scale: scale,
        triangles: triangles
    }
}

fn parse_coord_str(coord: &str, scale: f64, line: &str) -> f64 {
    match from_str::<f64>(coord.as_slice()) {
        Some(f) => f * scale,
        None => fail!(format!("Bad vertex or texture coordinate in file. `{}`", line))
    }
}

#[allow(dead_code)]
pub fn from_ppm(filename: &str) -> Surface {
    let path = Path::new(filename);
    let mut file = BufferedReader::new(File::open(&path));

    let tex = match file.read_to_string() { Ok(f) => f, Err(e) => { println!("Could not open {}", filename); fail!(e) }};
    let mut tokens: Vec<&str> = tex.as_slice().words().collect();

    tokens.remove(0); // PPM type
    let width  = match tokens.remove(0) { Some(x) => uint_from_string(x), None => fail!("Bad PPM") };
    let height = match tokens.remove(0) { Some(x) => uint_from_string(x), None => fail!("Bad PPM") };
    tokens.remove(0); // Max color value

    print!("Importing image texture {}", filename);
    println!(" {}x{}", width, height);

    let mut surface = Surface::new(width, height, ColorRGBA::new_rgb(0, 0, 0));

    let mut i = 0u;

    for chunk in tokens.as_slice().chunks(3) {
        let x = i % width;
        let y = i / width;
        i += 1;

        if x >= width || y >= height { break };

        *surface.get_mut(x, y) = ColorRGBA::new_rgb(
            uint_from_string(chunk[0]) as u8,
            uint_from_string(chunk[1]) as u8,
            uint_from_string(chunk[2]) as u8
        );
    }

    surface
}

fn uint_from_string(s: &str) -> uint {
     match from_str::<uint>(s) {
        Some(x) => x,
        None => fail!("Bad uint string {}", s)
    }
}
<commit_msg>Slightly clean up how texture coords are handled during import.<commit_after>use std::io::{BufferedReader, File};
use material::materials::CookTorranceMaterial;
use geometry::{Mesh, Prim};
use geometry::prims::Triangle;
use raytracer::compositor::{Surface, ColorRGBA};
use vec3::Vec3;

/// This is limited to only CookTorranceMaterials, as I couldn't get a Box<Material> to clone
/// a new material for each triangle primitive in the object model.
#[allow(dead_code)]
pub fn from_obj(position: Vec3, scale: f64, material: CookTorranceMaterial /*Box<Material>*/, flip_normals: bool, filename: &str) -> Mesh {
    let path = Path::new(filename);
    let mut file = BufferedReader::new(File::open(&path));

    let mut vertices: Vec<Vec3> = Vec::new();
    let mut tex_coords: Vec<Vec<f64>> = Vec::new();
    let mut normals: Vec<Vec3> = Vec::new();
    let mut triangles: Vec<Box<Prim+Send+Share>> = Vec::new();

    let start_time = ::time::get_time();
    let print_progress_every = 1024u;
    let mut current_line = 0;
    let mut processed_bytes = 0;
    let total_bytes = match path.stat() {
        Ok(stat) => stat.size,
        Err(e) => fail!(e)
    };

    for line_iter in file.lines() {
        let line: String = match line_iter {
            Ok(x) => x,
            Err(e) => fail!("Could not open file {} (file missing?): {}", filename, e)
        };

        let tokens: Vec<&str> = line.as_slice().words().collect();
        if tokens.len() < 4 { continue }

        match tokens[0].as_slice() {
            "v" => {
                vertices.push(Vec3 {
                    x: parse_coord_str(tokens[1].as_slice(), scale, line.as_slice()),
                    y: parse_coord_str(tokens[2].as_slice(), scale, line.as_slice()),
                    z: parse_coord_str(tokens[3].as_slice(), scale, line.as_slice()),
                });
            },
            "vt" => {
                tex_coords.push(vec![
                    parse_coord_str(tokens[1].as_slice(), scale, line.as_slice()),
                    parse_coord_str(tokens[2].as_slice(), scale, line.as_slice())
                ]);
            },
            "vn" => {
                let normals_flip_scale = if flip_normals { -1.0 } else { 1.0 } * scale;
                normals.push(Vec3 {
                    x: parse_coord_str(tokens[1].as_slice(), scale * normals_flip_scale, line.as_slice()),
                    y: parse_coord_str(tokens[2].as_slice(), scale * normals_flip_scale, line.as_slice()),
                    z: parse_coord_str(tokens[3].as_slice(), scale * normals_flip_scale, line.as_slice()),
                });
            },
            "f" => {
                // ["f", "1/2/3", "2/2/2", "12//4"] => [[1, 2, 3], [2, 2, 2], [12, -1u, 4]]
                let pairs: Vec<Vec<uint>> = tokens.tail().iter().map( |token| {
                    let str_tokens: Vec<&str> = token.as_slice().split('/').collect();
                    str_tokens.iter().map( |str_tok| {
                        match from_str::<uint>(*str_tok) {
                            Some(uint_tok) => uint_tok - 1,
                            None => -1 // No data available/not supplied
                        }
                    }).collect()
                }).collect();

                // If no texture coordinates were supplied, default to zero.
                let mut u = Vec3::zero();
                let mut v = Vec3::zero();

                // We store nothing supplied as -1 (uint=4294967295)
                if pairs[0][1] != 4294967295 {
                    u = Vec3 {
                        x: tex_coords[pairs[0][1]][0],
                        y: tex_coords[pairs[1][1]][0],
                        z: tex_coords[pairs[2][1]][0]
                    };

                    v = Vec3 {
                        x: tex_coords[pairs[0][1]][1],
                        y: tex_coords[pairs[1][1]][1],
                        z: tex_coords[pairs[2][1]][1]
                    };
                }

                triangles.push(box Triangle {
                    v0: vertices[pairs[0][0]],
                    v1: vertices[pairs[1][0]],
                    v2: vertices[pairs[2][0]],

                    n0: normals[pairs[0][2]],
                    n1: normals[pairs[1][2]],
                    n2: normals[pairs[2][2]],

                    u: u,
                    v: v,
                    material: box material.clone()
                });
            },
            _ => {}
        }

        current_line += 1;
        processed_bytes += line.as_bytes().len();
        if current_line % print_progress_every == 0 {
            ::util::print_progress("Bytes", start_time, processed_bytes, total_bytes as uint);
        }
    }

    // Cheat the progress meter
    ::util::print_progress("Bytes", start_time, total_bytes as uint, total_bytes as uint);

    Mesh {
        position: position,
        scale: scale,
        triangles: triangles
    }
}

fn parse_coord_str(coord: &str, scale: f64, line: &str) -> f64 {
    match from_str::<f64>(coord.as_slice()) {
        Some(f) => f * scale,
        None => fail!(format!("Bad vertex or texture coordinate in file. `{}`", line))
    }
}

#[allow(dead_code)]
pub fn from_ppm(filename: &str) -> Surface {
    let path = Path::new(filename);
    let mut file = BufferedReader::new(File::open(&path));

    let tex = match file.read_to_string() { Ok(f) => f, Err(e) => { fail!("Could not open file {} (file missing?): {}", filename, e) }};
    let mut tokens: Vec<&str> = tex.as_slice().words().collect();

    tokens.remove(0); // PPM type
    let width  = match tokens.remove(0) { Some(x) => uint_from_string(x), None => fail!("Bad PPM") };
    let height = match tokens.remove(0) { Some(x) => uint_from_string(x), None => fail!("Bad PPM") };
    tokens.remove(0); // Max color value

    print!("Importing image texture {}", filename);
    println!(" {}x{}", width, height);

    let mut surface = Surface::new(width, height, ColorRGBA::new_rgb(0, 0, 0));

    let mut i = 0u;

    for chunk in tokens.as_slice().chunks(3) {
        let x = i % width;
        let y = i / width;
        i += 1;

        if x >= width || y >= height { break };

        *surface.get_mut(x, y) = ColorRGBA::new_rgb(
            uint_from_string(chunk[0]) as u8,
            uint_from_string(chunk[1]) as u8,
            uint_from_string(chunk[2]) as u8
        );
    }

    surface
}

fn uint_from_string(s: &str) -> uint {
     match from_str::<uint>(s) {
        Some(x) => x,
        None => fail!("Bad uint string {}", s)
    }
}
<|endoftext|>


<commit_before>#![crate_name = "foo"]
#![feature(const_evaluatable_checked, const_generics)]
#![allow(incomplete_features)]
// make sure that `ConstEvaluatable` predicates dont cause rustdoc to ICE #77647
pub struct Ice<const N: usize> where [(); N + 1]:;<commit_msg>line<commit_after>#![crate_name = "foo"]
#![feature(const_evaluatable_checked, const_generics)]
#![allow(incomplete_features)]
// make sure that `ConstEvaluatable` predicates dont cause rustdoc to ICE #77647
pub struct Ice<const N: usize> where [(); N + 1]:;
<|endoftext|>


<commit_before>use netsurfcss::stylesheet::CssStylesheet;
use netsurfcss::select::{CssSelectCtx, css_select_ctx_create, CssSelectResults, CssSelectHandler, CssPseudoElementNone};
use netsurfcss::types::{CssQName, CssColor};
use netsurfcss::types::{CssUnit, CssUnitPx, CssUnitEm};
use netsurfcss::properties::CssProperty;
use netsurfcss::values::{CssColorValue, CssColorInherit, CssColorColor};
use netsurfcss::values::{CssBorderWidthValue, CssBorderWidthInherit, CssBorderWidthWidth};
use netsurfcss::ll::types::{CSS_ORIGIN_AUTHOR, CSS_MEDIA_SCREEN};
use netsurfcss::hint::{CssHint, CssHintDefault};
use netsurfcss::computed::CssComputedStyle;
use netsurfcss::util::css_fixed_to_float;
use wapcaplet::from_rust_string;
use util::DataStream;
use std::net::url::Url;
use values::{CSSValue, Inherit, Specified, Length, Em, Px};
use color::{Color, rgba};

pub use netsurfcss::util::VoidPtrLike;

pub struct Stylesheet {
    inner: CssStylesheet
}

impl Stylesheet {
    static fn new(url: Url, input: DataStream) -> Stylesheet {
        Stylesheet {
            inner: parser::parse_stylesheet(move url, input)
        }
    }
}

pub struct SelectCtx {
    inner: CssSelectCtx
}

impl SelectCtx {
    static fn new() -> SelectCtx {
        SelectCtx {
            inner: css_select_ctx_create()
        }
    }

    fn append_sheet(&mut self, sheet: Stylesheet) {
        let sheet = match move sheet {
            Stylesheet { inner: move inner } => move inner
        };

        self.inner.append_sheet(move sheet, CSS_ORIGIN_AUTHOR, CSS_MEDIA_SCREEN)
    }

    fn select_style<N: VoidPtrLike, H: SelectHandler<N>>(&self, node: &N, handler: &H) -> SelectResults {
        let inner_handler = InnerHandler {
            inner: ptr::to_unsafe_ptr(handler)
        };
        SelectResults {
            inner: self.inner.select_style::<N, InnerHandler<N, H>>(node, CSS_MEDIA_SCREEN, None, &inner_handler)
        }
    }
}

pub struct SelectResults {
    inner: CssSelectResults
}

impl SelectResults {
    fn computed_style(&self) -> ComputedStyle/&self {
        ComputedStyle {
            inner: self.inner.computed_style(CssPseudoElementNone)
        }
    }
}

pub trait SelectHandler<N> {
    fn node_name(node: &N) -> ~str;
}

struct InnerHandler<N, H: SelectHandler<N>> {
    // FIXME: Can't encode region variables
    inner: *H
}

priv impl<N, H: SelectHandler<N>> InnerHandler<N, H> {
    priv fn inner_ref() -> &self/H {
        unsafe { &*self.inner }
    }
}

impl<N, H: SelectHandler<N>> InnerHandler<N, H>: CssSelectHandler<N> {
    fn node_name(node: &N) -> CssQName {
        CssQName {
            ns: None,
            name: from_rust_string(self.inner_ref().node_name(node))
        }
    }
    fn parent_node(_node: &N) -> Option<N> {
        None
    }
    fn ua_default_for_property(property: CssProperty) -> CssHint {
        warn!("not specifiying ua default for property %?", property);
        CssHintDefault
    }
}

pub struct ComputedStyle {
    inner: CssComputedStyle
}

impl ComputedStyle {
    pub fn background_color() -> CSSValue<Color> {
        convert_net_color_value(self.inner.background_color())
    }

    pub fn border_top_width() -> CSSValue<Length> {
        convert_net_border_width(self.inner.border_top_width())
    }

    pub fn border_right_width() -> CSSValue<Length> {
        convert_net_border_width(self.inner.border_right_width())
    }

    pub fn border_bottom_width() -> CSSValue<Length> {
        convert_net_border_width(self.inner.border_bottom_width())
    }

    pub fn border_left_width() -> CSSValue<Length> {
        convert_net_border_width(self.inner.border_left_width())
    }
}

fn convert_net_color(color: CssColor) -> Color {
    rgba(color.r, color.g, color.b, (color.a as float) / 255.0)
}

fn convert_net_color_value(color: CssColorValue) -> CSSValue<Color> {
    match color {
        CssColorInherit => Inherit,
        CssColorColor(v) => Specified(convert_net_color(v))
    }
}

fn convert_net_border_width(width: CssBorderWidthValue) -> CSSValue<Length> {
    match width {
        CssBorderWidthInherit => Inherit,
        CssBorderWidthWidth(width) => Specified(convert_net_unit_to_length(width)),
        _ => unimpl("border width")
    }
}

fn convert_net_unit_to_length(unit: CssUnit) -> Length {
    match unit {
        CssUnitPx(l) => Px(css_fixed_to_float(l)),
        CssUnitEm(l) => Em(css_fixed_to_float(l)),
        _ => unimpl("unit")
    }
}

fn unimpl(what: &str) -> ! {
    fail fmt!("css unimplemented %?", what)
}
<commit_msg>Rename InnerHandler to SelectHandlerWrapper<commit_after>use netsurfcss::stylesheet::CssStylesheet;
use netsurfcss::select::{CssSelectCtx, css_select_ctx_create, CssSelectResults, CssSelectHandler, CssPseudoElementNone};
use netsurfcss::types::{CssQName, CssColor};
use netsurfcss::types::{CssUnit, CssUnitPx, CssUnitEm};
use netsurfcss::properties::CssProperty;
use netsurfcss::values::{CssColorValue, CssColorInherit, CssColorColor};
use netsurfcss::values::{CssBorderWidthValue, CssBorderWidthInherit, CssBorderWidthWidth};
use netsurfcss::ll::types::{CSS_ORIGIN_AUTHOR, CSS_MEDIA_SCREEN};
use netsurfcss::hint::{CssHint, CssHintDefault};
use netsurfcss::computed::CssComputedStyle;
use netsurfcss::util::css_fixed_to_float;
use wapcaplet::from_rust_string;
use util::DataStream;
use std::net::url::Url;
use values::{CSSValue, Inherit, Specified, Length, Em, Px};
use color::{Color, rgba};

pub use netsurfcss::util::VoidPtrLike;

pub struct Stylesheet {
    inner: CssStylesheet
}

impl Stylesheet {
    static fn new(url: Url, input: DataStream) -> Stylesheet {
        Stylesheet {
            inner: parser::parse_stylesheet(move url, input)
        }
    }
}

pub struct SelectCtx {
    inner: CssSelectCtx
}

impl SelectCtx {
    static fn new() -> SelectCtx {
        SelectCtx {
            inner: css_select_ctx_create()
        }
    }

    fn append_sheet(&mut self, sheet: Stylesheet) {
        let sheet = match move sheet {
            Stylesheet { inner: move inner } => move inner
        };

        self.inner.append_sheet(move sheet, CSS_ORIGIN_AUTHOR, CSS_MEDIA_SCREEN)
    }

    fn select_style<N: VoidPtrLike, H: SelectHandler<N>>(&self, node: &N, handler: &H) -> SelectResults {
        let inner_handler = SelectHandlerWrapper {
            inner: ptr::to_unsafe_ptr(handler)
        };
        SelectResults {
            inner: self.inner.select_style::<N, SelectHandlerWrapper<N, H>>(node, CSS_MEDIA_SCREEN, None, &inner_handler)
        }
    }
}

pub struct SelectResults {
    inner: CssSelectResults
}

impl SelectResults {
    fn computed_style(&self) -> ComputedStyle/&self {
        ComputedStyle {
            inner: self.inner.computed_style(CssPseudoElementNone)
        }
    }
}

pub trait SelectHandler<N> {
    fn node_name(node: &N) -> ~str;
}

struct SelectHandlerWrapper<N, H: SelectHandler<N>> {
    // FIXME: Can't encode region variables
    inner: *H
}

priv impl<N, H: SelectHandler<N>> SelectHandlerWrapper<N, H> {
    priv fn inner_ref() -> &self/H {
        unsafe { &*self.inner }
    }
}

impl<N, H: SelectHandler<N>> SelectHandlerWrapper<N, H>: CssSelectHandler<N> {
    fn node_name(node: &N) -> CssQName {
        CssQName {
            ns: None,
            name: from_rust_string(self.inner_ref().node_name(node))
        }
    }
    fn parent_node(_node: &N) -> Option<N> {
        None
    }
    fn ua_default_for_property(property: CssProperty) -> CssHint {
        warn!("not specifiying ua default for property %?", property);
        CssHintDefault
    }
}

pub struct ComputedStyle {
    inner: CssComputedStyle
}

impl ComputedStyle {
    pub fn background_color() -> CSSValue<Color> {
        convert_net_color_value(self.inner.background_color())
    }

    pub fn border_top_width() -> CSSValue<Length> {
        convert_net_border_width(self.inner.border_top_width())
    }

    pub fn border_right_width() -> CSSValue<Length> {
        convert_net_border_width(self.inner.border_right_width())
    }

    pub fn border_bottom_width() -> CSSValue<Length> {
        convert_net_border_width(self.inner.border_bottom_width())
    }

    pub fn border_left_width() -> CSSValue<Length> {
        convert_net_border_width(self.inner.border_left_width())
    }
}

fn convert_net_color(color: CssColor) -> Color {
    rgba(color.r, color.g, color.b, (color.a as float) / 255.0)
}

fn convert_net_color_value(color: CssColorValue) -> CSSValue<Color> {
    match color {
        CssColorInherit => Inherit,
        CssColorColor(v) => Specified(convert_net_color(v))
    }
}

fn convert_net_border_width(width: CssBorderWidthValue) -> CSSValue<Length> {
    match width {
        CssBorderWidthInherit => Inherit,
        CssBorderWidthWidth(width) => Specified(convert_net_unit_to_length(width)),
        _ => unimpl("border width")
    }
}

fn convert_net_unit_to_length(unit: CssUnit) -> Length {
    match unit {
        CssUnitPx(l) => Px(css_fixed_to_float(l)),
        CssUnitEm(l) => Em(css_fixed_to_float(l)),
        _ => unimpl("unit")
    }
}

fn unimpl(what: &str) -> ! {
    fail fmt!("css unimplemented %?", what)
}
<|endoftext|>


<commit_before>use primitive::*;

table!(HorizontalHeader {
    version             (Fixed   ),
    Ascender            (FWORD   ),
    Descender           (FWORD   ),
    LineGap             (FWORD   ),
    advanceWidthMax     (UFWORD  ),
    minLeftSideBearing  (FWORD   ),
    minRightSideBearing (FWORD   ),
    xMaxExtent          (FWORD   ),
    caretSlopeRise      (SHORT   ),
    caretSlopeRun       (SHORT   ),
    caretOffset         (SHORT   ),
    reserved1           (SHORT   ),
    reserved2           (SHORT   ),
    reserved3           (SHORT   ),
    reserved4           (SHORT   ),
    metricDataFormat    (SHORT   ),
    numberOfHMetrics    (USHORT  ),
});
<commit_msg>Fix styles<commit_after>use primitive::*;

table!(HorizontalHeader {
    version             (Fixed ),
    Ascender            (FWORD ),
    Descender           (FWORD ),
    LineGap             (FWORD ),
    advanceWidthMax     (UFWORD),
    minLeftSideBearing  (FWORD ),
    minRightSideBearing (FWORD ),
    xMaxExtent          (FWORD ),
    caretSlopeRise      (SHORT ),
    caretSlopeRun       (SHORT ),
    caretOffset         (SHORT ),
    reserved1           (SHORT ),
    reserved2           (SHORT ),
    reserved3           (SHORT ),
    reserved4           (SHORT ),
    metricDataFormat    (SHORT ),
    numberOfHMetrics    (USHORT),
});
<|endoftext|>


<commit_before>use std::convert::From;
use std::iter::Iterator;
use std::mem;
use timeclock::direction::Direction;
use timeclock::timeentry::TimeEntry;

/// Option-like enclosure for TimeEntrys
#[derive(Debug,PartialEq)]
enum TimeEntryOpt {
    In(TimeEntry),
    Out(TimeEntry),
    Invalid,
}

impl TimeEntryOpt {
    /// Take the TimeEntry variant and leave Invalid in it's place
    pub fn take(&mut self) -> TimeEntryOpt {
        mem::replace(self, TimeEntryOpt::Invalid)
    }
}

impl From<Option<TimeEntry>> for TimeEntryOpt {
    fn from(opt: Option<TimeEntry>) -> Self {
        let te = match opt {
            Some(te) => te,
            None => {
                return TimeEntryOpt::Invalid;
            }
        };
        match te.dir {
            Direction::In => TimeEntryOpt::In(te),
            Direction::Out => TimeEntryOpt::Out(te),
        }
    }
}

impl From<TimeEntry> for TimeEntryOpt {
    fn from(te: TimeEntry) -> Self {
        match te.dir {
            Direction::In => TimeEntryOpt::In(te),
            Direction::Out => TimeEntryOpt::Out(te),
        }
    }
}

#[derive(Debug,PartialEq)]
pub struct TimeEntryPair {
    start: TimeEntry,
    end: TimeEntry,
}

impl TimeEntryPair {
    pub fn new(s: TimeEntry, e: TimeEntry) -> Self {
        TimeEntryPair { start: s, end: e }
    }

    pub fn start(&self) -> &TimeEntry {
        &self.start
    }

    pub fn end(&self) -> &TimeEntry {
        &self.end
    }
}

/// Iterator to create TimeEntryPairs from TimeEntrys
pub struct TimeEntryPairsIter<I> {
    buf: TimeEntryOpt,
    v: I,
}

/// Constructor for TimeEntryPairsIter
pub fn timeentry_pairs<I>(entries: I) -> TimeEntryPairsIter<I>
    where I: Iterator<Item = TimeEntry>
{
    TimeEntryPairsIter {
        buf: TimeEntryOpt::Invalid,
        v: entries.into_iter(),
    }
}

impl<I> Iterator for TimeEntryPairsIter<I>
    where I: Iterator<Item = TimeEntry>
{
    type Item = TimeEntryPair;
    fn next(&mut self) -> Option<TimeEntryPair> {
        if self.buf == TimeEntryOpt::Invalid {
            self.buf = TimeEntryOpt::from(self.v.next());
        }

        let nxt = TimeEntryOpt::from(self.v.next());
        match (self.buf.take(), nxt) {
            (TimeEntryOpt::In(start), TimeEntryOpt::In(saved)) => {
                let mut end = start.clone();
                end.dir = Direction::Out;
                end.memo = String::from("Missing clock out.");
                self.buf = TimeEntryOpt::from(saved);
                Some(TimeEntryPair::new(start, end))
            }
            (TimeEntryOpt::In(start), TimeEntryOpt::Invalid) => {
                let mut end = start.clone();
                end.dir = Direction::Out;
                end.memo = String::from("Missing clock out.");
                Some(TimeEntryPair::new(start, end))
            }
            (TimeEntryOpt::In(start), TimeEntryOpt::Out(end)) => {
                Some(TimeEntryPair::new(start, end))
            }
            (TimeEntryOpt::Out(end), TimeEntryOpt::Out(saved)) => {
                let mut start = end.clone();
                start.dir = Direction::In;
                start.memo = String::from("Missing clock in.");
                self.buf = TimeEntryOpt::from(saved);
                Some(TimeEntryPair::new(start, end))
            }
            (TimeEntryOpt::Out(end), TimeEntryOpt::In(saved)) => {
                let mut start = end.clone();
                start.dir = Direction::In;
                start.memo = String::from("Missing clock in.");
                self.buf = TimeEntryOpt::from(saved);
                Some(TimeEntryPair::new(start, end))
            }
            (TimeEntryOpt::Out(end), TimeEntryOpt::Invalid) => {
                let mut start = end.clone();
                start.dir = Direction::In;
                start.memo = String::from("Missing clock in.");
                Some(TimeEntryPair::new(start, end))
            }
            (TimeEntryOpt::Invalid, TimeEntryOpt::Out(_)) => unreachable!(),
            (TimeEntryOpt::Invalid, TimeEntryOpt::In(_)) => unreachable!(),
            (TimeEntryOpt::Invalid, TimeEntryOpt::Invalid) => None,
        }
    }
}

#[cfg(test)]
mod tests {
    use super::*;
    use timeclock::direction::Direction;
    use timeclock::now;
    use timeclock::timeentry::TimeEntry;

    #[test]
    fn iter_in_in_test() {
        let a = TimeEntry::new(Direction::In, now(), "In");
        let b = TimeEntry::new(Direction::In, now(), "In");
        let v = vec![a, b];
        let y = timeentry_pairs(v.into_iter()).collect::<Vec<TimeEntryPair>>();

        assert_eq!(y.len(), 2);

        for pair in y {
            let (s, e) = (pair.start(), pair.end());
            assert_eq!(s.dir, Direction::In);
            assert_eq!(e.dir, Direction::Out);
        }
    }

    #[test]
    fn iter_in_invalid_test() {
        let a = TimeEntry::new(Direction::In, now(), "In");
        let v = vec![a];
        let y = timeentry_pairs(v.into_iter()).collect::<Vec<TimeEntryPair>>();

        assert_eq!(y.len(), 1);

        for pair in y {
            let (s, e) = (pair.start(), pair.end());
            assert_eq!(s.dir, Direction::In);
            assert_eq!(e.dir, Direction::Out);
            assert_eq!(e.memo, "Still clocked in.");
        }
    }

    #[test]
    fn iter_in_out_test() {
        let a = TimeEntry::new(Direction::In, now(), "In");
        let b = TimeEntry::new(Direction::Out, now(), "Out");
        let v = vec![a, b];
        let y = timeentry_pairs(v.into_iter()).collect::<Vec<TimeEntryPair>>();

        assert_eq!(y.len(), 1);

        for pair in y {
            let (s, e) = (pair.start(), pair.end());
            assert_eq!(s.dir, Direction::In);
            assert_eq!(e.dir, Direction::Out);
        }
    }

    #[test]
    fn iter_out_out_test() {
        let a = TimeEntry::new(Direction::Out, now(), "Out");
        let b = TimeEntry::new(Direction::Out, now(), "Out");
        let v = vec![a, b];
        let y = timeentry_pairs(v.into_iter()).collect::<Vec<TimeEntryPair>>();

        assert_eq!(y.len(), 2);

        for pair in y {
            let (s, e) = (pair.start(), pair.end());
            assert_eq!(s.dir, Direction::In);
            assert_eq!(e.dir, Direction::Out);
        }
    }

    #[test]
    fn iter_out_in_test() {
        let a = TimeEntry::new(Direction::Out, now(), "Out");
        let b = TimeEntry::new(Direction::In, now(), "In");
        let v = vec![a, b];
        let y = timeentry_pairs(v.into_iter()).collect::<Vec<TimeEntryPair>>();

        assert_eq!(y.len(), 2);

        for pair in y {
            let (s, e) = (pair.start(), pair.end());
            assert_eq!(s.dir, Direction::In);
            assert_eq!(e.dir, Direction::Out);
        }
    }

    #[test]
    fn iter_out_invalid_test() {
        let a = TimeEntry::new(Direction::Out, now(), "Out");
        let v = vec![a];
        let y = timeentry_pairs(v.into_iter()).collect::<Vec<TimeEntryPair>>();

        assert_eq!(y.len(), 1);

        for pair in y {
            let (s, e) = (pair.start(), pair.end());
            assert_eq!(s.dir, Direction::In);
            assert_eq!(e.dir, Direction::Out);
        }
    }

    #[test]
    fn iter_invalid_invalid_test() {
        let v: Vec<TimeEntry> = Vec::with_capacity(0);
        let y = timeentry_pairs(v.into_iter()).collect::<Vec<TimeEntryPair>>();

        assert_eq!(y.len(), 0);
    }

}<commit_msg>Fixed missing "Still clocked in" message<commit_after>use std::convert::From;
use std::iter::Iterator;
use std::mem;
use timeclock::{TimeEntry, now};
use timeclock::direction::Direction;

/// Option-like enclosure for TimeEntrys
#[derive(Debug,PartialEq)]
enum TimeEntryOpt {
    In(TimeEntry),
    Out(TimeEntry),
    Invalid,
}

impl TimeEntryOpt {
    /// Take the TimeEntry variant and leave Invalid in it's place
    pub fn take(&mut self) -> TimeEntryOpt {
        mem::replace(self, TimeEntryOpt::Invalid)
    }
}

impl From<Option<TimeEntry>> for TimeEntryOpt {
    fn from(opt: Option<TimeEntry>) -> Self {
        let te = match opt {
            Some(te) => te,
            None => {
                return TimeEntryOpt::Invalid;
            }
        };
        match te.dir {
            Direction::In => TimeEntryOpt::In(te),
            Direction::Out => TimeEntryOpt::Out(te),
        }
    }
}

impl From<TimeEntry> for TimeEntryOpt {
    fn from(te: TimeEntry) -> Self {
        match te.dir {
            Direction::In => TimeEntryOpt::In(te),
            Direction::Out => TimeEntryOpt::Out(te),
        }
    }
}

#[derive(Debug,PartialEq)]
pub struct TimeEntryPair {
    start: TimeEntry,
    end: TimeEntry,
}

impl TimeEntryPair {
    pub fn new(s: TimeEntry, e: TimeEntry) -> Self {
        TimeEntryPair { start: s, end: e }
    }

    pub fn start(&self) -> &TimeEntry {
        &self.start
    }

    pub fn end(&self) -> &TimeEntry {
        &self.end
    }
}

/// Iterator to create TimeEntryPairs from TimeEntrys
pub struct TimeEntryPairsIter<I> {
    buf: TimeEntryOpt,
    v: I,
}

/// Constructor for TimeEntryPairsIter
pub fn timeentry_pairs<I>(entries: I) -> TimeEntryPairsIter<I>
    where I: Iterator<Item = TimeEntry>
{
    TimeEntryPairsIter {
        buf: TimeEntryOpt::Invalid,
        v: entries.into_iter(),
    }
}

impl<I> Iterator for TimeEntryPairsIter<I>
    where I: Iterator<Item = TimeEntry>
{
    type Item = TimeEntryPair;
    fn next(&mut self) -> Option<TimeEntryPair> {
        if self.buf == TimeEntryOpt::Invalid {
            self.buf = TimeEntryOpt::from(self.v.next());
        }

        let nxt = TimeEntryOpt::from(self.v.next());
        match (self.buf.take(), nxt) {
            (TimeEntryOpt::In(start), TimeEntryOpt::In(saved)) => {
                let mut end = start.clone();
                end.dir = Direction::Out;
                end.memo = String::from("Missing clock out.");
                self.buf = TimeEntryOpt::from(saved);
                Some(TimeEntryPair::new(start, end))
            }
            (TimeEntryOpt::In(start), TimeEntryOpt::Invalid) => {
                let end =
                    TimeEntry::new(Direction::Out, now(), "Still clocked in.");
                Some(TimeEntryPair::new(start, end))
            }
            (TimeEntryOpt::In(start), TimeEntryOpt::Out(end)) => {
                Some(TimeEntryPair::new(start, end))
            }
            (TimeEntryOpt::Out(end), TimeEntryOpt::Out(saved)) => {
                let mut start = end.clone();
                start.dir = Direction::In;
                start.memo = String::from("Missing clock in.");
                self.buf = TimeEntryOpt::from(saved);
                Some(TimeEntryPair::new(start, end))
            }
            (TimeEntryOpt::Out(end), TimeEntryOpt::In(saved)) => {
                let mut start = end.clone();
                start.dir = Direction::In;
                start.memo = String::from("Missing clock in.");
                self.buf = TimeEntryOpt::from(saved);
                Some(TimeEntryPair::new(start, end))
            }
            (TimeEntryOpt::Out(end), TimeEntryOpt::Invalid) => {
                let mut start = end.clone();
                start.dir = Direction::In;
                start.memo = String::from("Missing clock in.");
                Some(TimeEntryPair::new(start, end))
            }
            (TimeEntryOpt::Invalid, TimeEntryOpt::Out(_)) => unreachable!(),
            (TimeEntryOpt::Invalid, TimeEntryOpt::In(_)) => unreachable!(),
            (TimeEntryOpt::Invalid, TimeEntryOpt::Invalid) => None,
        }
    }
}

#[cfg(test)]
mod tests {
    use super::*;
    use timeclock::direction::Direction;
    use timeclock::now;
    use timeclock::timeentry::TimeEntry;

    #[test]
    fn iter_in_in_test() {
        let a = TimeEntry::new(Direction::In, now(), "In");
        let b = TimeEntry::new(Direction::In, now(), "In");
        let v = vec![a, b];
        let y = timeentry_pairs(v.into_iter()).collect::<Vec<TimeEntryPair>>();

        assert_eq!(y.len(), 2);

        for pair in y {
            let (s, e) = (pair.start(), pair.end());
            assert_eq!(s.dir, Direction::In);
            assert_eq!(e.dir, Direction::Out);
        }
    }

    #[test]
    fn iter_in_invalid_test() {
        let a = TimeEntry::new(Direction::In, now(), "In");
        let v = vec![a];
        let y = timeentry_pairs(v.into_iter()).collect::<Vec<TimeEntryPair>>();

        assert_eq!(y.len(), 1);

        for pair in y {
            let (s, e) = (pair.start(), pair.end());
            assert_eq!(s.dir, Direction::In);
            assert_eq!(e.dir, Direction::Out);
            assert_eq!(e.memo, "Still clocked in.");
        }
    }

    #[test]
    fn iter_in_out_test() {
        let a = TimeEntry::new(Direction::In, now(), "In");
        let b = TimeEntry::new(Direction::Out, now(), "Out");
        let v = vec![a, b];
        let y = timeentry_pairs(v.into_iter()).collect::<Vec<TimeEntryPair>>();

        assert_eq!(y.len(), 1);

        for pair in y {
            let (s, e) = (pair.start(), pair.end());
            assert_eq!(s.dir, Direction::In);
            assert_eq!(e.dir, Direction::Out);
        }
    }

    #[test]
    fn iter_out_out_test() {
        let a = TimeEntry::new(Direction::Out, now(), "Out");
        let b = TimeEntry::new(Direction::Out, now(), "Out");
        let v = vec![a, b];
        let y = timeentry_pairs(v.into_iter()).collect::<Vec<TimeEntryPair>>();

        assert_eq!(y.len(), 2);

        for pair in y {
            let (s, e) = (pair.start(), pair.end());
            assert_eq!(s.dir, Direction::In);
            assert_eq!(e.dir, Direction::Out);
        }
    }

    #[test]
    fn iter_out_in_test() {
        let a = TimeEntry::new(Direction::Out, now(), "Out");
        let b = TimeEntry::new(Direction::In, now(), "In");
        let v = vec![a, b];
        let y = timeentry_pairs(v.into_iter()).collect::<Vec<TimeEntryPair>>();

        assert_eq!(y.len(), 2);

        for pair in y {
            let (s, e) = (pair.start(), pair.end());
            assert_eq!(s.dir, Direction::In);
            assert_eq!(e.dir, Direction::Out);
        }
    }

    #[test]
    fn iter_out_invalid_test() {
        let a = TimeEntry::new(Direction::Out, now(), "Out");
        let v = vec![a];
        let y = timeentry_pairs(v.into_iter()).collect::<Vec<TimeEntryPair>>();

        assert_eq!(y.len(), 1);

        for pair in y {
            let (s, e) = (pair.start(), pair.end());
            assert_eq!(s.dir, Direction::In);
            assert_eq!(e.dir, Direction::Out);
        }
    }

    #[test]
    fn iter_invalid_invalid_test() {
        let v: Vec<TimeEntry> = Vec::with_capacity(0);
        let y = timeentry_pairs(v.into_iter()).collect::<Vec<TimeEntryPair>>();

        assert_eq!(y.len(), 0);
    }

}<|endoftext|>


<commit_before>use std::env;
use std::path::Path;
use std::process::{Command, Output};

fn check_html_file(file: &Path) -> usize {
    let to_mute = &[
        // "disabled" on <link> or "autocomplete" on <select> emit this warning
        "PROPRIETARY_ATTRIBUTE",
        // It complains when multiple in the same page link to the same anchor for some reason...
        "ANCHOR_NOT_UNIQUE",
        // If a  contains only HTML elements and no text, it complains about it.
        "TRIM_EMPTY_ELEMENT",
        // FIXME: the three next warnings are about
 elements which are not supposed to
        //        contain HTML. The solution here would be to replace them with a

        "MISSING_ENDTAG_BEFORE",
        "INSERTING_TAG",
        "DISCARDING_UNEXPECTED",
        // This error is caused by nesting the Notable Traits tooltip within an
 tag.
        // The solution is to avoid doing that, but we need to have the
 tags for accessibility
        // reasons, and we need the Notable Traits tooltip to help everyone understand the Iterator
        // combinators
        "TAG_NOT_ALLOWED_IN",
    ];
    let to_mute_s = to_mute.join(",");
    let mut command = Command::new("tidy");
    command
        .arg("-errors")
        .arg("-quiet")
        .arg("--mute-id") // this option is useful in case we want to mute more warnings
        .arg("yes")
        .arg("--mute")
        .arg(&to_mute_s)
        .arg(file);

    let Output { status, stderr, .. } = command.output().expect("failed to run tidy command");
    if status.success() {
        0
    } else {
        let stderr = String::from_utf8(stderr).expect("String::from_utf8 failed...");
        if stderr.is_empty() && status.code() != Some(2) {
            0
        } else {
            eprintln!(
                "=> Errors for `{}` (error code: {}) <=",
                file.display(),
                status.code().unwrap_or(-1)
            );
            eprintln!("{}", stderr);
            stderr.lines().count()
        }
    }
}

const DOCS_TO_CHECK: &[&str] =
    &["alloc", "core", "proc_macro", "implementors", "src", "std", "test"];

// Returns the number of files read and the number of errors.
fn find_all_html_files(dir: &Path) -> (usize, usize) {
    let mut files_read = 0;
    let mut errors = 0;

    for entry in walkdir::WalkDir::new(dir).into_iter().filter_entry(|e| {
        e.depth() != 1
            || e.file_name()
                .to_str()
                .map(|s| DOCS_TO_CHECK.into_iter().any(|d| *d == s))
                .unwrap_or(false)
    }) {
        let entry = entry.expect("failed to read file");
        if !entry.file_type().is_file() {
            continue;
        }
        let entry = entry.path();
        if entry.extension().and_then(|s| s.to_str()) == Some("html") {
            errors += check_html_file(&entry);
            files_read += 1;
        }
    }
    (files_read, errors)
}

fn main() -> Result<(), String> {
    let args = env::args().collect::<Vec<_>>();
    if args.len() != 2 {
        return Err(format!("Usage: {} <doc folder>", args[0]));
    }

    println!("Running HTML checker...");

    let (files_read, errors) = find_all_html_files(&Path::new(&args[1]));
    println!("Done! Read {} files...", files_read);
    if errors > 0 {
        Err(format!("HTML check failed: {} errors", errors))
    } else {
        println!("No error found!");
        Ok(())
    }
}
<commit_msg>Auto merge of #90663 - ken-matsui:support-early-stopping-too-old-tidy-for-macos, r=Mark-Simulacrum<commit_after>use std::env;
use std::path::Path;
use std::process::{Command, Output};

fn check_html_file(file: &Path) -> usize {
    let to_mute = &[
        // "disabled" on <link> or "autocomplete" on <select> emit this warning
        "PROPRIETARY_ATTRIBUTE",
        // It complains when multiple in the same page link to the same anchor for some reason...
        "ANCHOR_NOT_UNIQUE",
        // If a  contains only HTML elements and no text, it complains about it.
        "TRIM_EMPTY_ELEMENT",
        // FIXME: the three next warnings are about
 elements which are not supposed to
        //        contain HTML. The solution here would be to replace them with a

        "MISSING_ENDTAG_BEFORE",
        "INSERTING_TAG",
        "DISCARDING_UNEXPECTED",
        // This error is caused by nesting the Notable Traits tooltip within an
 tag.
        // The solution is to avoid doing that, but we need to have the
 tags for accessibility
        // reasons, and we need the Notable Traits tooltip to help everyone understand the Iterator
        // combinators
        "TAG_NOT_ALLOWED_IN",
    ];
    let to_mute_s = to_mute.join(",");
    let mut command = Command::new("tidy");
    command
        .arg("-errors")
        .arg("-quiet")
        .arg("--mute-id") // this option is useful in case we want to mute more warnings
        .arg("yes")
        .arg("--mute")
        .arg(&to_mute_s)
        .arg(file);

    let Output { status, stderr, .. } = command.output().expect("failed to run tidy command");
    if status.success() {
        0
    } else {
        let stderr = String::from_utf8(stderr).expect("String::from_utf8 failed...");
        if stderr.is_empty() && status.code() != Some(2) {
            0
        } else {
            eprintln!(
                "=> Errors for `{}` (error code: {}) <=",
                file.display(),
                status.code().unwrap_or(-1)
            );
            eprintln!("{}", stderr);
            stderr.lines().count()
        }
    }
}

const DOCS_TO_CHECK: &[&str] =
    &["alloc", "core", "proc_macro", "implementors", "src", "std", "test"];

// Returns the number of files read and the number of errors.
fn find_all_html_files(dir: &Path) -> (usize, usize) {
    let mut files_read = 0;
    let mut errors = 0;

    for entry in walkdir::WalkDir::new(dir).into_iter().filter_entry(|e| {
        e.depth() != 1
            || e.file_name()
                .to_str()
                .map(|s| DOCS_TO_CHECK.into_iter().any(|d| *d == s))
                .unwrap_or(false)
    }) {
        let entry = entry.expect("failed to read file");
        if !entry.file_type().is_file() {
            continue;
        }
        let entry = entry.path();
        if entry.extension().and_then(|s| s.to_str()) == Some("html") {
            errors += check_html_file(&entry);
            files_read += 1;
        }
    }
    (files_read, errors)
}

/// Default `tidy` command for macOS is too old that it does not have `mute-id` and `mute` options.
/// `tidy` on macOS Monterey was released on 31 October 2006, and the same date can be seen seven
/// years ago at <https://stackoverflow.com/questions/22283382/overwrite-osx-tidy>. Accordingly,
/// the macOS environment using pre-installed `tidy` should immediately suspend HTML checker process
/// and show a hint to install a newer one.
#[cfg(target_os = "macos")]
fn check_tidy_version() -> Result<(), String> {
    let output = Command::new("tidy").arg("-v").output().expect("failed to run tidy command");
    let version = String::from_utf8(output.stdout).expect("failed to read version of tidy command");
    if version.contains("HTML Tidy for Mac OS X released on 31 October 2006") {
        eprintln!("The pre-installed HTML Tidy for macOS is not supported.");
        eprintln!("Consider installing a newer one and re-running.");
        eprintln!("If you're using Homebrew, you can install it by the following command:");
        eprintln!("    brew install tidy-html5");
        eprintln!();
        Err("HTML check failed: 1 error".to_string())
    } else {
        Ok(())
    }
}

fn main() -> Result<(), String> {
    let args = env::args().collect::<Vec<_>>();
    if args.len() != 2 {
        return Err(format!("Usage: {} <doc folder>", args[0]));
    }
    #[cfg(target_os = "macos")]
    check_tidy_version()?;

    println!("Running HTML checker...");

    let (files_read, errors) = find_all_html_files(&Path::new(&args[1]));
    println!("Done! Read {} files...", files_read);
    if errors > 0 {
        Err(format!("HTML check failed: {} errors", errors))
    } else {
        println!("No error found!");
        Ok(())
    }
}
<|endoftext|>


<commit_before>//! #Introduction
//! This crate focuses on geting system information.
//!
//! For now it supports Linux, Mac OS X and Windows.
//! And now it can get information of kernel/cpu/memory/disk/load/hostname and so on.
//!

extern crate libc;

use std::ffi;
use std::fmt;
use std::io::{self, Read};
use std::fs::File;
use std::os::raw::c_char;

#[cfg(target_os = "macos")]
use libc::sysctl;
use libc::timeval;
use std::mem::size_of_val;
use std::ptr::null_mut;

use std::collections::HashMap;

static MAC_CTL_KERN: libc::c_int = 1;
static MAC_KERN_BOOTTIME: libc::c_int = 21;

/// System load average value.
#[repr(C)]
#[derive(Debug)]
pub struct LoadAvg {
    /// Average load within one minite.
    pub one: f64,
    /// Average load within five minites.
    pub five: f64,
    /// Average load within fifteen minites.
    pub fifteen: f64,
}

/// System memory information.
#[repr(C)]
#[derive(Debug)]
pub struct MemInfo {
    /// Total physical memory.
    pub total: u64,
    pub free: u64,
    pub avail: u64,

    pub buffers: u64,
    pub cached: u64,

    /// Total swap memory.
    pub swap_total: u64,
    pub swap_free: u64,
}

/// Disk information.
#[repr(C)]
#[derive(Debug)]
pub struct DiskInfo {
    pub total: u64,
    pub free: u64,
}

/// Error types
#[derive(Debug)]
pub enum Error {
    UnsupportedSystem,
    ExecFailed(io::Error),
    IO(io::Error),
    Unknown,
}

impl fmt::Display for Error {
    fn fmt(&self, fmt: &mut fmt::Formatter) -> fmt::Result {
        use self::Error::*;
        match *self {
            UnsupportedSystem => write!(fmt, "System is not supported"),
            ExecFailed(ref e) => write!(fmt, "Execution failed: {}", e),
            IO(ref e) => write!(fmt, "IO error: {}", e),
            Unknown => write!(fmt, "An unknown error occurred"),
        }
    }
}

impl std::error::Error for Error {
    fn description(&self) -> &str {
        use self::Error::*;
        match *self {
            UnsupportedSystem => "unsupported system",
            ExecFailed(_) => "execution failed",
            IO(_) => "io error",
            Unknown => "unknown error",
        }
    }

    fn cause(&self) -> Option<&std::error::Error> {
        use self::Error::*;
        match *self {
            UnsupportedSystem => None,
            ExecFailed(ref e) => Some(e),
            IO(ref e) => Some(e),
            Unknown => None,
        }
    }
}

impl From<io::Error> for Error {
    fn from(e: io::Error) -> Error {
        Error::IO(e)
    }
}

extern "C" {
    fn get_os_type() -> *const i8;
    fn get_os_release() -> *const i8;

    fn get_cpu_num() -> u32;
    fn get_cpu_speed() -> u64;

    fn get_loadavg() -> LoadAvg;
    fn get_proc_total() -> u64;

    fn get_mem_info() -> MemInfo;
    fn get_disk_info() -> DiskInfo;
}

/// Get operation system type.
///
/// Such as "Linux", "Darwin", "Windows".
pub fn os_type() -> Result<String, Error> {
    if cfg!(target_os = "linux") {
        let mut s = String::new();
        File::open("/proc/sys/kernel/ostype")?.read_to_string(&mut s)?;
        s.pop(); // pop '\n'
        Ok(s)
    } else if cfg!(target_os = "macos") || cfg!(target_os = "windows") {
        let typ = unsafe { ffi::CStr::from_ptr(get_os_type() as *const c_char).to_bytes() };
        Ok(String::from_utf8_lossy(typ).into_owned())
    } else {
        Err(Error::UnsupportedSystem)
    }
}

/// Get operation system release version.
///
/// Such as "3.19.0-gentoo"
pub fn os_release() -> Result<String, Error> {
    if cfg!(target_os = "linux") {
        let mut s = String::new();
        File::open("/proc/sys/kernel/osrelease")?.read_to_string(&mut s)?;
        s.pop(); // pop '\n'
        Ok(s)
    } else if cfg!(target_os = "macos") || cfg!(target_os = "windows") {
        let typ = unsafe { ffi::CStr::from_ptr(get_os_release() as *const c_char).to_bytes() };
        Ok(String::from_utf8_lossy(typ).into_owned())
    } else {
        Err(Error::UnsupportedSystem)
    }
}

/// Get cpu num quantity.
///
/// Notice, it returns the logical cpu quantity.
pub fn cpu_num() -> Result<u32, Error> {
    if cfg!(unix) || cfg!(windows) {
        unsafe { Ok(get_cpu_num()) }
    } else {
        Err(Error::UnsupportedSystem)
    }
}

/// Get cpu speed.
///
/// Such as 2500, that is 2500 MHz.
pub fn cpu_speed() -> Result<u64, Error> {
    if cfg!(target_os = "linux") {
        // /sys/devices/system/cpu/cpu0/cpufreq/cpuinfo_cur_freq
        let mut s = String::new();
        File::open("/proc/cpuinfo")?.read_to_string(&mut s)?;

        s.split('\n')
            .find(|line| line.starts_with("cpu MHz"))
            .and_then(|line| line.split(':').last())
            .and_then(|val| val.trim().parse::<f64>().ok())
            .map(|speed| speed as u64)
            .ok_or(Error::Unknown)
    } else if cfg!(target_os = "macos") || cfg!(target_os = "windows") {
        unsafe { Ok(get_cpu_speed()) }
    } else {
        Err(Error::UnsupportedSystem)
    }
}

/// Get system load average value.
///
/// Notice, on windows, one/five/fifteen of the LoadAvg returned are the current load.
pub fn loadavg() -> Result<LoadAvg, Error> {
    if cfg!(target_os = "linux") {
        let mut s = String::new();
        File::open("/proc/loadavg")?.read_to_string(&mut s)?;
        let loads = s.trim().split(' ')
            .take(3)
            .map(|val| val.parse::<f64>().unwrap())
            .collect::<Vec<f64>>();
        Ok(LoadAvg {
            one: loads[0],
            five: loads[1],
            fifteen: loads[2],
        })
    } else if cfg!(target_os = "macos") || cfg!(target_os = "windows") {
        Ok(unsafe { get_loadavg() })
    } else {
        Err(Error::UnsupportedSystem)
    }
}

/// Get current processes quantity.
///
/// Notice, it temporarily does not support Windows.
pub fn proc_total() -> Result<u64, Error> {
    if cfg!(target_os = "linux") {
        let mut s = String::new();
        File::open("/proc/loadavg")?.read_to_string(&mut s)?;
        s.split(' ')
            .nth(3)
            .and_then(|val| val.split('/').last())
            .and_then(|val| val.parse::<u64>().ok())
            .ok_or(Error::Unknown)
    } else if cfg!(target_os = "macos") || cfg!(target_os = "windows") {
        Ok(unsafe { get_proc_total() })
    } else {
        Err(Error::UnsupportedSystem)
    }
}

/// Get memory information.
///
/// On Mac OS X and Windows, the buffers and cached variables of the MemInfo returned are zero.
pub fn mem_info() -> Result<MemInfo, Error> {
    if cfg!(target_os = "linux") {
        let mut s = String::new();
        File::open("/proc/meminfo")?.read_to_string(&mut s)?;
        let mut meminfo_hashmap = HashMap::new();
        for line in s.lines() {
            let mut split_line = line.split_whitespace();
            let label = split_line.next();
            let value = split_line.next();
            if value.is_some() && label.is_some() {
                let label = label.unwrap().split(":").nth(0).ok_or(Error::Unknown)?;
                let value = value.unwrap().parse::<u64>().ok().ok_or(Error::Unknown)?;
                meminfo_hashmap.insert(label, value);
            }
        }
        Ok(MemInfo {
            total: *meminfo_hashmap.get("MemTotal").ok_or(Error::Unknown)?,
            free: *meminfo_hashmap.get("MemFree").ok_or(Error::Unknown)?,
            avail: *meminfo_hashmap.get("MemAvailable").ok_or(Error::Unknown)?,
            buffers: *meminfo_hashmap.get("Buffers").ok_or(Error::Unknown)?,
            cached: *meminfo_hashmap.get("Cached").ok_or(Error::Unknown)?,
            swap_total: *meminfo_hashmap.get("SwapTotal").ok_or(Error::Unknown)?,
            swap_free: *meminfo_hashmap.get("SwapFree").ok_or(Error::Unknown)?,
        })
    } else if cfg!(target_os = "macos") || cfg!(target_os = "windows") {
        Ok(unsafe { get_mem_info() })
    } else {
        Err(Error::UnsupportedSystem)
    }
}

/// Get disk information.
///
/// Notice, it just calculate current disk on Windows.
pub fn disk_info() -> Result<DiskInfo, Error> {
    if cfg!(target_os = "linux") || cfg!(target_os = "macos") || cfg!(target_os = "windows") {
        Ok(unsafe { get_disk_info() })
    } else {
        Err(Error::UnsupportedSystem)
    }
}

/// Get hostname.
pub fn hostname() -> Result<String, Error> {
    use std::process::Command;
    if cfg!(unix) {
        Command::new("hostname")
            .output()
            .map_err(|e| Error::ExecFailed(e))
            .map(|output| String::from_utf8(output.stdout).unwrap().trim().to_string())
    } else if cfg!(windows) {
        Command::new("hostname")
            .output()
            .map_err(|e| Error::ExecFailed(e))
            .map(|output| String::from_utf8(output.stdout).unwrap().trim().to_string())
    } else {
        Err(Error::UnsupportedSystem)
    }
}

/// Get system boottime
#[cfg(not(windows))]
pub fn boottime() -> Result<timeval, Error> {
    let mut bt = timeval {
        tv_sec: 0,
        tv_usec: 0
    };

    #[cfg(target_os = "linux")]
    {
        let mut s = String::new();
        File::open("/proc/uptime")?.read_to_string(&mut s)?;
        let secs = s.trim().split(' ')
            .take(2)
            .map(|val| val.parse::<f64>().unwrap())
            .collect::<Vec<f64>>();
        bt.tv_sec = secs[0] as libc::time_t;
        bt.tv_usec = secs[1] as libc::suseconds_t;
    }
    #[cfg(target_os = "macos")]
    {
        let mut mib = [MAC_CTL_KERN, MAC_KERN_BOOTTIME];
        let mut size: libc::size_t = size_of_val(&bt) as libc::size_t;
        unsafe {
            sysctl(&mut mib[0], 2,
                   &mut bt as *mut timeval as *mut libc::c_void,
                   &mut size, null_mut(), 0);
        }
    }

    Ok(bt)
}

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

    #[test]
    pub fn test_os_type() {
        let typ = os_type().unwrap();
        assert!(typ.len() > 0);
        println!("os_type(): {}", typ);
    }

    #[test]
    pub fn test_os_release() {
        let release = os_release().unwrap();
        assert!(release.len() > 0);
        println!("os_release(): {}", release);
    }

    #[test]
    pub fn test_cpu_num() {
        let num = cpu_num().unwrap();
        assert!(num > 0);
        println!("cpu_num(): {}", num);
    }

    #[test]
    pub fn test_cpu_speed() {
        let speed = cpu_speed().unwrap();
        assert!(speed > 0);
        println!("cpu_speed(): {}", speed);
    }

    #[test]
    pub fn test_loadavg() {
        let load = loadavg().unwrap();
        println!("loadavg(): {:?}", load);
    }

    #[test]
    pub fn test_proc_total() {
        let procs = proc_total().unwrap();
        assert!(procs > 0);
        println!("proc_total(): {}", procs);
    }

    #[test]
    pub fn test_mem_info() {
        let mem = mem_info().unwrap();
        assert!(mem.total > 0);
        println!("mem_info(): {:?}", mem);
    }

    #[test]
    pub fn test_disk_info() {
        let info = disk_info().unwrap();
        println!("disk_info(): {:?}", info);
    }

    #[test]
    pub fn test_hostname() {
        let host = hostname().unwrap();
        assert!(host.len() > 0);
        println!("hostname(): {}", host);
    }

    #[test]
    pub fn test_boottime() {
        let bt = boottime().unwrap();
        println!("boottime(): {} {}", bt.tv_sec, bt.tv_usec);
        assert!(bt.tv_sec > 0 || bt.tv_usec > 0);
    }
}
<commit_msg>fix issues/23 (#24)<commit_after>//! #Introduction
//! This crate focuses on geting system information.
//!
//! For now it supports Linux, Mac OS X and Windows.
//! And now it can get information of kernel/cpu/memory/disk/load/hostname and so on.
//!

extern crate libc;

use std::ffi;
use std::fmt;
use std::io::{self, Read};
use std::fs::File;
use std::os::raw::c_char;

#[cfg(target_os = "macos")]
use libc::sysctl;
use libc::timeval;
use std::mem::size_of_val;
use std::ptr::null_mut;

use std::collections::HashMap;

static MAC_CTL_KERN: libc::c_int = 1;
static MAC_KERN_BOOTTIME: libc::c_int = 21;

/// System load average value.
#[repr(C)]
#[derive(Debug)]
pub struct LoadAvg {
    /// Average load within one minite.
    pub one: f64,
    /// Average load within five minites.
    pub five: f64,
    /// Average load within fifteen minites.
    pub fifteen: f64,
}

/// System memory information.
#[repr(C)]
#[derive(Debug)]
pub struct MemInfo {
    /// Total physical memory.
    pub total: u64,
    pub free: u64,
    pub avail: u64,

    pub buffers: u64,
    pub cached: u64,

    /// Total swap memory.
    pub swap_total: u64,
    pub swap_free: u64,
}

/// Disk information.
#[repr(C)]
#[derive(Debug)]
pub struct DiskInfo {
    pub total: u64,
    pub free: u64,
}

/// Error types
#[derive(Debug)]
pub enum Error {
    UnsupportedSystem,
    ExecFailed(io::Error),
    IO(io::Error),
    Unknown,
}

impl fmt::Display for Error {
    fn fmt(&self, fmt: &mut fmt::Formatter) -> fmt::Result {
        use self::Error::*;
        match *self {
            UnsupportedSystem => write!(fmt, "System is not supported"),
            ExecFailed(ref e) => write!(fmt, "Execution failed: {}", e),
            IO(ref e) => write!(fmt, "IO error: {}", e),
            Unknown => write!(fmt, "An unknown error occurred"),
        }
    }
}

impl std::error::Error for Error {
    fn description(&self) -> &str {
        use self::Error::*;
        match *self {
            UnsupportedSystem => "unsupported system",
            ExecFailed(_) => "execution failed",
            IO(_) => "io error",
            Unknown => "unknown error",
        }
    }

    fn cause(&self) -> Option<&std::error::Error> {
        use self::Error::*;
        match *self {
            UnsupportedSystem => None,
            ExecFailed(ref e) => Some(e),
            IO(ref e) => Some(e),
            Unknown => None,
        }
    }
}

impl From<io::Error> for Error {
    fn from(e: io::Error) -> Error {
        Error::IO(e)
    }
}

extern "C" {
    fn get_os_type() -> *const i8;
    fn get_os_release() -> *const i8;

    fn get_cpu_num() -> u32;
    fn get_cpu_speed() -> u64;

    fn get_loadavg() -> LoadAvg;
    fn get_proc_total() -> u64;

    fn get_mem_info() -> MemInfo;
    fn get_disk_info() -> DiskInfo;
}

/// Get operation system type.
///
/// Such as "Linux", "Darwin", "Windows".
pub fn os_type() -> Result<String, Error> {
    if cfg!(target_os = "linux") {
        let mut s = String::new();
        File::open("/proc/sys/kernel/ostype")?.read_to_string(&mut s)?;
        s.pop(); // pop '\n'
        Ok(s)
    } else if cfg!(target_os = "macos") || cfg!(target_os = "windows") {
        let typ = unsafe { ffi::CStr::from_ptr(get_os_type() as *const c_char).to_bytes() };
        Ok(String::from_utf8_lossy(typ).into_owned())
    } else {
        Err(Error::UnsupportedSystem)
    }
}

/// Get operation system release version.
///
/// Such as "3.19.0-gentoo"
pub fn os_release() -> Result<String, Error> {
    if cfg!(target_os = "linux") {
        let mut s = String::new();
        File::open("/proc/sys/kernel/osrelease")?.read_to_string(&mut s)?;
        s.pop(); // pop '\n'
        Ok(s)
    } else if cfg!(target_os = "macos") || cfg!(target_os = "windows") {
        let typ = unsafe { ffi::CStr::from_ptr(get_os_release() as *const c_char).to_bytes() };
        Ok(String::from_utf8_lossy(typ).into_owned())
    } else {
        Err(Error::UnsupportedSystem)
    }
}

/// Get cpu num quantity.
///
/// Notice, it returns the logical cpu quantity.
pub fn cpu_num() -> Result<u32, Error> {
    if cfg!(unix) || cfg!(windows) {
        unsafe { Ok(get_cpu_num()) }
    } else {
        Err(Error::UnsupportedSystem)
    }
}

/// Get cpu speed.
///
/// Such as 2500, that is 2500 MHz.
pub fn cpu_speed() -> Result<u64, Error> {
    if cfg!(target_os = "linux") {
        // /sys/devices/system/cpu/cpu0/cpufreq/cpuinfo_cur_freq
        let mut s = String::new();
        File::open("/proc/cpuinfo")?.read_to_string(&mut s)?;

        let mut find_cpu_mhz = s.split('\n').find(|line| line.starts_with("cpu MHz"));
        match find_cpu_mhz {
            None => find_cpu_mhz = s.split('\n').find(|line| line.starts_with("BogoMIPS")),
            _ => {}
        }

        find_cpu_mhz.and_then(|line| line.split(':').last())
            .and_then(|val| val.trim().parse::<f64>().ok())
            .map(|speed| speed as u64)
            .ok_or(Error::Unknown)

    } else if cfg!(target_os = "macos") || cfg!(target_os = "windows") {
        unsafe { Ok(get_cpu_speed()) }
    } else {
        Err(Error::UnsupportedSystem)
    }
}

/// Get system load average value.
///
/// Notice, on windows, one/five/fifteen of the LoadAvg returned are the current load.
pub fn loadavg() -> Result<LoadAvg, Error> {
    if cfg!(target_os = "linux") {
        let mut s = String::new();
        File::open("/proc/loadavg")?.read_to_string(&mut s)?;
        let loads = s.trim().split(' ')
            .take(3)
            .map(|val| val.parse::<f64>().unwrap())
            .collect::<Vec<f64>>();
        Ok(LoadAvg {
            one: loads[0],
            five: loads[1],
            fifteen: loads[2],
        })
    } else if cfg!(target_os = "macos") || cfg!(target_os = "windows") {
        Ok(unsafe { get_loadavg() })
    } else {
        Err(Error::UnsupportedSystem)
    }
}

/// Get current processes quantity.
///
/// Notice, it temporarily does not support Windows.
pub fn proc_total() -> Result<u64, Error> {
    if cfg!(target_os = "linux") {
        let mut s = String::new();
        File::open("/proc/loadavg")?.read_to_string(&mut s)?;
        s.split(' ')
            .nth(3)
            .and_then(|val| val.split('/').last())
            .and_then(|val| val.parse::<u64>().ok())
            .ok_or(Error::Unknown)
    } else if cfg!(target_os = "macos") || cfg!(target_os = "windows") {
        Ok(unsafe { get_proc_total() })
    } else {
        Err(Error::UnsupportedSystem)
    }
}

/// Get memory information.
///
/// On Mac OS X and Windows, the buffers and cached variables of the MemInfo returned are zero.
pub fn mem_info() -> Result<MemInfo, Error> {
    if cfg!(target_os = "linux") {
        let mut s = String::new();
        File::open("/proc/meminfo")?.read_to_string(&mut s)?;
        let mut meminfo_hashmap = HashMap::new();
        for line in s.lines() {
            let mut split_line = line.split_whitespace();
            let label = split_line.next();
            let value = split_line.next();
            if value.is_some() && label.is_some() {
                let label = label.unwrap().split(":").nth(0).ok_or(Error::Unknown)?;
                let value = value.unwrap().parse::<u64>().ok().ok_or(Error::Unknown)?;
                meminfo_hashmap.insert(label, value);
            }
        }
        Ok(MemInfo {
            total: *meminfo_hashmap.get("MemTotal").ok_or(Error::Unknown)?,
            free: *meminfo_hashmap.get("MemFree").ok_or(Error::Unknown)?,
            avail: *meminfo_hashmap.get("MemAvailable").ok_or(Error::Unknown)?,
            buffers: *meminfo_hashmap.get("Buffers").ok_or(Error::Unknown)?,
            cached: *meminfo_hashmap.get("Cached").ok_or(Error::Unknown)?,
            swap_total: *meminfo_hashmap.get("SwapTotal").ok_or(Error::Unknown)?,
            swap_free: *meminfo_hashmap.get("SwapFree").ok_or(Error::Unknown)?,
        })
    } else if cfg!(target_os = "macos") || cfg!(target_os = "windows") {
        Ok(unsafe { get_mem_info() })
    } else {
        Err(Error::UnsupportedSystem)
    }
}

/// Get disk information.
///
/// Notice, it just calculate current disk on Windows.
pub fn disk_info() -> Result<DiskInfo, Error> {
    if cfg!(target_os = "linux") || cfg!(target_os = "macos") || cfg!(target_os = "windows") {
        Ok(unsafe { get_disk_info() })
    } else {
        Err(Error::UnsupportedSystem)
    }
}

/// Get hostname.
pub fn hostname() -> Result<String, Error> {
    use std::process::Command;
    if cfg!(unix) {
        Command::new("hostname")
            .output()
            .map_err(|e| Error::ExecFailed(e))
            .map(|output| String::from_utf8(output.stdout).unwrap().trim().to_string())
    } else if cfg!(windows) {
        Command::new("hostname")
            .output()
            .map_err(|e| Error::ExecFailed(e))
            .map(|output| String::from_utf8(output.stdout).unwrap().trim().to_string())
    } else {
        Err(Error::UnsupportedSystem)
    }
}

/// Get system boottime
#[cfg(not(windows))]
pub fn boottime() -> Result<timeval, Error> {
    let mut bt = timeval {
        tv_sec: 0,
        tv_usec: 0
    };

    #[cfg(target_os = "linux")]
    {
        let mut s = String::new();
        File::open("/proc/uptime")?.read_to_string(&mut s)?;
        let secs = s.trim().split(' ')
            .take(2)
            .map(|val| val.parse::<f64>().unwrap())
            .collect::<Vec<f64>>();
        bt.tv_sec = secs[0] as libc::time_t;
        bt.tv_usec = secs[1] as libc::suseconds_t;
    }
    #[cfg(target_os = "macos")]
    {
        let mut mib = [MAC_CTL_KERN, MAC_KERN_BOOTTIME];
        let mut size: libc::size_t = size_of_val(&bt) as libc::size_t;
        unsafe {
            sysctl(&mut mib[0], 2,
                   &mut bt as *mut timeval as *mut libc::c_void,
                   &mut size, null_mut(), 0);
        }
    }

    Ok(bt)
}

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

    #[test]
    pub fn test_os_type() {
        let typ = os_type().unwrap();
        assert!(typ.len() > 0);
        println!("os_type(): {}", typ);
    }

    #[test]
    pub fn test_os_release() {
        let release = os_release().unwrap();
        assert!(release.len() > 0);
        println!("os_release(): {}", release);
    }

    #[test]
    pub fn test_cpu_num() {
        let num = cpu_num().unwrap();
        assert!(num > 0);
        println!("cpu_num(): {}", num);
    }

    #[test]
    pub fn test_cpu_speed() {
        let speed = cpu_speed().unwrap();
        assert!(speed > 0);
        println!("cpu_speed(): {}", speed);
    }

    #[test]
    pub fn test_loadavg() {
        let load = loadavg().unwrap();
        println!("loadavg(): {:?}", load);
    }

    #[test]
    pub fn test_proc_total() {
        let procs = proc_total().unwrap();
        assert!(procs > 0);
        println!("proc_total(): {}", procs);
    }

    #[test]
    pub fn test_mem_info() {
        let mem = mem_info().unwrap();
        assert!(mem.total > 0);
        println!("mem_info(): {:?}", mem);
    }

    #[test]
    pub fn test_disk_info() {
        let info = disk_info().unwrap();
        println!("disk_info(): {:?}", info);
    }

    #[test]
    pub fn test_hostname() {
        let host = hostname().unwrap();
        assert!(host.len() > 0);
        println!("hostname(): {}", host);
    }

    #[test]
    pub fn test_boottime() {
        let bt = boottime().unwrap();
        println!("boottime(): {} {}", bt.tv_sec, bt.tv_usec);
        assert!(bt.tv_sec > 0 || bt.tv_usec > 0);
    }
}
<|endoftext|>


<commit_before>/// This function is where the kernel sets up IRQ handlers
/// It is increcibly unsafe, and should be minimal in nature
/// It must create the IDT with the correct entries, those entries are
/// defined in other files inside of the `arch` module

use core::sync::atomic::{AtomicBool, ATOMIC_BOOL_INIT, AtomicUsize, ATOMIC_USIZE_INIT, Ordering};

use acpi;
use allocator;
use device;
use externs::memset;
use gdt;
use idt;
use interrupt;
use memory::{self, Frame};
use paging::{self, entry, Page, PhysicalAddress, VirtualAddress};

/// Test of zero values in BSS.
static BSS_TEST_ZERO: usize = 0;
/// Test of non-zero values in data.
static DATA_TEST_NONZERO: usize = 0xFFFFFFFFFFFFFFFF;
/// Test of zero values in thread BSS
#[thread_local]
static mut TBSS_TEST_ZERO: usize = 0;
/// Test of non-zero values in thread data.
#[thread_local]
static mut TDATA_TEST_NONZERO: usize = 0xFFFFFFFFFFFFFFFF;

pub static AP_READY: AtomicBool = ATOMIC_BOOL_INIT;
static BSP_READY: AtomicBool = ATOMIC_BOOL_INIT;
static KERNEL_TABLE: AtomicUsize = ATOMIC_USIZE_INIT;

extern {
    /// Kernel main function
    fn kmain() -> !;
    /// Kernel main for APs
    fn kmain_ap(id: usize) -> !;
}

/// The entry to Rust, all things must be initialized
#[no_mangle]
pub unsafe extern fn kstart() -> ! {
    {
        extern {
            /// The starting byte of the _.bss_ (uninitialized data) segment.
            static mut __bss_start: u8;
            /// The ending byte of the _.bss_ (uninitialized data) segment.
            static mut __bss_end: u8;
            /// The end of the kernel
            static mut __end: u8;
        }

        // Zero BSS, this initializes statics that are set to 0
        {
            let start_ptr = &mut __bss_start as *mut u8;
            let end_ptr = & __bss_end as *const u8 as usize;

            if start_ptr as usize <= end_ptr {
                let size = end_ptr - start_ptr as usize;
                memset(start_ptr, 0, size);
            }

            assert_eq!(BSS_TEST_ZERO, 0);
            assert_eq!(DATA_TEST_NONZERO, 0xFFFFFFFFFFFFFFFF);
        }

        // Initialize memory management
        memory::init(0, &__end as *const u8 as usize - ::KERNEL_OFFSET);

        // TODO: allocate a stack
        let stack_start = 0x00080000;
        let stack_end = 0x0009F000;

        // Initialize paging
        let (mut active_table, tcb_offset) = paging::init(0, stack_start, stack_end);

        // Set up GDT
        gdt::init(tcb_offset, stack_end);

        // Set up IDT
        idt::init();

        // Test tdata and tbss
        {
            assert_eq!(TBSS_TEST_ZERO, 0);
            TBSS_TEST_ZERO += 1;
            assert_eq!(TBSS_TEST_ZERO, 1);
            assert_eq!(TDATA_TEST_NONZERO, 0xFFFFFFFFFFFFFFFF);
            TDATA_TEST_NONZERO -= 1;
            assert_eq!(TDATA_TEST_NONZERO, 0xFFFFFFFFFFFFFFFE);
        }

        // Reset AP variables
        AP_READY.store(false, Ordering::SeqCst);
        BSP_READY.store(false, Ordering::SeqCst);
        KERNEL_TABLE.store(0, Ordering::SeqCst);

        // Setup kernel heap
        {
            // Map heap pages
            let heap_start_page = Page::containing_address(VirtualAddress::new(::KERNEL_HEAP_OFFSET));
            let heap_end_page = Page::containing_address(VirtualAddress::new(::KERNEL_HEAP_OFFSET + ::KERNEL_HEAP_SIZE-1));
            for page in Page::range_inclusive(heap_start_page, heap_end_page) {
                active_table.map(page, entry::WRITABLE | entry::NO_EXECUTE);
            }

            // Init the allocator
            allocator::init(::KERNEL_HEAP_OFFSET, ::KERNEL_HEAP_SIZE);
        }

        // Initialize devices
        device::init(&mut active_table);

        // Send kernel page table to APs
        {
            let index = Page::containing_address(VirtualAddress::new(::KERNEL_OFFSET)).p4_index();

            let p4 = active_table.p4();
            {
                let entry = &p4[index];
                if let Some(frame) = entry.pointed_frame() {
                    KERNEL_TABLE.store(frame.start_address().get(), Ordering::SeqCst);
                } else {
                    panic!("kernel does not have PML4 entry");
                }
            }
        }

        // Read ACPI tables, starts APs
        acpi::init(&mut active_table);

        BSP_READY.store(true, Ordering::SeqCst);
    }

    kmain();
}

/// Entry to rust for an AP
pub unsafe extern fn kstart_ap(cpu_id: usize, page_table: usize, stack_start: usize, stack_end: usize) -> ! {
    {
        assert_eq!(BSS_TEST_ZERO, 0);
        assert_eq!(DATA_TEST_NONZERO, 0xFFFFFFFFFFFFFFFF);

        // Retrieve kernel table entry
        while KERNEL_TABLE.load(Ordering::SeqCst) == 0 {
            interrupt::pause();
        }
        let kernel_table = KERNEL_TABLE.load(Ordering::SeqCst);

        // Initialize paging
        let (mut active_table, tcb_offset) = paging::init_ap(cpu_id, stack_start, stack_end, kernel_table);

        // Set up GDT for AP
        gdt::init(tcb_offset, stack_end);

        // Set up IDT for AP
        idt::init();

        // Test tdata and tbss
        {
            assert_eq!(TBSS_TEST_ZERO, 0);
            TBSS_TEST_ZERO += 1;
            assert_eq!(TBSS_TEST_ZERO, 1);
            assert_eq!(TDATA_TEST_NONZERO, 0xFFFFFFFFFFFFFFFF);
            TDATA_TEST_NONZERO -= 1;
            assert_eq!(TDATA_TEST_NONZERO, 0xFFFFFFFFFFFFFFFE);
        }

        AP_READY.store(true, Ordering::SeqCst);
    }

    while ! BSP_READY.load(Ordering::SeqCst) {
        interrupt::pause();
    }

    kmain_ap(cpu_id);
}

pub unsafe fn usermode(ip: usize, sp: usize) -> ! {
    // Go to usermode
    asm!("xchg bx, bx
        mov ds, ax
        mov es, ax
        mov fs, ax
        mov gs, ax
        push rax
        push rbx
        push rcx
        push rdx
        push rsi
        iretq"
        : // No output because it never returns
        :   "{rax}"(gdt::GDT_USER_DATA << 3 | 3), // Stack segment
            "{rbx}"(sp), // Stack pointer
            "{rcx}"(3 << 12/* | 1 << 9*/), // Flags - Set IOPL and interrupt enable flag
            "{rdx}"(gdt::GDT_USER_CODE << 3 | 3), // Code segment
            "{rsi}"(ip) // IP
        : // No clobers because it never returns
        : "intel", "volatile");
    unreachable!();
}
<commit_msg>Remove debug breaks<commit_after>/// This function is where the kernel sets up IRQ handlers
/// It is increcibly unsafe, and should be minimal in nature
/// It must create the IDT with the correct entries, those entries are
/// defined in other files inside of the `arch` module

use core::sync::atomic::{AtomicBool, ATOMIC_BOOL_INIT, AtomicUsize, ATOMIC_USIZE_INIT, Ordering};

use acpi;
use allocator;
use device;
use externs::memset;
use gdt;
use idt;
use interrupt;
use memory::{self, Frame};
use paging::{self, entry, Page, PhysicalAddress, VirtualAddress};

/// Test of zero values in BSS.
static BSS_TEST_ZERO: usize = 0;
/// Test of non-zero values in data.
static DATA_TEST_NONZERO: usize = 0xFFFFFFFFFFFFFFFF;
/// Test of zero values in thread BSS
#[thread_local]
static mut TBSS_TEST_ZERO: usize = 0;
/// Test of non-zero values in thread data.
#[thread_local]
static mut TDATA_TEST_NONZERO: usize = 0xFFFFFFFFFFFFFFFF;

pub static AP_READY: AtomicBool = ATOMIC_BOOL_INIT;
static BSP_READY: AtomicBool = ATOMIC_BOOL_INIT;
static KERNEL_TABLE: AtomicUsize = ATOMIC_USIZE_INIT;

extern {
    /// Kernel main function
    fn kmain() -> !;
    /// Kernel main for APs
    fn kmain_ap(id: usize) -> !;
}

/// The entry to Rust, all things must be initialized
#[no_mangle]
pub unsafe extern fn kstart() -> ! {
    {
        extern {
            /// The starting byte of the _.bss_ (uninitialized data) segment.
            static mut __bss_start: u8;
            /// The ending byte of the _.bss_ (uninitialized data) segment.
            static mut __bss_end: u8;
            /// The end of the kernel
            static mut __end: u8;
        }

        // Zero BSS, this initializes statics that are set to 0
        {
            let start_ptr = &mut __bss_start as *mut u8;
            let end_ptr = & __bss_end as *const u8 as usize;

            if start_ptr as usize <= end_ptr {
                let size = end_ptr - start_ptr as usize;
                memset(start_ptr, 0, size);
            }

            assert_eq!(BSS_TEST_ZERO, 0);
            assert_eq!(DATA_TEST_NONZERO, 0xFFFFFFFFFFFFFFFF);
        }

        // Initialize memory management
        memory::init(0, &__end as *const u8 as usize - ::KERNEL_OFFSET);

        // TODO: allocate a stack
        let stack_start = 0x00080000;
        let stack_end = 0x0009F000;

        // Initialize paging
        let (mut active_table, tcb_offset) = paging::init(0, stack_start, stack_end);

        // Set up GDT
        gdt::init(tcb_offset, stack_end);

        // Set up IDT
        idt::init();

        // Test tdata and tbss
        {
            assert_eq!(TBSS_TEST_ZERO, 0);
            TBSS_TEST_ZERO += 1;
            assert_eq!(TBSS_TEST_ZERO, 1);
            assert_eq!(TDATA_TEST_NONZERO, 0xFFFFFFFFFFFFFFFF);
            TDATA_TEST_NONZERO -= 1;
            assert_eq!(TDATA_TEST_NONZERO, 0xFFFFFFFFFFFFFFFE);
        }

        // Reset AP variables
        AP_READY.store(false, Ordering::SeqCst);
        BSP_READY.store(false, Ordering::SeqCst);
        KERNEL_TABLE.store(0, Ordering::SeqCst);

        // Setup kernel heap
        {
            // Map heap pages
            let heap_start_page = Page::containing_address(VirtualAddress::new(::KERNEL_HEAP_OFFSET));
            let heap_end_page = Page::containing_address(VirtualAddress::new(::KERNEL_HEAP_OFFSET + ::KERNEL_HEAP_SIZE-1));
            for page in Page::range_inclusive(heap_start_page, heap_end_page) {
                active_table.map(page, entry::WRITABLE | entry::NO_EXECUTE);
            }

            // Init the allocator
            allocator::init(::KERNEL_HEAP_OFFSET, ::KERNEL_HEAP_SIZE);
        }

        // Initialize devices
        device::init(&mut active_table);

        // Send kernel page table to APs
        {
            let index = Page::containing_address(VirtualAddress::new(::KERNEL_OFFSET)).p4_index();

            let p4 = active_table.p4();
            {
                let entry = &p4[index];
                if let Some(frame) = entry.pointed_frame() {
                    KERNEL_TABLE.store(frame.start_address().get(), Ordering::SeqCst);
                } else {
                    panic!("kernel does not have PML4 entry");
                }
            }
        }

        // Read ACPI tables, starts APs
        acpi::init(&mut active_table);

        BSP_READY.store(true, Ordering::SeqCst);
    }

    kmain();
}

/// Entry to rust for an AP
pub unsafe extern fn kstart_ap(cpu_id: usize, page_table: usize, stack_start: usize, stack_end: usize) -> ! {
    {
        assert_eq!(BSS_TEST_ZERO, 0);
        assert_eq!(DATA_TEST_NONZERO, 0xFFFFFFFFFFFFFFFF);

        // Retrieve kernel table entry
        while KERNEL_TABLE.load(Ordering::SeqCst) == 0 {
            interrupt::pause();
        }
        let kernel_table = KERNEL_TABLE.load(Ordering::SeqCst);

        // Initialize paging
        let (mut active_table, tcb_offset) = paging::init_ap(cpu_id, stack_start, stack_end, kernel_table);

        // Set up GDT for AP
        gdt::init(tcb_offset, stack_end);

        // Set up IDT for AP
        idt::init();

        // Test tdata and tbss
        {
            assert_eq!(TBSS_TEST_ZERO, 0);
            TBSS_TEST_ZERO += 1;
            assert_eq!(TBSS_TEST_ZERO, 1);
            assert_eq!(TDATA_TEST_NONZERO, 0xFFFFFFFFFFFFFFFF);
            TDATA_TEST_NONZERO -= 1;
            assert_eq!(TDATA_TEST_NONZERO, 0xFFFFFFFFFFFFFFFE);
        }

        AP_READY.store(true, Ordering::SeqCst);
    }

    while ! BSP_READY.load(Ordering::SeqCst) {
        interrupt::pause();
    }

    kmain_ap(cpu_id);
}

pub unsafe fn usermode(ip: usize, sp: usize) -> ! {
    // Go to usermode
    asm!("mov ds, ax
        mov es, ax
        mov fs, ax
        mov gs, ax
        push rax
        push rbx
        push rcx
        push rdx
        push rsi
        iretq"
        : // No output because it never returns
        :   "{rax}"(gdt::GDT_USER_DATA << 3 | 3), // Stack segment
            "{rbx}"(sp), // Stack pointer
            "{rcx}"(3 << 12/* | 1 << 9*/), // Flags - Set IOPL and interrupt enable flag
            "{rdx}"(gdt::GDT_USER_CODE << 3 | 3), // Code segment
            "{rsi}"(ip) // IP
        : // No clobers because it never returns
        : "intel", "volatile");
    unreachable!();
}
<|endoftext|>