<commit_before>//! Coredump implementation of the Space API server.
//!
//! Start this via the commandline:
//!
//!     ./coredump_status [-p PORT] [-i IP]

use docopt::Docopt;
use env_logger;
use serde_derive::Deserialize;
use spaceapi_server::api;
use spaceapi_server::api::sensors::{PeopleNowPresentSensorTemplate, TemperatureSensorTemplate};
use spaceapi_server::modifiers::StateFromPeopleNowPresent;
use spaceapi_server::SpaceapiServerBuilder;
use std::env;

static USAGE: &'static str = "
Usage: coredump_status [-p PORT] [-i IP]

Options:
    -p PORT  The port to listen on [default: 3000].
    -i IP    The IP address to listen on [default: 127.0.0.1].
";

#[derive(Deserialize, Debug)]
struct Args {
    flag_p: u16,
    flag_i: std::net::IpAddr,
}

#[cfg_attr(test, allow(dead_code))]
fn main() {
    env_logger::init();

    // Parse arguments
    let args: Args = Docopt::new(USAGE)
        .and_then(|d| d.deserialize())
        .unwrap_or_else(|e| e.exit());
    let host = args.flag_i;
    let port = args.flag_p;

    // Create new Status instance
    let mut status = api::StatusBuilder::new("coredump")
        .logo("https://www.coredump.ch/wp-content/uploads/2016/11/logo.png")
        .url("https://www.coredump.ch/")
        .location(api::Location {
            address: Some("Holzwiesstrasse 50, 8645 Jona, Switzerland".into()),
            lat: 47.2359607,
            lon: 8.8410057,
        })
        .contact(api::Contact {
            irc: Some("irc://freenode.net/#coredump".into()),
            twitter: Some("@coredump_ch".into()),
            email: Some("vorstand@lists.coredump.ch".into()),
            ..Default::default()
        })
        .add_issue_report_channel("email")
        .add_issue_report_channel("twitter")
        .spacefed(api::Spacefed {
            spacenet: false,
            spacesaml: false,
            spacephone: false,
        })
        .feeds(api::Feeds {
            blog: Some(api::Feed {
                type_: Some("rss".into()),
                url: "https://www.coredump.ch/feed/".into(),
            }),
            wiki: None,
            calendar: Some(api::Feed {
                type_: Some("ical".into()),
                url: "https://www.coredump.ch/events/?ical=1".into(),
            }),
            flickr: None,
        })
        .add_project("https://www.coredump.ch/projekte/")
        .add_project("https://forum.coredump.ch/c/projects")
        .add_project("https://github.com/coredump-ch/")
        .add_cam("https://webcam.coredump.ch/cams/ultimaker_0.jpg")
        .add_extension("ccc", "chaostreff")
        .build()
        .expect("Couldn't create status object");

    // Set state message
    status.state.message = Some("Open Mondays from 20:00".into());

    // Redis connection info
    let redis_host: String = env::var("REDIS_HOST").unwrap_or("127.0.0.1".to_string());
    let redis_port: u16 = env::var("REDIS_PORT")
        .unwrap_or("6379".to_string())
        .parse()
        .unwrap_or(6379);
    let redis_db: i64 = env::var("REDIS_DB")
        .unwrap_or("0".to_string())
        .parse()
        .unwrap_or(0);
    let redis_url = format!("redis://{}:{}/{}", redis_host, redis_port, redis_db);

    // Create server
    let server = SpaceapiServerBuilder::new(status)
        .redis_connection_info(&*redis_url)
        .add_status_modifier(StateFromPeopleNowPresent)
        .add_sensor(
            TemperatureSensorTemplate {
                unit: "°C".into(),
                location: "Hackerspace".into(),
                name: Some("Raspberry CPU".into()),
                description: None,
            },
            "temperature_raspi".into(),
        )
        .add_sensor(
            TemperatureSensorTemplate {
                unit: "°C".into(),
                location: "Hackerspace".into(),
                name: Some("Room Temperature (Entrance)".into()),
                description: None,
            },
            "temperature_entrance".into(),
        )
        .add_sensor(
            TemperatureSensorTemplate {
                unit: "°C".into(),
                location: "Hackerspace".into(),
                name: Some("Room Temperature (Tables)".into()),
                description: None,
            },
            "temperature_tables".into(),
        )
        .add_sensor(
            PeopleNowPresentSensorTemplate {
                location: Some("Hackerspace".into()),
                name: None,
                description: None,
                names: None,
            },
            "people_now_present".into(),
        )
        .build()
        .expect("Could not build server");

    server.serve((host, port)).expect("Could not start the server");
}
<commit_msg>Fix clippy issues<commit_after>//! Coredump implementation of the Space API server.
//!
//! Start this via the commandline:
//!
//!     ./coredump_status [-p PORT] [-i IP]

use docopt::Docopt;
use env_logger;
use serde_derive::Deserialize;
use spaceapi_server::api;
use spaceapi_server::api::sensors::{PeopleNowPresentSensorTemplate, TemperatureSensorTemplate};
use spaceapi_server::modifiers::StateFromPeopleNowPresent;
use spaceapi_server::SpaceapiServerBuilder;
use std::env;

static USAGE: &str = "
Usage: coredump_status [-p PORT] [-i IP]

Options:
    -p PORT  The port to listen on [default: 3000].
    -i IP    The IP address to listen on [default: 127.0.0.1].
";

#[derive(Deserialize, Debug)]
struct Args {
    flag_p: u16,
    flag_i: std::net::IpAddr,
}

#[cfg_attr(test, allow(dead_code))]
fn main() {
    env_logger::init();

    // Parse arguments
    let args: Args = Docopt::new(USAGE)
        .and_then(|d| d.deserialize())
        .unwrap_or_else(|e| e.exit());
    let host = args.flag_i;
    let port = args.flag_p;

    // Create new Status instance
    let mut status = api::StatusBuilder::new("coredump")
        .logo("https://www.coredump.ch/wp-content/uploads/2016/11/logo.png")
        .url("https://www.coredump.ch/")
        .location(api::Location {
            address: Some("Holzwiesstrasse 50, 8645 Jona, Switzerland".into()),
            lat: 47.235_960_7,
            lon: 8.841_005_7,
        })
        .contact(api::Contact {
            irc: Some("irc://freenode.net/#coredump".into()),
            twitter: Some("@coredump_ch".into()),
            email: Some("vorstand@lists.coredump.ch".into()),
            ..Default::default()
        })
        .add_issue_report_channel("email")
        .add_issue_report_channel("twitter")
        .spacefed(api::Spacefed {
            spacenet: false,
            spacesaml: false,
            spacephone: false,
        })
        .feeds(api::Feeds {
            blog: Some(api::Feed {
                type_: Some("rss".into()),
                url: "https://www.coredump.ch/feed/".into(),
            }),
            wiki: None,
            calendar: Some(api::Feed {
                type_: Some("ical".into()),
                url: "https://www.coredump.ch/events/?ical=1".into(),
            }),
            flickr: None,
        })
        .add_project("https://www.coredump.ch/projekte/")
        .add_project("https://forum.coredump.ch/c/projects")
        .add_project("https://github.com/coredump-ch/")
        .add_cam("https://webcam.coredump.ch/cams/ultimaker_0.jpg")
        .add_extension("ccc", "chaostreff")
        .build()
        .expect("Couldn't create status object");

    // Set state message
    status.state.message = Some("Open Mondays from 20:00".into());

    // Redis connection info
    let redis_host: String = env::var("REDIS_HOST").unwrap_or_else(|_| "127.0.0.1".to_string());
    let redis_port: u16 = env::var("REDIS_PORT")
        .unwrap_or_else(|_| "6379".to_string())
        .parse()
        .unwrap_or(6379);
    let redis_db: i64 = env::var("REDIS_DB")
        .unwrap_or_else(|_| "0".to_string())
        .parse()
        .unwrap_or(0);
    let redis_url = format!("redis://{}:{}/{}", redis_host, redis_port, redis_db);

    // Create server
    let server = SpaceapiServerBuilder::new(status)
        .redis_connection_info(&*redis_url)
        .add_status_modifier(StateFromPeopleNowPresent)
        .add_sensor(
            TemperatureSensorTemplate {
                unit: "°C".into(),
                location: "Hackerspace".into(),
                name: Some("Raspberry CPU".into()),
                description: None,
            },
            "temperature_raspi".into(),
        )
        .add_sensor(
            TemperatureSensorTemplate {
                unit: "°C".into(),
                location: "Hackerspace".into(),
                name: Some("Room Temperature (Entrance)".into()),
                description: None,
            },
            "temperature_entrance".into(),
        )
        .add_sensor(
            TemperatureSensorTemplate {
                unit: "°C".into(),
                location: "Hackerspace".into(),
                name: Some("Room Temperature (Tables)".into()),
                description: None,
            },
            "temperature_tables".into(),
        )
        .add_sensor(
            PeopleNowPresentSensorTemplate {
                location: Some("Hackerspace".into()),
                name: None,
                description: None,
                names: None,
            },
            "people_now_present".into(),
        )
        .build()
        .expect("Could not build server");

    server.serve((host, port)).expect("Could not start the server");
}
<|endoftext|>


<commit_before>//! # The Rust core allocation and collections library
//!
//! This library provides smart pointers and collections for managing
//! heap-allocated values.
//!
//! This library, like libcore, normally doesn’t need to be used directly
//! since its contents are re-exported in the [`std` crate](../std/index.html).
//! Crates that use the `#![no_std]` attribute however will typically
//! not depend on `std`, so they’d use this crate instead.
//!
//! ## Boxed values
//!
//! The [`Box`] type is a smart pointer type. There can only be one owner of a
//! [`Box`], and the owner can decide to mutate the contents, which live on the
//! heap.
//!
//! This type can be sent among threads efficiently as the size of a `Box` value
//! is the same as that of a pointer. Tree-like data structures are often built
//! with boxes because each node often has only one owner, the parent.
//!
//! ## Reference counted pointers
//!
//! The [`Rc`] type is a non-threadsafe reference-counted pointer type intended
//! for sharing memory within a thread. An [`Rc`] pointer wraps a type, `T`, and
//! only allows access to `&T`, a shared reference.
//!
//! This type is useful when inherited mutability (such as using [`Box`]) is too
//! constraining for an application, and is often paired with the [`Cell`] or
//! [`RefCell`] types in order to allow mutation.
//!
//! ## Atomically reference counted pointers
//!
//! The [`Arc`] type is the threadsafe equivalent of the [`Rc`] type. It
//! provides all the same functionality of [`Rc`], except it requires that the
//! contained type `T` is shareable. Additionally, [`Arc<T>`][`Arc`] is itself
//! sendable while [`Rc<T>`][`Rc`] is not.
//!
//! This type allows for shared access to the contained data, and is often
//! paired with synchronization primitives such as mutexes to allow mutation of
//! shared resources.
//!
//! ## Collections
//!
//! Implementations of the most common general purpose data structures are
//! defined in this library. They are re-exported through the
//! [standard collections library](../std/collections/index.html).
//!
//! ## Heap interfaces
//!
//! The [`alloc`](alloc/index.html) module defines the low-level interface to the
//! default global allocator. It is not compatible with the libc allocator API.
//!
//! [`Arc`]: sync
//! [`Box`]: boxed
//! [`Cell`]: core::cell
//! [`Rc`]: rc
//! [`RefCell`]: core::cell

// To run liballoc tests without x.py without ending up with two copies of liballoc, Miri needs to be
// able to "empty" this crate. See <https://github.com/rust-lang/miri-test-libstd/issues/4>.
// rustc itself never sets the feature, so this line has no affect there.
#![cfg(any(not(feature = "miri-test-libstd"), test, doctest))]
#![allow(unused_attributes)]
#![stable(feature = "alloc", since = "1.36.0")]
#![doc(
    html_playground_url = "https://play.rust-lang.org/",
    issue_tracker_base_url = "https://github.com/rust-lang/rust/issues/",
    test(no_crate_inject, attr(allow(unused_variables), deny(warnings)))
)]
#![cfg_attr(
    not(bootstrap),
    doc(cfg_hide(
        not(test),
        not(any(test, bootstrap)),
        any(not(feature = "miri-test-libstd"), test, doctest),
        no_global_oom_handling,
        not(no_global_oom_handling),
        target_has_atomic = "ptr"
    ))
)]
#![no_std]
#![needs_allocator]
//
// Lints:
#![deny(unsafe_op_in_unsafe_fn)]
#![warn(deprecated_in_future)]
#![warn(missing_debug_implementations)]
#![warn(missing_docs)]
#![allow(explicit_outlives_requirements)]
//
// Library features:
#![feature(alloc_layout_extra)]
#![feature(allocator_api)]
#![feature(array_chunks)]
#![feature(array_methods)]
#![feature(array_windows)]
#![feature(async_stream)]
#![feature(coerce_unsized)]
#![cfg_attr(not(no_global_oom_handling), feature(const_btree_new))]
#![feature(const_cow_is_borrowed)]
#![feature(core_intrinsics)]
#![feature(dispatch_from_dyn)]
#![feature(exact_size_is_empty)]
#![feature(extend_one)]
#![feature(fmt_internals)]
#![feature(fn_traits)]
#![feature(inplace_iteration)]
#![feature(iter_advance_by)]
#![feature(iter_zip)]
#![feature(layout_for_ptr)]
#![feature(maybe_uninit_extra)]
#![feature(maybe_uninit_slice)]
#![cfg_attr(test, feature(new_uninit))]
#![feature(nonnull_slice_from_raw_parts)]
#![feature(option_result_unwrap_unchecked)]
#![feature(pattern)]
#![feature(ptr_internals)]
#![feature(receiver_trait)]
#![feature(set_ptr_value)]
#![feature(slice_group_by)]
#![feature(slice_ptr_get)]
#![feature(slice_ptr_len)]
#![feature(slice_range)]
#![feature(str_internals)]
#![feature(trusted_len)]
#![feature(trusted_random_access)]
#![feature(try_trait_v2)]
#![feature(unicode_internals)]
#![feature(unsize)]
//
// Language features:
#![feature(allocator_internals)]
#![feature(allow_internal_unstable)]
#![feature(arbitrary_self_types)]
#![feature(associated_type_bounds)]
#![feature(auto_traits)]
#![feature(box_patterns)]
#![feature(box_syntax)]
#![feature(cfg_sanitize)]
#![feature(cfg_target_has_atomic)]
#![feature(const_fn_trait_bound)]
#![feature(const_trait_impl)]
#![feature(decl_macro)]
#![feature(destructuring_assignment)]
#![feature(dropck_eyepatch)]
#![feature(exclusive_range_pattern)]
#![feature(fundamental)]
#![cfg_attr(not(test), feature(generator_trait))]
#![feature(lang_items)]
#![feature(min_specialization)]
#![feature(negative_impls)]
#![feature(never_type)]
#![feature(nll)]
#![feature(rustc_allow_const_fn_unstable)]
#![feature(rustc_attrs)]
#![feature(staged_api)]
#![cfg_attr(test, feature(test))]
#![feature(unboxed_closures)]
#![feature(unsized_fn_params)]
//
// Rustdoc features:
#![feature(doc_cfg)]
#![cfg_attr(not(bootstrap), feature(doc_cfg_hide))]
// Technically, this is a bug in rustdoc: rustdoc sees the documentation on `#[lang = slice_alloc]`
// blocks is for `&[T]`, which also has documentation using this feature in `core`, and gets mad
// that the feature-gate isn't enabled. Ideally, it wouldn't check for the feature gate for docs
// from other crates, but since this can only appear for lang items, it doesn't seem worth fixing.
#![feature(intra_doc_pointers)]

// Allow testing this library
#[cfg(test)]
#[macro_use]
extern crate std;
#[cfg(test)]
extern crate test;

// Module with internal macros used by other modules (needs to be included before other modules).
#[macro_use]
mod macros;

// Heaps provided for low-level allocation strategies

pub mod alloc;

// Primitive types using the heaps above

// Need to conditionally define the mod from `boxed.rs` to avoid
// duplicating the lang-items when building in test cfg; but also need
// to allow code to have `use boxed::Box;` declarations.
#[cfg(not(test))]
pub mod boxed;
#[cfg(test)]
mod boxed {
    pub use std::boxed::Box;
}
pub mod borrow;
pub mod collections;
pub mod fmt;
pub mod raw_vec;
pub mod rc;
pub mod slice;
pub mod str;
pub mod string;
#[cfg(target_has_atomic = "ptr")]
pub mod sync;
#[cfg(all(not(no_global_oom_handling), target_has_atomic = "ptr"))]
pub mod task;
#[cfg(test)]
mod tests;
pub mod vec;

#[doc(hidden)]
#[unstable(feature = "liballoc_internals", issue = "none", reason = "implementation detail")]
pub mod __export {
    pub use core::format_args;
}
<commit_msg>Remove unused language #![feature]s from alloc.<commit_after>//! # The Rust core allocation and collections library
//!
//! This library provides smart pointers and collections for managing
//! heap-allocated values.
//!
//! This library, like libcore, normally doesn’t need to be used directly
//! since its contents are re-exported in the [`std` crate](../std/index.html).
//! Crates that use the `#![no_std]` attribute however will typically
//! not depend on `std`, so they’d use this crate instead.
//!
//! ## Boxed values
//!
//! The [`Box`] type is a smart pointer type. There can only be one owner of a
//! [`Box`], and the owner can decide to mutate the contents, which live on the
//! heap.
//!
//! This type can be sent among threads efficiently as the size of a `Box` value
//! is the same as that of a pointer. Tree-like data structures are often built
//! with boxes because each node often has only one owner, the parent.
//!
//! ## Reference counted pointers
//!
//! The [`Rc`] type is a non-threadsafe reference-counted pointer type intended
//! for sharing memory within a thread. An [`Rc`] pointer wraps a type, `T`, and
//! only allows access to `&T`, a shared reference.
//!
//! This type is useful when inherited mutability (such as using [`Box`]) is too
//! constraining for an application, and is often paired with the [`Cell`] or
//! [`RefCell`] types in order to allow mutation.
//!
//! ## Atomically reference counted pointers
//!
//! The [`Arc`] type is the threadsafe equivalent of the [`Rc`] type. It
//! provides all the same functionality of [`Rc`], except it requires that the
//! contained type `T` is shareable. Additionally, [`Arc<T>`][`Arc`] is itself
//! sendable while [`Rc<T>`][`Rc`] is not.
//!
//! This type allows for shared access to the contained data, and is often
//! paired with synchronization primitives such as mutexes to allow mutation of
//! shared resources.
//!
//! ## Collections
//!
//! Implementations of the most common general purpose data structures are
//! defined in this library. They are re-exported through the
//! [standard collections library](../std/collections/index.html).
//!
//! ## Heap interfaces
//!
//! The [`alloc`](alloc/index.html) module defines the low-level interface to the
//! default global allocator. It is not compatible with the libc allocator API.
//!
//! [`Arc`]: sync
//! [`Box`]: boxed
//! [`Cell`]: core::cell
//! [`Rc`]: rc
//! [`RefCell`]: core::cell

// To run liballoc tests without x.py without ending up with two copies of liballoc, Miri needs to be
// able to "empty" this crate. See <https://github.com/rust-lang/miri-test-libstd/issues/4>.
// rustc itself never sets the feature, so this line has no affect there.
#![cfg(any(not(feature = "miri-test-libstd"), test, doctest))]
#![allow(unused_attributes)]
#![stable(feature = "alloc", since = "1.36.0")]
#![doc(
    html_playground_url = "https://play.rust-lang.org/",
    issue_tracker_base_url = "https://github.com/rust-lang/rust/issues/",
    test(no_crate_inject, attr(allow(unused_variables), deny(warnings)))
)]
#![cfg_attr(
    not(bootstrap),
    doc(cfg_hide(
        not(test),
        not(any(test, bootstrap)),
        any(not(feature = "miri-test-libstd"), test, doctest),
        no_global_oom_handling,
        not(no_global_oom_handling),
        target_has_atomic = "ptr"
    ))
)]
#![no_std]
#![needs_allocator]
//
// Lints:
#![deny(unsafe_op_in_unsafe_fn)]
#![warn(deprecated_in_future)]
#![warn(missing_debug_implementations)]
#![warn(missing_docs)]
#![allow(explicit_outlives_requirements)]
//
// Library features:
#![feature(alloc_layout_extra)]
#![feature(allocator_api)]
#![feature(array_chunks)]
#![feature(array_methods)]
#![feature(array_windows)]
#![feature(async_stream)]
#![feature(coerce_unsized)]
#![cfg_attr(not(no_global_oom_handling), feature(const_btree_new))]
#![feature(const_cow_is_borrowed)]
#![feature(core_intrinsics)]
#![feature(dispatch_from_dyn)]
#![feature(exact_size_is_empty)]
#![feature(extend_one)]
#![feature(fmt_internals)]
#![feature(fn_traits)]
#![feature(inplace_iteration)]
#![feature(iter_advance_by)]
#![feature(iter_zip)]
#![feature(layout_for_ptr)]
#![feature(maybe_uninit_extra)]
#![feature(maybe_uninit_slice)]
#![cfg_attr(test, feature(new_uninit))]
#![feature(nonnull_slice_from_raw_parts)]
#![feature(option_result_unwrap_unchecked)]
#![feature(pattern)]
#![feature(ptr_internals)]
#![feature(receiver_trait)]
#![feature(set_ptr_value)]
#![feature(slice_group_by)]
#![feature(slice_ptr_get)]
#![feature(slice_ptr_len)]
#![feature(slice_range)]
#![feature(str_internals)]
#![feature(trusted_len)]
#![feature(trusted_random_access)]
#![feature(try_trait_v2)]
#![feature(unicode_internals)]
#![feature(unsize)]
//
// Language features:
#![feature(allocator_internals)]
#![feature(allow_internal_unstable)]
#![feature(associated_type_bounds)]
#![feature(box_syntax)]
#![feature(cfg_sanitize)]
#![feature(cfg_target_has_atomic)]
#![feature(const_fn_trait_bound)]
#![feature(const_trait_impl)]
#![feature(destructuring_assignment)]
#![feature(dropck_eyepatch)]
#![feature(exclusive_range_pattern)]
#![feature(fundamental)]
#![cfg_attr(not(test), feature(generator_trait))]
#![feature(lang_items)]
#![feature(min_specialization)]
#![feature(negative_impls)]
#![feature(never_type)]
#![feature(rustc_allow_const_fn_unstable)]
#![feature(rustc_attrs)]
#![feature(staged_api)]
#![cfg_attr(test, feature(test))]
#![feature(unboxed_closures)]
#![feature(unsized_fn_params)]
//
// Rustdoc features:
#![feature(doc_cfg)]
#![cfg_attr(not(bootstrap), feature(doc_cfg_hide))]
// Technically, this is a bug in rustdoc: rustdoc sees the documentation on `#[lang = slice_alloc]`
// blocks is for `&[T]`, which also has documentation using this feature in `core`, and gets mad
// that the feature-gate isn't enabled. Ideally, it wouldn't check for the feature gate for docs
// from other crates, but since this can only appear for lang items, it doesn't seem worth fixing.
#![feature(intra_doc_pointers)]

// Allow testing this library
#[cfg(test)]
#[macro_use]
extern crate std;
#[cfg(test)]
extern crate test;

// Module with internal macros used by other modules (needs to be included before other modules).
#[macro_use]
mod macros;

// Heaps provided for low-level allocation strategies

pub mod alloc;

// Primitive types using the heaps above

// Need to conditionally define the mod from `boxed.rs` to avoid
// duplicating the lang-items when building in test cfg; but also need
// to allow code to have `use boxed::Box;` declarations.
#[cfg(not(test))]
pub mod boxed;
#[cfg(test)]
mod boxed {
    pub use std::boxed::Box;
}
pub mod borrow;
pub mod collections;
pub mod fmt;
pub mod raw_vec;
pub mod rc;
pub mod slice;
pub mod str;
pub mod string;
#[cfg(target_has_atomic = "ptr")]
pub mod sync;
#[cfg(all(not(no_global_oom_handling), target_has_atomic = "ptr"))]
pub mod task;
#[cfg(test)]
mod tests;
pub mod vec;

#[doc(hidden)]
#[unstable(feature = "liballoc_internals", issue = "none", reason = "implementation detail")]
pub mod __export {
    pub use core::format_args;
}
<|endoftext|>


<commit_before>extern crate html5ever;
extern crate html5ever_dom_sink;
extern crate hyper;
extern crate rustc_serialize;
extern crate tempfile;
extern crate tendril;
extern crate url;

mod json;

use std::fs::OpenOptions;
use std::io::Read;
use std::io::Write;
use std::process::Command;
use std::process::Stdio;
use std::str::FromStr;

use html5ever::Attribute;
use html5ever::tree_builder::interface::TreeSink;
use html5ever_dom_sink::common::NodeEnum;
use html5ever_dom_sink::rcdom::Handle;
use html5ever_dom_sink::rcdom::RcDom;
use hyper::Client;
use hyper::header::Connection;
use rustc_serialize::json::Json;
use tempfile::NamedTempFile;
use url::percent_encoding;

use json::JsonPathElement::{Key, Only};

// TODO: there are some places where I've handrolled try!() equivalents. Fix those.
// TODO: make sure I'm returning Results everywhere, and propagating errors correctly.

// TODO: Can I get rid of the repeated "Zachary_Taylor"s everywhere? Surely the MediaWiki API doesn't actually need that - I can't imagine revision IDs aren't unique across all pages.

fn get_revisions(page: &str, limit: i32) -> String {
    let client = Client::new();
    let mut res = client.get(
        &format!(
            "https://en.wikipedia.org/w/api.php?action=query&prop=revisions&titles={}&rvprop=timestamp|user|comment|ids&rvlimit={}&format=json",
            page, limit))
        .header(Connection::close())
        .send().unwrap();

    let mut body = String::new();
    res.read_to_string(&mut body).unwrap();

    body
}

// TODO: this name is terrible.
// Returns pairs of (revid, parentid)
fn get_revert_revision_ids(page: &str) -> Result<Vec<(u64, u64)>, String> {
    let json = Json::from_str(&get_revisions(page, 60)).unwrap();
    let revisions = try!(
        json::get_json_array(&json, &[Key("query"), Key("pages"), Only, Key("revisions")]));

    // Filter for revisions that mention vandalism
    let filtered_revisions =
        revisions.into_iter().filter(|revision| {
            match json::get_json_string(revision, &[Key("comment")]) {
                Ok(ref comment) => comment.contains("vandal"),
                // TODO: need to warn somehow when this happens, because it generally shouldn't.
                _ => false,
            }
        })
        // Filter out revisions with missing revid or parentid (which shouldn't happen).
        .filter(|revision| {
            match (json::get_json_number(revision, &[Key("revid")]),
                   json::get_json_number(revision, &[Key("parentid")])) {
                (Ok(_), Ok(_)) => true,
                // TODO: need to warn somehow when this happens, because it should never.
                _ => false,
            }});
    Ok(filtered_revisions.map(|revision| {
        (json::get_json_number(revision, &[Key("revid")]).unwrap(),
         json::get_json_number(revision, &[Key("parentid")]).unwrap())
    }).collect())
}

fn get_latest_revision_id(page: &str) -> u64 {
    let json = Json::from_str(&get_revisions(page, 1)).unwrap();
    let revision_id = json::get_json_number(
        &json, &[Key("query"), Key("pages"), Only, Key("revisions"), Only, Key("revid")]);
    match revision_id {
        Ok(revision_id) => revision_id,
        // TODO: This function should return a Result so we don't have to panic here.
        Err(message) => panic!(format!("Failed to get latest revision ID: \"{}\"", message)),
    }
}

fn get_revision(page: &str, id: u64) -> String {
    let client = Client::new();
    let mut res = client.get(
        &format!(
            "https://en.wikipedia.org/w/api.php?action=query&prop=revisions&titles={}&rvprop=content&rvlimit=1&rvstartid={}&format=json",
            page, id))
        .header(Connection::close())
        .send().unwrap();

    let mut body = String::new();
    res.read_to_string(&mut body).unwrap();
    body
}

fn get_revision_content(page: &str, id: u64) -> String {
    // TODO: this function should return a Result so we don't have to unwrap() here.
    json::get_json_string(
        &Json::from_str(&get_revision(page, id)).unwrap(),
        &[Key("query"), Key("pages"), Only, Key("revisions"), Only, Key("*")]).unwrap().to_string()
}

fn write_to_temp_file(contents: &str) -> NamedTempFile {
    let tempfile = NamedTempFile::new().unwrap();
    let mut file = OpenOptions::new().write(true).open(tempfile.path()).unwrap();
    file.write_all(contents.as_bytes()).unwrap();
    file.flush().unwrap();
    tempfile
}

// TODO: I'm not so sure these parameter names aren't terrible.
fn merge(old: &str, new1: &str, new2: &str) -> Option<String> {
    let old_tempfile = write_to_temp_file(old);
    let new1_tempfile = write_to_temp_file(new1);
    let new2_tempfile = write_to_temp_file(new2);

    let mut process = Command::new("diff3");
    process.arg("-m").args(&[new1_tempfile.path(), old_tempfile.path(), new2_tempfile.path()])
        .stdout(Stdio::piped()).stderr(Stdio::null());
    let output = process.output().unwrap();
    if output.status.success() {
        Some(String::from_utf8(output.stdout).unwrap())
    } else {
        None
    }
}

fn render(wikitext: &str) -> Result<String, String> {
    let post_body = format!(
        "text={}", percent_encoding::percent_encode(
            wikitext.as_bytes(), percent_encoding::QUERY_ENCODE_SET));

    let client = Client::new();
    let mut res = client.post(
        "https://en.wikipedia.org/w/api.php?action=parse&format=json&prop=text&disablepp=&contentmodel=wikitext")
        .body(&post_body)
        .header(Connection::close())
        .send().unwrap();

    let mut body = String::new();
    res.read_to_string(&mut body).unwrap();

    println!("AAAAAAAAAAAAAAAAAAAAAAAAAAAAAA");
    println!("{}", body);
    // TODO: check return value
    let json = Json::from_str(&body).unwrap();
    match json::get_json_string(&json, &[Key("parse"), Key("text"), Key("*")]) {
        Ok(contents) => Ok(contents.to_string()),
        Err(message) => Err(message),
    }
}

fn has_matching_id(attributes: &Vec<Attribute>, id: &str) -> bool {
    // TODO: could also do this with filter() and is_empty(). Not sure if that would be better.
    for attribute in attributes {
        // TODO: do I seriously have to construct a StrTendril here?
        // There has to be a better way.
        if attribute.name.local.as_slice() == "id" &&
            attribute.value == tendril::StrTendril::from_str(id).unwrap() {
                return true;
            }
    }
    return false;
}

fn find_node_by_id(handle: &Handle, id: &str) -> Result<Handle, String> {
    let node = handle.borrow();
    match node.node {
        NodeEnum::Element(_, ref attributes) if has_matching_id(attributes, id) => Ok(handle.clone()),
        _ => {
            for child in &node.children {
                match find_node_by_id(child, id) {
                    // TODO: this looks weird. Is this an abnormal way to do things?
                    Ok(node) => return Ok(node),
                    _ => continue,
                }
            }
            Err(format!("No node with ID {} found", id))
        },
    }
}

fn process_html(original_html: &str, div_id: &str, replacement_text: &str) -> Result<String, String> {
    // TODO: check errors
    let html = tendril::StrTendril::from_str(original_html).unwrap();
    let mut dom: RcDom = html5ever::parse(html5ever::one_input(html), Default::default());

    let handle = try!(find_node_by_id(&dom.get_document(), "mw-content-text"));
    let child_handles =
        (&handle.borrow().children).into_iter().map(|child| child.clone()).collect::<Vec<_>>();
    for child_handle in child_handles {
        dom.remove_from_parent(child_handle);
    }
    dom.append(handle,
               html5ever::tree_builder::interface::NodeOrText::AppendText(
                   tendril::StrTendril::from_str(replacement_text).unwrap()));
    let mut serialized: Vec<u8> = vec![];
    html5ever::serialize::serialize(&mut serialized, &dom.document, Default::default());
    // TODO: error handling
    Ok(String::from_utf8(serialized).unwrap())
}

fn main() {
    let latest_revid = get_latest_revision_id("Zachary_Taylor");
    let mut accumulated_contents = get_revision_content("Zachary_Taylor", latest_revid);
    let mut revisions = vec![];
    for (revert_revid, vandalism_revid) in get_revert_revision_ids("Zachary_Taylor").ok().unwrap() {
        let reverted_contents = get_revision_content("Zachary_Taylor", revert_revid);
        let vandalized_contents = get_revision_content("Zachary_Taylor", vandalism_revid);
        match merge(&reverted_contents, &vandalized_contents, &accumulated_contents) {
            Some(merged_contents) => {
                accumulated_contents = merged_contents;
                revisions.push(revert_revid);
            }
            None => (),
        }
    }

    match render(&accumulated_contents) {
        Ok(contents) => {
            println!("Restored vandalisms reverted in: {:?}", revisions);
            println!("{}", contents);
        },
        Err(message) => panic!(message),
    }
}

#[cfg(test)]
mod tests {
    use super::{merge, process_html};

    #[test]
    fn test_merge_clean() {
        let old = "First line.\n\nSecond line.\n";
        let new1 = "First line.\n\nSecond line changed.\n";
        let new2 = "First line changed.\n\nSecond line.\n";
        assert_eq!("First line changed.\n\nSecond line changed.\n", merge(old, new1, new2).unwrap());
    }

    #[test]
    fn test_merge_conflicting() {
        let old = "First line.\n\nSecond line.\n";
        let new1 = "First line.\n\nSecond line changed one way.\n";
        let new2 = "First line changed.\n\nSecond line changed a different way.\n";
        assert_eq!(None, merge(old, new1, new2));
    }

    #[test]
    fn test_merge_special_characters() {
        let old = "First line.\n\nSecond line.\n";
        let new1 = "First line.\n\nSecond line 𐅃.\n";
        let new2 = "First line さようなら.\n\nSecond line.\n";
        assert_eq!("First line さようなら.\n\nSecond line 𐅃.\n", merge(old, new1, new2).unwrap());
    }

    #[test]
    fn test_replace_html_content() {
        let original_html = "

original text

Other text

";
        let expected_html = "

replaced text

Other text

";
        let processed_html = process_html(original_html, "mw-content-text", "replaced text").unwrap();
        assert_eq!(expected_html, processed_html);
    }
}
<commit_msg>Render the page fully, by replacing the body of the real Wikipedia page with the generated body. Also, assorted cleanups.<commit_after>extern crate html5ever;
extern crate html5ever_dom_sink;
extern crate hyper;
extern crate rustc_serialize;
extern crate tempfile;
extern crate tendril;
extern crate url;

mod json;

use std::fs::OpenOptions;
use std::io::Read;
use std::io::Write;
use std::process::Command;
use std::process::Stdio;
use std::str::FromStr;

use html5ever::Attribute;
use html5ever::tree_builder::interface::TreeSink;
use html5ever_dom_sink::common::NodeEnum;
use html5ever_dom_sink::rcdom::Handle;
use html5ever_dom_sink::rcdom::RcDom;
use hyper::Client;
use hyper::header::Connection;
use rustc_serialize::json::Json;
use tempfile::NamedTempFile;
use url::percent_encoding;

use json::JsonPathElement::{Key, Only};

// TODO: there are some places where I've handrolled try!() equivalents. Fix those.
// TODO: make sure I'm returning Results everywhere, and propagating errors correctly.
// TODO: randomize placeholder per-request

// TODO: Can I get rid of the repeated "Zachary_Taylor"s everywhere? Surely the MediaWiki API doesn't actually need that - I can't imagine revision IDs aren't unique across all pages.

fn get_revisions(page: &str, limit: i32) -> String {
    let client = Client::new();
    let mut res = client.get(
        &format!(
            "https://en.wikipedia.org/w/api.php?action=query&prop=revisions&titles={}&rvprop=timestamp|user|comment|ids&rvlimit={}&format=json",
            page, limit))
        .header(Connection::close())
        .send().unwrap();

    let mut body = String::new();
    res.read_to_string(&mut body).unwrap();

    body
}

// TODO: this name is terrible.
// Returns pairs of (revid, parentid)
fn get_revert_revision_ids(page: &str) -> Result<Vec<(u64, u64)>, String> {
    let json = Json::from_str(&get_revisions(page, 60)).unwrap();
    let revisions = try!(
        json::get_json_array(&json, &[Key("query"), Key("pages"), Only, Key("revisions")]));

    // Filter for revisions that mention vandalism, and that have revid and parentid (which they all
    // should, but merits checking).
    Ok(revisions.into_iter().filter(|revision| {
        match (json::get_json_string(revision, &[Key("comment")]),
               json::get_json_number(revision, &[Key("revid")]),
               json::get_json_number(revision, &[Key("parentid")])) {
            (Ok(ref comment), Ok(_), Ok(_)) => comment.contains("vandal"),
            _ => false,
        }})
       .map(|revision| {
           (json::get_json_number(revision, &[Key("revid")]).unwrap(),
            json::get_json_number(revision, &[Key("parentid")]).unwrap())
       }).collect())
}

fn get_latest_revision_id(page: &str) -> Result<u64, String> {
    let json = Json::from_str(&get_revisions(page, 1)).unwrap();
    json::get_json_number(
        &json, &[Key("query"), Key("pages"), Only, Key("revisions"), Only, Key("revid")])
}

fn get_revision(page: &str, id: u64) -> String {
    let client = Client::new();
    let mut res = client.get(
        &format!(
            "https://en.wikipedia.org/w/api.php?action=query&prop=revisions&titles={}&rvprop=content&rvlimit=1&rvstartid={}&format=json",
            page, id))
        .header(Connection::close())
        .send().unwrap();

    let mut body = String::new();
    res.read_to_string(&mut body).unwrap();
    body
}

fn get_revision_content(page: &str, id: u64) -> Result<String, String> {
    let json = Json::from_str(&get_revision(page, id)).unwrap();
    match json::get_json_string(
        &json, &[Key("query"), Key("pages"), Only, Key("revisions"), Only, Key("*")]) {
        Ok(content) => Ok(content.to_string()),
        Err(msg) => Err(msg),
    }
}

fn write_to_temp_file(contents: &str) -> NamedTempFile {
    let tempfile = NamedTempFile::new().unwrap();
    let mut file = OpenOptions::new().write(true).open(tempfile.path()).unwrap();
    file.write_all(contents.as_bytes()).unwrap();
    file.flush().unwrap();
    tempfile
}

// TODO: I'm not so sure these parameter names aren't terrible.
fn merge(old: &str, new1: &str, new2: &str) -> Option<String> {
    let old_tempfile = write_to_temp_file(old);
    let new1_tempfile = write_to_temp_file(new1);
    let new2_tempfile = write_to_temp_file(new2);

    let mut process = Command::new("diff3");
    process.arg("-m").args(&[new1_tempfile.path(), old_tempfile.path(), new2_tempfile.path()])
        .stdout(Stdio::piped()).stderr(Stdio::null());
    let output = process.output().unwrap();
    if output.status.success() {
        Some(String::from_utf8(output.stdout).unwrap())
    } else {
        None
    }
}

fn render(wikitext: &str) -> Result<String, String> {
    let post_body = format!(
        "text={}", percent_encoding::percent_encode(
            wikitext.as_bytes(), percent_encoding::QUERY_ENCODE_SET));

    let client = Client::new();
    let mut res = client.post(
        "https://en.wikipedia.org/w/api.php?action=parse&format=json&prop=text&disablepp=&contentmodel=wikitext")
        .body(&post_body)
        .header(Connection::close())
        .send().unwrap();

    let mut body = String::new();
    res.read_to_string(&mut body).unwrap();

    // TODO: check return value
    let json = Json::from_str(&body).unwrap();
    match json::get_json_string(&json, &[Key("parse"), Key("text"), Key("*")]) {
        Ok(contents) => Ok(contents.to_string()),
        Err(message) => Err(message),
    }
}

fn has_matching_id(attributes: &Vec<Attribute>, id: &str) -> bool {
    // TODO: could also do this with filter() and is_empty(). Not sure if that would be better.
    for attribute in attributes {
        // TODO: do I seriously have to construct a StrTendril here?
        // There has to be a better way.
        if attribute.name.local.as_slice() == "id" &&
            attribute.value == tendril::StrTendril::from_str(id).unwrap() {
                return true;
            }
    }
    return false;
}

fn find_node_by_id(handle: &Handle, id: &str) -> Result<Handle, String> {
    let node = handle.borrow();
    match node.node {
        NodeEnum::Element(_, ref attributes) if has_matching_id(attributes, id) => Ok(handle.clone()),
        _ => {
            for child in &node.children {
                match find_node_by_id(child, id) {
                    // TODO: this looks weird. Is this an abnormal way to do things?
                    Ok(node) => return Ok(node),
                    _ => continue,
                }
            }
            Err(format!("No node with ID {} found", id))
        },
    }
}

fn replace_node_with_placeholder(original_html: &str, div_id: &str, placeholder: &str) -> Result<String, String> {
    // TODO: check errors
    let html = tendril::StrTendril::from_str(original_html).unwrap();
    let mut dom: RcDom = html5ever::parse(html5ever::one_input(html), Default::default());

    let handle = try!(find_node_by_id(&dom.get_document(), div_id));
    let child_handles =
        (&handle.borrow().children).into_iter().map(|child| child.clone()).collect::<Vec<_>>();
    for child_handle in child_handles {
        dom.remove_from_parent(child_handle);
    }
    dom.append(handle,
               html5ever::tree_builder::interface::NodeOrText::AppendText(
                   tendril::StrTendril::from_str(placeholder).unwrap()));
    let mut serialized: Vec<u8> = vec![];
    html5ever::serialize::serialize(&mut serialized, &dom.document, Default::default());
    // TODO: error handling
    Ok(String::from_utf8(serialized).unwrap())
}

fn get_current_page(page: &str) -> String {
    let client = Client::new();
    let mut res = client.get(
        &format!("https://en.wikipedia.org/wiki/{}", page))
        .header(Connection::close())
        .send().unwrap();

    let mut body = String::new();
    res.read_to_string(&mut body).unwrap();

    body
}

fn main() {
    let latest_revid = get_latest_revision_id("Zachary_Taylor").unwrap();
    let mut accumulated_contents = get_revision_content("Zachary_Taylor", latest_revid).unwrap();
    let mut revisions = vec![];
    for (revert_revid, vandalism_revid) in get_revert_revision_ids("Zachary_Taylor").ok().unwrap() {
        let reverted_contents = get_revision_content("Zachary_Taylor", revert_revid).unwrap();
        let vandalized_contents = get_revision_content("Zachary_Taylor", vandalism_revid).unwrap();
        match merge(&reverted_contents, &vandalized_contents, &accumulated_contents) {
            Some(merged_contents) => {
                accumulated_contents = merged_contents;
                revisions.push(revert_revid);
            }
            None => (),
        }
    }

    let rendered_body = render(&accumulated_contents).unwrap();
    let current_page_contents = get_current_page("Zachary_Taylor");
    let page_contents_with_placeholder =
        replace_node_with_placeholder(
            &current_page_contents, "mw-content-text", "WMW_PLACEHOLDER_TEXT").unwrap();
    let final_page_contents = page_contents_with_placeholder.replace("WMW_PLACEHOLDER_TEXT", &rendered_body);

    println!("Restored vandalisms reverted in: {:?}", revisions);
    println!("{}", final_page_contents);
}

#[cfg(test)]
mod tests {
    use super::{merge, replace_node_with_placeholder};

    #[test]
    fn test_merge_clean() {
        let old = "First line.\n\nSecond line.\n";
        let new1 = "First line.\n\nSecond line changed.\n";
        let new2 = "First line changed.\n\nSecond line.\n";
        assert_eq!("First line changed.\n\nSecond line changed.\n", merge(old, new1, new2).unwrap());
    }

    #[test]
    fn test_merge_conflicting() {
        let old = "First line.\n\nSecond line.\n";
        let new1 = "First line.\n\nSecond line changed one way.\n";
        let new2 = "First line changed.\n\nSecond line changed a different way.\n";
        assert_eq!(None, merge(old, new1, new2));
    }

    #[test]
    fn test_merge_special_characters() {
        let old = "First line.\n\nSecond line.\n";
        let new1 = "First line.\n\nSecond line 𐅃.\n";
        let new2 = "First line さようなら.\n\nSecond line.\n";
        assert_eq!("First line さようなら.\n\nSecond line 𐅃.\n", merge(old, new1, new2).unwrap());
    }

    #[test]
    fn test_replace_html_content() {
        let original_html = "

original text

Other text

";
        let expected_html = "

replaced text

Other text

";
        let processed_html = replace_node_with_placeholder(original_html, "mw-content-text", "replaced text").unwrap();
        assert_eq!(expected_html, processed_html);
    }
}
<|endoftext|>


<commit_before>#![feature(phase)]
#[phase(plugin, link)] extern crate log;
#[phase(plugin)] extern crate nickel_macros;
extern crate nickel;
extern crate serialize;

use p150::P150Cpu;

use serialize::json;
use std::collections::HashMap;
use std::io::net::ip::Ipv4Addr;
use std::sync::{Arc,RWLock};
use nickel::{Nickel, Request, Response};
use nickel::{Middleware, HttpRouter, StaticFilesHandler};
use nickel::{MiddlewareResult, NickelError};

mod p150;

struct CpuMw { db: Arc<RWLock<CpuServ>> }
struct CpuServ {
	next_cpu: i32,
	database: HashMap<i32, P150Cpu>,
}

impl Middleware for CpuMw {
	/// Injects the protected CPU map into the request
	fn invoke(&self, req: &mut Request, _res: &mut Response) -> MiddlewareResult {
		req.map.insert(self.db.clone());
		Ok(nickel::Continue)
	}
}

impl CpuServ {
	fn new() -> CpuServ {
		CpuServ {
			next_cpu: 0,
			database: HashMap::new(),
		}
	}
}

fn main() {
	let mut server = Nickel::new();
	let mut router = Nickel::router();

	fn index_show(_request: &Request, response: &mut Response) {
		let mut page = HashMap::new();
		page.insert("title", "P150 Emulator");

		response.render("./assets/views/index.html", &page);
	}

	fn cpu_new(req: &Request, response: &mut Response) {
		match req.map.find::<Arc<RWLock<CpuServ>>>() {
			Some(cpuserv) => {
				let mut w_cpuserv = cpuserv.write();
				let cpu_no = w_cpuserv.next_cpu;

				w_cpuserv.database.insert(cpu_no, P150Cpu::new());
				w_cpuserv.next_cpu += 1;

				response.send(json::encode(&cpu_no));
			},
			None => { response.send("did not find cpu serv"); }
		}
	}

	fn cpu_load(req: &Request, response: &mut Response) {
		let id = from_str::<i32>(req.param("id")).unwrap();
		match req.map.find::<Arc<RWLock<CpuServ>>>() {
			Some(cpuserv) => {
				let mut w_serv = cpuserv.write();
				let cpu        = w_serv.database.find_mut(&id).unwrap();

				cpu.init_mem([0x1001, 0xB000]); // TODO: load from req.
				response.send(format!("{}", cpu.js_dump()));
			},
			None => { response.send("did not find cpu serv"); }
		}
	}

	fn cpu_run(req: &Request, response: &mut Response) {
		let id = from_str::<i32>(req.param("id")).unwrap();
		match req.map.find::<Arc<RWLock<CpuServ>>>() {
			Some(cpuserv) => {
				let mut cycles = 0i16;
				let mut w_serv = cpuserv.write();
				let cpu    = w_serv.database.find_mut(&id).unwrap();
				loop {
					if cycles == 10000 { println!("CPU executed > 10000 cycles."); break; }
					cycles += 1;

					match cpu.tick() {
						p150::Halt => { break; },
						p150::Continue => { continue; },
					}
				}

				response.send(format!("{}", cpu.js_dump()));
			},
			None => { response.send("did not find cpu serv"); }
		}
	}

	router.get("/", index_show);
	router.post("/cpu/new", cpu_new);
	router.post("/cpu/:id/load", cpu_load);
	router.post("/cpu/:id/run", cpu_run);

	server.utilize(CpuMw { db: Arc::new(RWLock::new(CpuServ::new())) });
	server.utilize(StaticFilesHandler::new("./public"));
	server.utilize(router);

	server.listen(Ipv4Addr(0,0,0,0), 3200);
}

<commit_msg>Update to handle new actions<commit_after>#![feature(phase)]
#[phase(plugin, link)] extern crate log;
#[phase(plugin)] extern crate nickel_macros;
extern crate nickel;
extern crate serialize;

use p150::P150Cpu;

use serialize::json;
use serialize::hex::FromHex;
use std::collections::HashMap;
use std::io::net::ip::Ipv4Addr;
use std::sync::{Arc,RWLock};
use nickel::{JsonBody, Nickel, Request, Response};
use nickel::{Middleware, HttpRouter, StaticFilesHandler};
use nickel::{MiddlewareResult, NickelError};

mod p150;

static MAX_CYCLES: i32 = 100000; // maximum number of cycles CPU can execute to prevent DoS attacks.

/// Injects a mutex-protected CpuServ into request map when invoked
struct CpuMw { db: Arc<RWLock<CpuServ>> }

/// A database of multiple uniquely identified P150 CPU emulators.
///
/// Each emulator maintains its own state and can be recalled
/// using the integer identification.
struct CpuServ {
	next_cpu: i32,
	database: HashMap<i32, P150Cpu>,
}

impl Middleware for CpuMw {
	fn invoke(&self, req: &mut Request, _res: &mut Response) -> MiddlewareResult {
		req.map.insert(self.db.clone());
		Ok(nickel::Continue)
	}
}

impl CpuServ {
	fn new() -> CpuServ {
		CpuServ {
			next_cpu: 0,
			database: HashMap::new(),
		}
	}
}

fn main() {
	let mut server = Nickel::new();
	let mut router = Nickel::router();

	fn index_show(_request: &Request, response: &mut Response) {
		let mut page = HashMap::new();
		page.insert("title", "P150 Emulator");

		response.render("./assets/views/index.html", &page);
	}

	fn about_show(_request: &Request, response: &mut Response) {
		let mut page = HashMap::new();
		page.insert("title", "P150 Emulator");

		response.render("./assets/views/about.html", &page);
	}

	/// POST /cpu/new: creates a new CPU and adds it to the CpuServ with the next
	/// available ID number.
	fn cpu_new(req: &Request, response: &mut Response) {
		match req.map.find::<Arc<RWLock<CpuServ>>>() {
			Some(cpuserv) => {
				let mut w_cpuserv = cpuserv.write();
				let cpu_no = w_cpuserv.next_cpu;

				w_cpuserv.database.insert(cpu_no, P150Cpu::new());
				w_cpuserv.next_cpu += 1;

				response.send(json::encode(&cpu_no));
			},
			None => { response.send("did not find cpu serv"); }
		}
	}

	/// POST /cpu/:id/load
	/// Overwrites the CPUs system memory with response data.
	/// The current machine state is then dumped into the response as JSON.
	fn cpu_load(req: &Request, response: &mut Response) {
		let id = from_str::<i32>(req.param("id")).unwrap();
		let mem_js = req.json_as::<Vec<String>>().unwrap();
		let mem_native: Vec<u8> = mem_js.iter().map(|hex| {
			hex.as_slice().from_hex().unwrap()[0]
		}).collect();

		// pair odd u8s with even u8s to produce u16 ops.
		let even_mn = mem_native.iter().enumerate().filter(|&(idx, _)| { idx % 2 == 0 });
		let odd_mn  = mem_native.iter().enumerate().filter(|&(idx, _)| { idx % 2 != 0 });
		let ops: Vec<u16> = even_mn.zip(odd_mn).map(|((_, &msb), (_, &lsb))| {
			(msb as u16 << 8) | (lsb as u16)
		}).collect();

		match req.map.find::<Arc<RWLock<CpuServ>>>() {
			Some(cpuserv) => {
				let mut w_serv = cpuserv.write();
				let cpu        = w_serv.database.find_mut(&id).unwrap();

				cpu.init_mem(ops.as_slice()); // TODO: load from req.
				response.send(format!("{}", cpu.js_dump()));
			},
			None => { response.send("did not find cpu serv"); }
		}
	}

	/// POST /cpu/:id/dump
	/// Dumps the CPUs current state to a JSON format.
	/// This request does not modify the CPU state in any way.
	fn cpu_dump(req: &Request, response: &mut Response) {
		let id = from_str::<i32>(req.param("id")).unwrap();
		match req.map.find::<Arc<RWLock<CpuServ>>>() {
			Some(cpuserv) => {
				let r_serv = cpuserv.read();
				let cpu        = r_serv.database.find(&id).unwrap();

				response.send(format!("{}", cpu.js_dump()));
			},
			None => { response.send("did not find cpu serv"); }
		}
	}

	/// POST /cpu/:id/tick
	/// Resumes execution from the next instruction; running the machine to completion.
	/// The resultant state is then dumped to the console.
	fn cpu_tick(req: &Request, response: &mut Response) {
		let id = from_str::<i32>(req.param("id")).unwrap();
		match req.map.find::<Arc<RWLock<CpuServ>>>() {
			Some(cpuserv) => {
				let mut w_serv = cpuserv.write();
				let cpu        = w_serv.database.find_mut(&id).unwrap();
				let _          = cpu.tick();

				response.send("");
			},
			None => { response.send("did not find cpu serv"); }
		}
	}

	/// POST /cpu/:id/run
	/// Resumes execution from the next instruction; running the machine to completion.
	/// The resultant state is then dumped to the console.
	fn cpu_run(req: &Request, response: &mut Response) {
		let id = from_str::<i32>(req.param("id")).unwrap();
		match req.map.find::<Arc<RWLock<CpuServ>>>() {
			Some(cpuserv) => {
				let mut cycles = 0i32;
				let mut w_serv = cpuserv.write();
				let cpu    = w_serv.database.find_mut(&id).unwrap();
				loop {
					if cycles >= MAX_CYCLES { println!("CPU executed > {} cycles.", MAX_CYCLES); break; }
					cycles += 1;

					match cpu.tick() {
						p150::Halt => { break; },
						p150::Continue => { continue; },
					}
				}

				response.send("");
			},
			None => { response.send("did not find cpu serv"); }
		}
	}

	router.get("/", index_show);
	router.get("/about", about_show);

	router.post("/cpu/new", cpu_new);
	router.post("/cpu/:id/tick", cpu_tick);
	router.post("/cpu/:id/run", cpu_run);
	router.post("/cpu/:id/load", cpu_load);
	router.get("/cpu/:id/dump", cpu_dump);
	// TODO: /cpu/:id/tick, /cpu/:id/reset
	// TODO: /cpu/:id/save ???

	server.utilize(CpuMw { db: Arc::new(RWLock::new(CpuServ::new())) });
	server.utilize(StaticFilesHandler::new("./public"));
	server.utilize(Nickel::json_body_parser());
	server.utilize(router);

	server.listen(Ipv4Addr(0,0,0,0), 3200);
}

<|endoftext|>


<commit_before>use std::io::prelude::*;
use std::fs::File;
use std::path::Path;
use std::error::Error;
use std::env;

extern crate ncurses;
use ncurses::*;

//extern crate getopts;
//use getopts::Options;

fn main() {
    let mut buffer = vec![];
    let mut cursorpos:usize = 0;
    const SPALTEN:usize = 16;
//    let mut command = vec![]; // If command is typed by user, store it
//    command.push(":asdf");
    let mut command = String::new();

    initscr(); //start ncursesw
    let screenheight : usize = getmaxy(stdscr) as usize;
    cbreak();  //ctrl+z and fg works with this
    noecho();
    start_color();
    init_pair(1, COLOR_GREEN, COLOR_BLACK);

    printw("Welcome to Hexdino.\nPlease find the any-key on your keyboard and press it.\n");

    let args: Vec<_> = env::args().collect();
    /*
    let program = args[0].clone();
    let mut opts = Options::new();
    opts.optopt("f", "", "set file name", "NAME");
    opts.optflag("h", "help", "print this help menu");

    let matches = match opts.parse(&args[1..]) {
        Ok(m) => { m }
        Err(f) => { panic!(f.to_string()) }
    };
    if matches.opt_present("h") {
        let brief = format!("Usage: {} FILE [options]", program);
            print!("{}", opts.usage(&brief));
        return;
    }
    let path = if !matches.free.is_empty() {
        matches.free[0].clone()
    } else {
        let brief = format!("Usage: {} FILE [options]", program);
            print!("{}", opts.usage(&brief));
        return;
    };*/

    let path = if args.len() > 1 {
        printw(&format!("Opening file {} (TODO)", args[1]));
        args[1].clone()
    } else {
        "foo.txt".into()
    };

    let path = Path::new(&path);
    let display = path.display();

    getch();
    clear();

    if !has_colors() {
        endwin();
        println!("Your terminal does not support color!\n");
        return;
    }

//TODO in this order: 1)disp files 2) hjkl movement 3) edit file by 'r' 4) save file 5) edit file by 'x, i' 6) search by '/'

    let mut file = match File::open(&path) {
//        Err(why) => printw("oh... file not opened..."),
        Err(why) => panic!("couldn't open {}: {}", display,
            Error::description(&why)),
        Ok(file) => file,
    };

//    file.read_to_end(&mut buffer).ok().expect("File could not be read.");
    file.read_to_end(&mut buffer).ok().expect("File could not be read.");

    let mut mode = 0; // 0 Command mode, 1 replace next char, 2 type a command, TODO 3 Insert

    draw(&buffer, cursorpos, SPALTEN, screenheight, mode, &command);

    let mut key;
//    key = getch();
//    printw(&format!("{:?}", key));

    let mut ragequitnow = 0;
    while ragequitnow == 0 {
        key = getch();
        if mode == 0 { // movement mode
            match key {
                104 => if cursorpos != 0 {cursorpos-=1}, //left is "-1"
                106 => if cursorpos+SPALTEN < buffer.len() {cursorpos+=SPALTEN} //down is "+16"
                        else {cursorpos=buffer.len()-1}, //down if on last line is "to end"
                107 => if cursorpos >= SPALTEN {cursorpos-=SPALTEN}, //up is "-16"
                108 => if cursorpos != buffer.len()-1 {cursorpos+=1}, //right is "+1"
                 48 => cursorpos -= cursorpos%16, //start of line is "to start"
                 36 => if cursorpos-(cursorpos%16)+(SPALTEN-1) < buffer.len() {cursorpos = cursorpos-(cursorpos%16)+(SPALTEN-1)} //dollar is "to end"
                        else {cursorpos = buffer.len()-1},
                114 => mode = 1, //r replaces the next char
                 27 => ragequitnow = 1, // TODO replace by KEY_...?
                 58 => mode = 2, //TODO use screenheight;
//                 63 => printw("{:?}", asdf), //TODO: print available key helpfile
                _ => (),
            }
        } else
        if mode == 1 { // r was pressed so replace next char
            match key {
                c @ 32...126 => { buffer[cursorpos] = c as u8; mode = 0 },
                27 => mode = 0,
                _ => (),
            }
        } else
        if mode == 2 {
            match key {
                c @ 32...126 => { command.push(c as u8 as char); }, //TODO check this
                27 => {command.clear();mode = 0;},
                _ => (),
            }
        }
 //       printw(&format!("{:?}", key));
        draw(&buffer, cursorpos, SPALTEN, screenheight, mode, &command);
    }

    refresh();
    endwin();
}

fn draw(buffer:&Vec<u8>, cursorpos:usize, spalten:usize, maxzeilen:usize, mode:usize, command:&String) {
//    let zeilen = buffer.len() / spalten;
    erase();

    let mut zeilen = buffer.len() / spalten;
    if zeilen >= maxzeilen-1 { // Last line reserved for Status/Commands/etc (Like in vim)
        zeilen = maxzeilen-2;
    }

    for z in 0 .. zeilen+1 {
        for s in 0 .. spalten {
            if z*spalten+s == cursorpos { attron(COLOR_PAIR(1)); }
            if z*spalten+s < buffer.len() { printw(&format!("{:02X} ", buffer[z*spalten+s]) ); }
                else {printw("-- "); }
            if z*spalten+s == cursorpos { attroff(COLOR_PAIR(1)); }
        }
        printw(" "); // Space between hex and ascii
        for s in 0 .. spalten {
            if z*spalten+s == cursorpos { attron(COLOR_PAIR(1)); }
                if z*spalten+s < buffer.len() {
                    if let c @ 32...126 = buffer[z*spalten+s] {printw(&format!("{}", c as char) );}
                    else {printw(&format!(".") );}
                }
            if z*spalten+s == cursorpos { attroff(COLOR_PAIR(1)); }
        }
        printw("\n");
    }
//TODO: use maxzeilen to draw the command (if in mode == 2) on last line of terminal
//show that the mode is 2 by adding a ':'
    if mode == 2 {
        printw(":");
    }
    printw(&format!("{}", command));
}
<commit_msg>Implement ':w' to write the file<commit_after>use std::io::prelude::*;
use std::fs::OpenOptions;
use std::io::SeekFrom;
use std::path::Path;
use std::error::Error;
use std::env;

extern crate ncurses;
use ncurses::*;

//extern crate getopts;
//use getopts::Options;

fn main() {
    let mut buffer = vec![];
    let mut cursorpos:usize = 0;
    const SPALTEN:usize = 16;
//    let mut command = vec![]; // If command is typed by user, store it
//    command.push(":asdf");
    let mut command = String::new();

    initscr(); //start ncursesw
    let screenheight : usize = getmaxy(stdscr) as usize;
    cbreak();  //ctrl+z and fg works with this
    noecho();
    start_color();
    init_pair(1, COLOR_GREEN, COLOR_BLACK);

    printw("Welcome to Hexdino.\nPlease find the any-key on your keyboard and press it.\n");

    let args: Vec<_> = env::args().collect();
    /*
    let program = args[0].clone();
    let mut opts = Options::new();
    opts.optopt("f", "", "set file name", "NAME");
    opts.optflag("h", "help", "print this help menu");

    let matches = match opts.parse(&args[1..]) {
        Ok(m) => { m }
        Err(f) => { panic!(f.to_string()) }
    };
    if matches.opt_present("h") {
        let brief = format!("Usage: {} FILE [options]", program);
            print!("{}", opts.usage(&brief));
        return;
    }
    let path = if !matches.free.is_empty() {
        matches.free[0].clone()
    } else {
        let brief = format!("Usage: {} FILE [options]", program);
            print!("{}", opts.usage(&brief));
        return;
    };*/

    let path = if args.len() > 1 {
        printw(&format!("Opening file {} (TODO)", args[1]));
        args[1].clone()
    } else {
        "foo.txt".into()
    };

    let path = Path::new(&path);
    let display = path.display();

    getch();
    clear();

    if !has_colors() {
        endwin();
        println!("Your terminal does not support color!\n");
        return;
    }

//TODO in this order: 1)disp files 2) hjkl movement 3) edit file by 'r' 4) save file 5) edit file by 'x, i' 6) search by '/'

    let mut file = match OpenOptions::new().read(true).write(true).create(true).open(&path) {
//        Err(why) => printw("oh... file not opened..."),
        Err(why) => panic!("couldn't open {}: {}", display,
            Error::description(&why)),
        Ok(file) => file,
    };

//    file.read_to_end(&mut buffer).ok().expect("File could not be read.");
    file.read_to_end(&mut buffer).ok().expect("File could not be read.");

    let mut mode = 0; // 0 Command mode, 1 replace next char, 2 type a command, TODO 3 Insert

    draw(&buffer, cursorpos, SPALTEN, screenheight, mode, &command);

    let mut key;
//    key = getch();
//    printw(&format!("{:?}", key));

    let mut ragequitnow = 0;
    while ragequitnow == 0 {
        key = getch();
        if mode == 0 { // movement mode
            match key {
                104 => if cursorpos != 0 {cursorpos-=1}, //left is "-1"
                106 => if cursorpos+SPALTEN < buffer.len() {cursorpos+=SPALTEN} //down is "+16"
                        else {cursorpos=buffer.len()-1}, //down if on last line is "to end"
                107 => if cursorpos >= SPALTEN {cursorpos-=SPALTEN}, //up is "-16"
                108 => if cursorpos != buffer.len()-1 {cursorpos+=1}, //right is "+1"
                 48 => cursorpos -= cursorpos%16, //start of line is "to start"
                 36 => if cursorpos-(cursorpos%16)+(SPALTEN-1) < buffer.len() {cursorpos = cursorpos-(cursorpos%16)+(SPALTEN-1)} //dollar is "to end"
                        else {cursorpos = buffer.len()-1},
                114 => mode = 1, //r replaces the next char
                 27 => ragequitnow = 1, // TODO replace by KEY_...?
                 58 => mode = 2, // ":" TODO use screenheight;
//                 63 => printw("{:?}", asdf), //TODO: print available key helpfile
                _ => (),
            }
        } else
        if mode == 1 { // r was pressed so replace next char
            match key {
                c @ 32...126 => { buffer[cursorpos] = c as u8; mode = 0 },
                27 => mode = 0,
                _ => (),
            }
        } else
        if mode == 2 {
            match key {
                c @ 32...126 => { command.push(c as u8 as char); }, //TODO check this
                27 => {command.clear();mode = 0;},
                10 => { // Enter pressed, compute command!
                        if command == "w".to_string() {
                            file.seek(SeekFrom::Start(0)).ok().expect("Filepointer could not be set to 0");
                            file.write_all(&mut buffer).ok().expect("File could not be written.");
                            mode = 0;
                        }
                    },
                _ => (),
            }
        }
 //       printw(&format!("{:?}", key));
        draw(&buffer, cursorpos, SPALTEN, screenheight, mode, &command);
    }

    refresh();
    endwin();
}

fn draw(buffer:&Vec<u8>, cursorpos:usize, spalten:usize, maxzeilen:usize, mode:usize, command:&String) {
//    let zeilen = buffer.len() / spalten;
    erase();

    let mut zeilen = buffer.len() / spalten;
    if zeilen >= maxzeilen-1 { // Last line reserved for Status/Commands/etc (Like in vim)
        zeilen = maxzeilen-2;
    }

    for z in 0 .. zeilen+1 {
        for s in 0 .. spalten {
            if z*spalten+s == cursorpos { attron(COLOR_PAIR(1)); }
            if z*spalten+s < buffer.len() { printw(&format!("{:02X} ", buffer[z*spalten+s]) ); }
                else {printw("-- "); }
            if z*spalten+s == cursorpos { attroff(COLOR_PAIR(1)); }
        }
        printw(" "); // Space between hex and ascii
        for s in 0 .. spalten {
            if z*spalten+s == cursorpos { attron(COLOR_PAIR(1)); }
                if z*spalten+s < buffer.len() {
                    if let c @ 32...126 = buffer[z*spalten+s] {printw(&format!("{}", c as char) );}
                    else {printw(&format!(".") );}
                }
            if z*spalten+s == cursorpos { attroff(COLOR_PAIR(1)); }
        }
        printw("\n");
    }
//TODO: use maxzeilen to draw the command (if in mode == 2) on last line of terminal
//show that the mode is 2 by adding a ':'
    if mode == 2 {
        printw(":");
    }
    printw(&format!("{}", command));
}
<|endoftext|>


<commit_before>extern crate nalgebra as na;
extern crate kiss3d;
extern crate rand;
extern crate num;
extern crate clap;

use kiss3d::window::Window;
use na::{Pnt2, Pnt3, Vec2, Vec3, Norm, FloatPnt, FloatVec};
use rand::{Rng, Closed01};
use num::Zero;
use clap::{Arg, App};
use std::str::FromStr;

struct Node<T> {
    parent: usize,
    position: T,
}

pub struct SpaceColonization<T, F>
    where T: FloatPnt<f32, F>,
          F: FloatVec<f32> + Zero + Copy
{
    nodes: Vec<Node<T>>,
    leaf_node_pointers: Vec<usize>, // XXX: how can a branch be terminated?
    leaf_node_grow_directions: Vec<F>,
}

impl<T, F> SpaceColonization<T, F>
    where T: FloatPnt<f32, F>,
          F: FloatVec<f32> + Zero + Copy
{
    fn new() -> SpaceColonization<T, F> {
        SpaceColonization {
            nodes: Vec::new(),
            leaf_node_pointers: Vec::new(),
            leaf_node_grow_directions: Vec::new(),
        }
    }

    // adds initial root node(s)
    fn add_root_node(&mut self, pos: T) {
        debug_assert!(self.leaf_node_pointers.len() == self.leaf_node_grow_directions.len()); // invariant

        // a root node has it's own index as parent
        let idx = self.nodes.len();
        self.nodes.push(Node {
            parent: idx,
            position: pos,
        });

        // a root node creates a new "leaf" node (better: branch!)
        self.leaf_node_pointers.push(idx);
        self.leaf_node_grow_directions.push(Zero::zero());

        debug_assert!(self.leaf_node_pointers.len() == self.leaf_node_grow_directions.len()); // invariant
    }

    fn add_moved_leaf_node(&mut self, leaf_node_slot: usize, v: F) {
        let parent = self.leaf_node_pointers[leaf_node_slot];
        let parent_pos = self.nodes[parent].position;

        self.update_leaf_node(leaf_node_slot, parent_pos + v)
    }

    fn update_leaf_node(&mut self, leaf_node_slot: usize, new_pos: T) {
        let parent = self.leaf_node_pointers[leaf_node_slot];

        // create new node
        let idx = self.nodes.len();
        self.nodes.push(Node {
            parent: parent,
            position: new_pos,
        });

        // update the leaf node pointer
        self.leaf_node_pointers[leaf_node_slot] = idx;
    }

    fn find_nearest_leaf_for_attraction_point(&self,
                                              attraction_pt: &T,
                                              radius_influence_sq: f32)
                                              -> Option<(usize, f32)> {
        let mut nearest_leaf: Option<usize> = None;
        let mut nearest_distance_sq: f32 = radius_influence_sq;

        for (i, &leaf_node_ptr) in self.leaf_node_pointers.iter().enumerate() {
            let leaf_pos = &self.nodes[leaf_node_ptr].position;
            let dist_sq = leaf_pos.sqdist(attraction_pt);

            // is the attraction point within the radius of influence
            // and closed than the currently best
            if dist_sq < nearest_distance_sq {
                nearest_distance_sq = dist_sq;
                nearest_leaf = Some(i);
            }
        }

        return nearest_leaf.map(|idx| (idx, nearest_distance_sq));
    }

    fn iter_segments<C>(&self, callback: &mut C)
        where C: FnMut(&T, &T)
    {
        for (i, node) in self.nodes.iter().enumerate() {
            if i != node.parent {
                callback(&node.position, &self.nodes[node.parent].position);
            }
        }
    }

    fn iterate(&mut self,
               attraction_points: &mut [(T, bool)],
               radius_influce_sq: f32,
               move_distance: f32,
               kill_distance_sq: f32) {
        // reset all attraction forces
        for force in &mut self.leaf_node_grow_directions {
            *force = Zero::zero();
        }

        // for each attraction_point, find the nearest (leaf) node that it
        // influences.
        for ap in attraction_points.iter_mut() {
            let active = ap.1;
            if !active {
                continue;
            }
            let nearest_leaf_opt = self.find_nearest_leaf_for_attraction_point(&ap.0,
                                                                               radius_influce_sq);
            if let Some((nearest_leaf_slot, d_sq)) = nearest_leaf_opt {
                if d_sq < kill_distance_sq {
                    // set inactive
                    ap.1 = false;
                } else {
                    let nearest_leaf_node_idx = self.leaf_node_pointers[nearest_leaf_slot];
                    // update the force with the normalized vector towards the attraction point
                    let v = (ap.0 - self.nodes[nearest_leaf_node_idx].position).normalize();
                    self.leaf_node_grow_directions[nearest_leaf_slot] =
                        self.leaf_node_grow_directions[nearest_leaf_slot] + v;
                }
            }
        }

        // now create new leaf nodes
        for i in 0..self.leaf_node_grow_directions.len() {
            let force = self.leaf_node_grow_directions[i];

            // update leaf node only if there is a force!
            if !force.is_zero() {
                let n = force.normalize();
                self.add_moved_leaf_node(i, n * move_distance);
            }
        }

    }
}

fn random_closed01<R: Rng>(rng: &mut R) -> f32 {
    rng.gen::<Closed01<f32>>().0
}

fn random_coord<R: Rng>(rng: &mut R) -> f32 {
    2.0 * random_closed01(rng) - 1.0
}

trait MyPoint {
    fn into_pnt3(self) -> Pnt3<f32>;
    fn random<R: Rng>(rng: &mut R) -> Self;
}

impl MyPoint for Pnt3<f32> {
    fn into_pnt3(self) -> Pnt3<f32> {
        self
    }

    fn random<R: Rng>(rng: &mut R) -> Pnt3<f32> {
        Pnt3::new(random_coord(rng), random_coord(rng), random_coord(rng))
    }
}

impl MyPoint for Pnt2<f32> {
    fn into_pnt3(self) -> Pnt3<f32> {
        Pnt3::new(self.x, self.y, 3.0)
    }

    fn random<R: Rng>(rng: &mut R) -> Pnt2<f32> {
        Pnt2::new(random_coord(rng), random_coord(rng))
    }
}

#[derive(Debug)]
struct Config {
    n_attraction_points: usize,
    n_roots: usize,
    influence_radius: f32,
    move_distance: f32,
    kill_distance: f32,
    use_3d: bool,
    max_iter: Option<usize>,
}

impl Config {
    fn config1() -> Config {
        Config {
            n_attraction_points: 10_000,
            n_roots: 5,
            influence_radius: 0.44,
            kill_distance: 0.22,
            move_distance: 0.02,
            use_3d: true,
            max_iter: None,
        }
    }

    fn config2() -> Config {
        Config {
            n_attraction_points: 10_000,
            n_roots: 5,
            influence_radius: 0.1,
            kill_distance: 0.07,
            move_distance: 0.01,
            use_3d: false,
            max_iter: None,
        }
    }

    fn from_cmd() -> Config {
        let matches = App::new("space-colonization")
                          .arg(Arg::with_name("NUM_POINTS")
                                   .long("num-points")
                                   .help("Number of attraction points (default: 1000)")
                                   .takes_value(true)
                                   .required(false))
                          .arg(Arg::with_name("NUM_ROOTS")
                                   .long("num-roots")
                                   .help("Number of root nodes (default: 1)")
                                   .takes_value(true)
                                   .required(false))
                          .arg(Arg::with_name("IR")
                                   .long("radius")
                                   .help("Influence radius (default: 0.25)")
                                   .takes_value(true)
                                   .required(false))
                          .arg(Arg::with_name("KD")
                                   .long("kill-distance")
                                   .help("Kill distance (default: 0.1)")
                                   .takes_value(true)
                                   .required(false))
                          .arg(Arg::with_name("MD")
                                   .long("move-distance")
                                   .help("Kill distance (default: 0.05)")
                                   .takes_value(true)
                                   .required(false))
                          .arg(Arg::with_name("MAX_ITER")
                                   .long("max-iter")
                                   .help("Maximum iterations (default: infinite)")
                                   .takes_value(true)
                                   .required(false))
                          .arg(Arg::with_name("USE_3D")
                                   .long("use-3d")
                                   .help("Use 3d mode"))
                          .get_matches();

        Config {
            n_attraction_points: usize::from_str(matches.value_of("NUM_POINTS").unwrap_or("1000"))
                                     .unwrap(),
            n_roots: usize::from_str(matches.value_of("NUM_ROOTS").unwrap_or("1")).unwrap(),
            influence_radius: f32::from_str(matches.value_of("IR").unwrap_or("0.25")).unwrap(),
            kill_distance: f32::from_str(matches.value_of("KD").unwrap_or("0.1")).unwrap(),
            move_distance: f32::from_str(matches.value_of("MD").unwrap_or("0.05")).unwrap(),
            use_3d: matches.is_present("USE_3D"),
            max_iter: usize::from_str(matches.value_of("MAX_ITER").unwrap_or("INVALID")).ok(),
        }
    }
}

fn run<T, F>(config: &Config)
    where T: MyPoint + FloatPnt<f32, F>,
          F: FloatVec<f32> + Zero + Copy
{
    let mut rng = rand::thread_rng();

    // generate initial attraction point configuration
    // stores status of attraction point in the second tuple element (true=active, false=used)
    let mut attraction_points: Vec<(T, bool)> = (0..config.n_attraction_points)
                                                    .into_iter()
                                                    .map(|_| {
                                                        (<T as MyPoint>::random(&mut rng), true)
                                                    })
                                                    .collect();

    let mut sc: SpaceColonization<T, F> = SpaceColonization::new();
    for _ in 0..config.n_roots {
        sc.add_root_node(<T as MyPoint>::random(&mut rng));
    }

    let mut window = Window::new("Space Colonization");
    let white = Pnt3::new(1.0, 1.0, 1.0);
    let red = Pnt3::new(1.0, 0.0, 0.0);

    let mut i = 0;

    while window.render() {
        i += 1;

        if let Some(m) = config.max_iter {
            if i > m {
                break;
            }
        }

        println!("Iteration: {}", i);
        for &(pt, status) in &attraction_points {
            if status {
                window.draw_point(&pt.into_pnt3(), &white);
            }
        }

        sc.iter_segments(&mut |&a, &b| {
            window.draw_line(&a.into_pnt3(), &b.into_pnt3(), &red);
        });

        sc.iterate(&mut attraction_points,
                   config.influence_radius * config.influence_radius,
                   config.move_distance,
                   config.kill_distance * config.kill_distance);

    }
}

fn main() {
    let config = Config::from_cmd();

    println!("{:?}", config);

    if config.use_3d {
        run::<Pnt3<f32>, Vec3<f32>>(&config);
    } else {
        run::<Pnt2<f32>, Vec2<f32>>(&config);
    }
}
<commit_msg>Correctly implement the Algorithm.<commit_after>extern crate nalgebra as na;
extern crate kiss3d;
extern crate rand;
extern crate num;
extern crate clap;

use kiss3d::window::Window;
use na::{Pnt2, Pnt3, Vec2, Vec3, Norm, FloatPnt, FloatVec};
use rand::{Rng, Closed01};
use num::Zero;
use clap::{Arg, App};
use std::str::FromStr;

struct Node<T, F> {
    parent: usize,
    position: T,
    growth: F,
}

pub struct SpaceColonization<T, F>
    where T: FloatPnt<f32, F>,
          F: FloatVec<f32> + Zero + Copy
{
    nodes: Vec<Node<T, F>>,
}

impl<T, F> SpaceColonization<T, F>
    where T: FloatPnt<f32, F>,
          F: FloatVec<f32> + Zero + Copy
{
    fn new() -> SpaceColonization<T, F> {
        SpaceColonization { nodes: Vec::new() }
    }

    pub fn add_root_node(&mut self, position: T) {
        self.add_node(position, None);
    }

    fn add_node(&mut self, position: T, parent: Option<usize>) {
        // NOTE: a root node has it's own index as parent
        let len = self.nodes.len();
        let parent = match parent {
            Some(p) => {
                assert!(p < len);
                p
            }
            None => len,
        };

        self.nodes.push(Node {
            parent: parent,
            position: position,
            growth: Zero::zero(),
        });
    }

    fn iter_segments<C>(&self, callback: &mut C)
        where C: FnMut(&T, &T)
    {
        for (i, node) in self.nodes.iter().enumerate() {
            if i != node.parent {
                callback(&node.position, &self.nodes[node.parent].position);
            }
        }
    }

    fn iterate(&mut self,
               attraction_points: &mut [(T, bool)],
               influence_radius_sq: f32,
               move_distance: f32,
               kill_distance_sq: f32)
               -> usize {
        assert!(kill_distance_sq <= influence_radius_sq);

        // for each attraction_point, find the nearest node that it influences
        for ap in attraction_points.iter_mut() {
            let active = ap.1;
            if !active {
                continue;
            }

            // find the node nearest to the `ap` attraction point
            let mut nearest_node: Option<usize> = None;
            let mut nearest_distance_sq: f32 = influence_radius_sq;
            for (i, node) in self.nodes.iter().enumerate() {
                let dist_sq = node.position.sqdist(&ap.0);

                if dist_sq < kill_distance_sq {
                    // set attraction point inactive
                    ap.1 = false;
                    nearest_node = None;
                    break;
                }

                // is the attraction point within the radius of influence
                // and closer than the currently best
                if dist_sq < nearest_distance_sq {
                    nearest_distance_sq = dist_sq;
                    nearest_node = Some(i);
                }
            }

            if let Some(nearest_node_idx) = nearest_node {
                // update the force with the normalized vector towards the attraction point
                let v = (ap.0 - self.nodes[nearest_node_idx].position).normalize();
                self.nodes[nearest_node_idx].growth = self.nodes[nearest_node_idx].growth + v;
            }
        }

        // now create new nodes
        let mut new_nodes = 0;
        let len = self.nodes.len();
        for i in 0..len {
            let growth = self.nodes[i].growth;
            // update leaf node only if there is a force!
            if !growth.is_zero() {
                new_nodes += 1;
                let n = growth.normalize();
                let new_position = self.nodes[i].position + (n * move_distance);
                self.add_node(new_position, Some(i));
                // and reset growth attraction forces
                self.nodes[i].growth = Zero::zero();
            }
        }

        return new_nodes;
    }
}

fn random_closed01<R: Rng>(rng: &mut R) -> f32 {
    rng.gen::<Closed01<f32>>().0
}

fn random_coord<R: Rng>(rng: &mut R) -> f32 {
    2.0 * random_closed01(rng) - 1.0
}

trait MyPoint {
    fn into_pnt3(self) -> Pnt3<f32>;
    fn random<R: Rng>(rng: &mut R) -> Self;
}

impl MyPoint for Pnt3<f32> {
    fn into_pnt3(self) -> Pnt3<f32> {
        self
    }

    fn random<R: Rng>(rng: &mut R) -> Pnt3<f32> {
        Pnt3::new(random_coord(rng), random_coord(rng), random_coord(rng))
    }
}

impl MyPoint for Pnt2<f32> {
    fn into_pnt3(self) -> Pnt3<f32> {
        Pnt3::new(self.x, self.y, 3.0)
    }

    fn random<R: Rng>(rng: &mut R) -> Pnt2<f32> {
        Pnt2::new(random_coord(rng), random_coord(rng))
    }
}

#[derive(Debug)]
struct Config {
    n_attraction_points: usize,
    n_roots: usize,
    influence_radius: f32,
    move_distance: f32,
    kill_distance: f32,
    use_3d: bool,
    max_iter: Option<usize>,
}

impl Config {
    #[allow(dead_code)]
    fn config1() -> Config {
        Config {
            n_attraction_points: 10_000,
            n_roots: 5,
            influence_radius: 0.44,
            kill_distance: 0.22,
            move_distance: 0.02,
            use_3d: true,
            max_iter: None,
        }
    }

    #[allow(dead_code)]
    fn config2() -> Config {
        Config {
            n_attraction_points: 10_000,
            n_roots: 5,
            influence_radius: 0.1,
            kill_distance: 0.07,
            move_distance: 0.01,
            use_3d: false,
            max_iter: None,
        }
    }

    fn from_cmd() -> Config {
        let matches = App::new("space-colonization")
                          .arg(Arg::with_name("NUM_POINTS")
                                   .long("num-points")
                                   .help("Number of attraction points (default: 1000)")
                                   .takes_value(true)
                                   .required(false))
                          .arg(Arg::with_name("NUM_ROOTS")
                                   .long("num-roots")
                                   .help("Number of root nodes (default: 1)")
                                   .takes_value(true)
                                   .required(false))
                          .arg(Arg::with_name("IR")
                                   .long("radius")
                                   .help("Influence radius (default: 0.25)")
                                   .takes_value(true)
                                   .required(false))
                          .arg(Arg::with_name("KD")
                                   .long("kill-distance")
                                   .help("Kill distance (default: 0.1)")
                                   .takes_value(true)
                                   .required(false))
                          .arg(Arg::with_name("MD")
                                   .long("move-distance")
                                   .help("Kill distance (default: 0.05)")
                                   .takes_value(true)
                                   .required(false))
                          .arg(Arg::with_name("MAX_ITER")
                                   .long("max-iter")
                                   .help("Maximum iterations (default: infinite)")
                                   .takes_value(true)
                                   .required(false))
                          .arg(Arg::with_name("USE_3D")
                                   .long("use-3d")
                                   .help("Use 3d mode"))
                          .get_matches();

        Config {
            n_attraction_points: usize::from_str(matches.value_of("NUM_POINTS").unwrap_or("1000"))
                                     .unwrap(),
            n_roots: usize::from_str(matches.value_of("NUM_ROOTS").unwrap_or("1")).unwrap(),
            influence_radius: f32::from_str(matches.value_of("IR").unwrap_or("0.25")).unwrap(),
            kill_distance: f32::from_str(matches.value_of("KD").unwrap_or("0.1")).unwrap(),
            move_distance: f32::from_str(matches.value_of("MD").unwrap_or("0.05")).unwrap(),
            use_3d: matches.is_present("USE_3D"),
            max_iter: usize::from_str(matches.value_of("MAX_ITER").unwrap_or("INVALID")).ok(),
        }
    }
}

fn run<T, F>(config: &Config)
    where T: MyPoint + FloatPnt<f32, F>,
          F: FloatVec<f32> + Zero + Copy
{
    let mut rng = rand::thread_rng();

    // generate initial attraction point configuration
    // stores status of attraction point in the second tuple element (true=active, false=used)
    let mut attraction_points: Vec<(T, bool)> = (0..config.n_attraction_points)
                                                    .into_iter()
                                                    .map(|_| {
                                                        (<T as MyPoint>::random(&mut rng), true)
                                                    })
                                                    .collect();

    let mut sc: SpaceColonization<T, F> = SpaceColonization::new();
    for _ in 0..config.n_roots {
        sc.add_root_node(<T as MyPoint>::random(&mut rng));
    }

    let mut window = Window::new("Space Colonization");
    let white = Pnt3::new(1.0, 1.0, 1.0);
    let red = Pnt3::new(1.0, 0.0, 0.0);

    let mut i = 0;

    while window.render() {
        i += 1;

        if let Some(m) = config.max_iter {
            if i > m {
                break;
            }
        }

        println!("Iteration: {}", i);
        for &(pt, status) in &attraction_points {
            if status {
                window.draw_point(&pt.into_pnt3(), &white);
            }
        }

        sc.iter_segments(&mut |&a, &b| {
            window.draw_line(&a.into_pnt3(), &b.into_pnt3(), &red);
        });

        let new_nodes = sc.iterate(&mut attraction_points,
                                   config.influence_radius * config.influence_radius,
                                   config.move_distance,
                                   config.kill_distance * config.kill_distance);

        if new_nodes == 0 {
            break;
        }
    }
}

fn main() {
    let config = Config::from_cmd();

    println!("{:?}", config);

    if config.use_3d {
        run::<Pnt3<f32>, Vec3<f32>>(&config);
    } else {
        run::<Pnt2<f32>, Vec2<f32>>(&config);
    }
}
<|endoftext|>