<commit_before>//! Emit diagnostics using the `annotate-snippets` library
//!
//! This is the equivalent of `./emitter.rs` but making use of the
//! [`annotate-snippets`][annotate_snippets] library instead of building the output ourselves.
//!
//! [annotate_snippets]: https://docs.rs/crate/annotate-snippets/

use syntax_pos::{SourceFile, MultiSpan, Loc};
use crate::{
    Level, CodeSuggestion, DiagnosticBuilder, Emitter,
    SourceMapperDyn, SubDiagnostic, DiagnosticId
};
use crate::emitter::FileWithAnnotatedLines;
use rustc_data_structures::sync::Lrc;
use crate::snippet::Line;
use annotate_snippets::snippet::*;
use annotate_snippets::display_list::DisplayList;
use annotate_snippets::formatter::DisplayListFormatter;

/// Generates diagnostics using annotate-snippet
pub struct AnnotateSnippetEmitterWriter {
    source_map: Option<Lrc<SourceMapperDyn>>,
    /// If true, hides the longer explanation text
    short_message: bool,
    /// If true, will normalize line numbers with `LL` to prevent noise in UI test diffs.
    ui_testing: bool,
}

impl Emitter for AnnotateSnippetEmitterWriter {
    /// The entry point for the diagnostics generation
    fn emit_diagnostic(&mut self, db: &DiagnosticBuilder<'_>) {
        let mut children = db.children.clone();
        let (mut primary_span, suggestions) = self.primary_span_formatted(&db);

        self.fix_multispans_in_std_macros(&self.source_map,
                                          &mut primary_span,
                                          &mut children,
                                          &db.level,
                                          db.handler.flags.external_macro_backtrace);

        self.emit_messages_default(&db.level,
                                   db.message(),
                                   &db.code,
                                   &primary_span,
                                   &children,
                                   &suggestions);
    }

    fn should_show_explain(&self) -> bool {
        !self.short_message
    }
}

/// Collects all the data needed to generate the data structures needed for the
/// `annotate-snippets` library.
struct DiagnosticConverter<'a> {
    source_map: Option<Lrc<SourceMapperDyn>>,
    level: Level,
    message: String,
    code: Option<DiagnosticId>,
    msp: MultiSpan,
    #[allow(dead_code)]
    children: &'a [SubDiagnostic],
    #[allow(dead_code)]
    suggestions: &'a [CodeSuggestion]
}

impl<'a>  DiagnosticConverter<'a> {
    /// Turns rustc Diagnostic information into a `annotate_snippets::snippet::Snippet`.
    fn to_annotation_snippet(&self) -> Option<Snippet> {
        if let Some(source_map) = &self.source_map {
            // Make sure our primary file comes first
            let primary_lo = if let Some(ref primary_span) =
                self.msp.primary_span().as_ref() {
                source_map.lookup_char_pos(primary_span.lo())
            } else {
                // FIXME(#59346): Not sure when this is the case and what
                // should be done if it happens
                return None
            };
            let annotated_files = FileWithAnnotatedLines::collect_annotations(
                &self.msp,
                &self.source_map
            );
            let slices = self.slices_for_files(annotated_files, primary_lo);

            Some(Snippet {
                title: Some(Annotation {
                    label: Some(self.message.to_string()),
                    id: self.code.clone().map(|c| {
                        match c {
                            DiagnosticId::Error(val) | DiagnosticId::Lint(val) => val
                        }
                    }),
                    annotation_type: Self::annotation_type_for_level(self.level),
                }),
                footer: vec![],
                slices,
            })
        } else {
            // FIXME(#59346): Is it ok to return None if there's no source_map?
            None
        }
    }

    fn slices_for_files(
        &self,
        annotated_files: Vec<FileWithAnnotatedLines>,
        primary_lo: Loc
    ) -> Vec<Slice> {
        // FIXME(#59346): Provide a test case where `annotated_files` is > 1
        annotated_files.iter().flat_map(|annotated_file| {
            annotated_file.lines.iter().map(|line| {
                let line_source = Self::source_string(annotated_file.file.clone(), &line);
                Slice {
                    source: line_source,
                    line_start: line.line_index,
                    origin: Some(primary_lo.file.name.to_string()),
                    // FIXME(#59346): Not really sure when `fold` should be true or false
                    fold: false,
                    annotations: line.annotations.iter().map(|a| {
                        self.annotation_to_source_annotation(a.clone())
                    }).collect(),
                }
            }).collect::<Vec<Slice>>()
        }).collect::<Vec<Slice>>()
    }

    /// Turns a `crate::snippet::Annotation` into a `SourceAnnotation`
    fn annotation_to_source_annotation(
        &self,
        annotation: crate::snippet::Annotation
    ) -> SourceAnnotation {
        SourceAnnotation {
            range: (annotation.start_col, annotation.end_col),
            label: annotation.label.unwrap_or("".to_string()),
            annotation_type: Self::annotation_type_for_level(self.level)
        }
    }

    /// Provides the source string for the given `line` of `file`
    fn source_string(
        file: Lrc<SourceFile>,
        line: &Line
    ) -> String {
        file.get_line(line.line_index - 1).map(|a| a.to_string()).unwrap_or(String::new())
    }

    /// Maps `Diagnostic::Level` to `snippet::AnnotationType`
    fn annotation_type_for_level(level: Level) -> AnnotationType {
        match level {
            Level::Bug | Level::Fatal | Level::Error => AnnotationType::Error,
            Level::Warning => AnnotationType::Warning,
            Level::Note => AnnotationType::Note,
            Level::Help => AnnotationType::Help,
            // FIXME(#59346): Not sure how to map these two levels
            Level::Cancelled | Level::FailureNote => AnnotationType::Error
        }
    }
}

impl AnnotateSnippetEmitterWriter {
    pub fn new(
        source_map: Option<Lrc<SourceMapperDyn>>,
        short_message: bool
    ) -> Self {
        Self {
            source_map,
            short_message,
            ui_testing: false,
        }
    }

    /// Allows to modify `Self` to enable or disable the `ui_testing` flag.
    ///
    /// If this is set to true, line numbers will be normalized as `LL` in the output.
    pub fn ui_testing(mut self, ui_testing: bool) -> Self {
        self.ui_testing = ui_testing;
        self
    }

    fn emit_messages_default(
        &mut self,
        level: &Level,
        message: String,
        code: &Option<DiagnosticId>,
        msp: &MultiSpan,
        children: &[SubDiagnostic],
        suggestions: &[CodeSuggestion]
    ) {
        let converter = DiagnosticConverter {
            source_map: self.source_map.clone(),
            level: level.clone(),
            message,
            code: code.clone(),
            msp: msp.clone(),
            children,
            suggestions
        };
        if let Some(snippet) = converter.to_annotation_snippet() {
            let dl = DisplayList::from(snippet);
            let dlf = DisplayListFormatter::new(true, self.ui_testing);
            // FIXME(#59346): Figure out if we can _always_ print to stderr or not.
            // `emitter.rs` has the `Destination` enum that lists various possible output
            // destinations.
            eprintln!("{}", dlf.format(&dl));
        };
    }
}
<commit_msg>Rollup merge of #64206 - phansch:update_issue_number, r=varkor<commit_after>//! Emit diagnostics using the `annotate-snippets` library
//!
//! This is the equivalent of `./emitter.rs` but making use of the
//! [`annotate-snippets`][annotate_snippets] library instead of building the output ourselves.
//!
//! [annotate_snippets]: https://docs.rs/crate/annotate-snippets/

use syntax_pos::{SourceFile, MultiSpan, Loc};
use crate::{
    Level, CodeSuggestion, DiagnosticBuilder, Emitter,
    SourceMapperDyn, SubDiagnostic, DiagnosticId
};
use crate::emitter::FileWithAnnotatedLines;
use rustc_data_structures::sync::Lrc;
use crate::snippet::Line;
use annotate_snippets::snippet::*;
use annotate_snippets::display_list::DisplayList;
use annotate_snippets::formatter::DisplayListFormatter;

/// Generates diagnostics using annotate-snippet
pub struct AnnotateSnippetEmitterWriter {
    source_map: Option<Lrc<SourceMapperDyn>>,
    /// If true, hides the longer explanation text
    short_message: bool,
    /// If true, will normalize line numbers with `LL` to prevent noise in UI test diffs.
    ui_testing: bool,
}

impl Emitter for AnnotateSnippetEmitterWriter {
    /// The entry point for the diagnostics generation
    fn emit_diagnostic(&mut self, db: &DiagnosticBuilder<'_>) {
        let mut children = db.children.clone();
        let (mut primary_span, suggestions) = self.primary_span_formatted(&db);

        self.fix_multispans_in_std_macros(&self.source_map,
                                          &mut primary_span,
                                          &mut children,
                                          &db.level,
                                          db.handler.flags.external_macro_backtrace);

        self.emit_messages_default(&db.level,
                                   db.message(),
                                   &db.code,
                                   &primary_span,
                                   &children,
                                   &suggestions);
    }

    fn should_show_explain(&self) -> bool {
        !self.short_message
    }
}

/// Collects all the data needed to generate the data structures needed for the
/// `annotate-snippets` library.
struct DiagnosticConverter<'a> {
    source_map: Option<Lrc<SourceMapperDyn>>,
    level: Level,
    message: String,
    code: Option<DiagnosticId>,
    msp: MultiSpan,
    #[allow(dead_code)]
    children: &'a [SubDiagnostic],
    #[allow(dead_code)]
    suggestions: &'a [CodeSuggestion]
}

impl<'a>  DiagnosticConverter<'a> {
    /// Turns rustc Diagnostic information into a `annotate_snippets::snippet::Snippet`.
    fn to_annotation_snippet(&self) -> Option<Snippet> {
        if let Some(source_map) = &self.source_map {
            // Make sure our primary file comes first
            let primary_lo = if let Some(ref primary_span) =
                self.msp.primary_span().as_ref() {
                source_map.lookup_char_pos(primary_span.lo())
            } else {
                // FIXME(#59346): Not sure when this is the case and what
                // should be done if it happens
                return None
            };
            let annotated_files = FileWithAnnotatedLines::collect_annotations(
                &self.msp,
                &self.source_map
            );
            let slices = self.slices_for_files(annotated_files, primary_lo);

            Some(Snippet {
                title: Some(Annotation {
                    label: Some(self.message.to_string()),
                    id: self.code.clone().map(|c| {
                        match c {
                            DiagnosticId::Error(val) | DiagnosticId::Lint(val) => val
                        }
                    }),
                    annotation_type: Self::annotation_type_for_level(self.level),
                }),
                footer: vec![],
                slices,
            })
        } else {
            // FIXME(#59346): Is it ok to return None if there's no source_map?
            None
        }
    }

    fn slices_for_files(
        &self,
        annotated_files: Vec<FileWithAnnotatedLines>,
        primary_lo: Loc
    ) -> Vec<Slice> {
        // FIXME(#64205): Provide a test case where `annotated_files` is > 1
        annotated_files.iter().flat_map(|annotated_file| {
            annotated_file.lines.iter().map(|line| {
                let line_source = Self::source_string(annotated_file.file.clone(), &line);
                Slice {
                    source: line_source,
                    line_start: line.line_index,
                    origin: Some(primary_lo.file.name.to_string()),
                    // FIXME(#59346): Not really sure when `fold` should be true or false
                    fold: false,
                    annotations: line.annotations.iter().map(|a| {
                        self.annotation_to_source_annotation(a.clone())
                    }).collect(),
                }
            }).collect::<Vec<Slice>>()
        }).collect::<Vec<Slice>>()
    }

    /// Turns a `crate::snippet::Annotation` into a `SourceAnnotation`
    fn annotation_to_source_annotation(
        &self,
        annotation: crate::snippet::Annotation
    ) -> SourceAnnotation {
        SourceAnnotation {
            range: (annotation.start_col, annotation.end_col),
            label: annotation.label.unwrap_or("".to_string()),
            annotation_type: Self::annotation_type_for_level(self.level)
        }
    }

    /// Provides the source string for the given `line` of `file`
    fn source_string(
        file: Lrc<SourceFile>,
        line: &Line
    ) -> String {
        file.get_line(line.line_index - 1).map(|a| a.to_string()).unwrap_or(String::new())
    }

    /// Maps `Diagnostic::Level` to `snippet::AnnotationType`
    fn annotation_type_for_level(level: Level) -> AnnotationType {
        match level {
            Level::Bug | Level::Fatal | Level::Error => AnnotationType::Error,
            Level::Warning => AnnotationType::Warning,
            Level::Note => AnnotationType::Note,
            Level::Help => AnnotationType::Help,
            // FIXME(#59346): Not sure how to map these two levels
            Level::Cancelled | Level::FailureNote => AnnotationType::Error
        }
    }
}

impl AnnotateSnippetEmitterWriter {
    pub fn new(
        source_map: Option<Lrc<SourceMapperDyn>>,
        short_message: bool
    ) -> Self {
        Self {
            source_map,
            short_message,
            ui_testing: false,
        }
    }

    /// Allows to modify `Self` to enable or disable the `ui_testing` flag.
    ///
    /// If this is set to true, line numbers will be normalized as `LL` in the output.
    pub fn ui_testing(mut self, ui_testing: bool) -> Self {
        self.ui_testing = ui_testing;
        self
    }

    fn emit_messages_default(
        &mut self,
        level: &Level,
        message: String,
        code: &Option<DiagnosticId>,
        msp: &MultiSpan,
        children: &[SubDiagnostic],
        suggestions: &[CodeSuggestion]
    ) {
        let converter = DiagnosticConverter {
            source_map: self.source_map.clone(),
            level: level.clone(),
            message,
            code: code.clone(),
            msp: msp.clone(),
            children,
            suggestions
        };
        if let Some(snippet) = converter.to_annotation_snippet() {
            let dl = DisplayList::from(snippet);
            let dlf = DisplayListFormatter::new(true, self.ui_testing);
            // FIXME(#59346): Figure out if we can _always_ print to stderr or not.
            // `emitter.rs` has the `Destination` enum that lists various possible output
            // destinations.
            eprintln!("{}", dlf.format(&dl));
        };
    }
}
<|endoftext|>


<commit_before>extern crate chrono;
#[macro_use] extern crate log;
#[macro_use] extern crate lazy_static;
extern crate semver;
extern crate toml;
extern crate regex;
extern crate itertools;

#[macro_use] extern crate libimagstore;
extern crate libimagutil;
#[macro_use] extern crate libimagerror;
extern crate libimagrt;

module_entry_path_mod!("diary", "0.1.0");

pub mod error;
pub mod iter;
pub mod result;
<commit_msg>Add modules in lib.rs<commit_after>extern crate chrono;
#[macro_use] extern crate log;
#[macro_use] extern crate lazy_static;
extern crate semver;
extern crate toml;
extern crate regex;
extern crate itertools;

#[macro_use] extern crate libimagstore;
extern crate libimagutil;
#[macro_use] extern crate libimagerror;
extern crate libimagrt;

module_entry_path_mod!("diary", "0.1.0");

pub mod error;
pub mod diaryid;
pub mod is_in_diary;
pub mod entry;
pub mod iter;
pub mod result;
<|endoftext|>


<commit_before>/* This Source Code Form is subject to the terms of the Mozilla Public
 * License, v. 2.0. If a copy of the MPL was not distributed with this
 * file, You can obtain one at http://mozilla.org/MPL/2.0/. */

/// Liberally derived from the [Firefox JS implementation](http://mxr.mozilla.org/mozilla-central/source/toolkit/devtools/server/actors/webconsole.js).
/// Mediates interaction between the remote web console and equivalent functionality (object
/// inspection, JS evaluation, autocompletion) in Servo.

use actor::{Actor, ActorRegistry};
use protocol::JsonPacketStream;

use devtools_traits;
use devtools_traits::{DevtoolScriptControlMsg};
use msg::constellation_msg::PipelineId;

use collections::BTreeMap;
use core::cell::RefCell;
use serialize::json::{self, Json, ToJson};
use std::old_io::TcpStream;
use std::num::Float;
use std::sync::mpsc::{channel, Sender};

#[derive(RustcEncodable)]
struct StartedListenersTraits {
    customNetworkRequest: bool,
}

#[derive(RustcEncodable)]
struct StartedListenersReply {
    from: String,
    nativeConsoleAPI: bool,
    startedListeners: Vec<String>,
    traits: StartedListenersTraits,
}

#[derive(RustcEncodable)]
#[allow(dead_code)]
struct ConsoleAPIMessage {
    _type: String, //FIXME: should this be __type__ instead?
}

#[derive(RustcEncodable)]
#[allow(dead_code)]
struct PageErrorMessage {
    _type: String, //FIXME: should this be __type__ instead?
    errorMessage: String,
    sourceName: String,
    lineText: String,
    lineNumber: uint,
    columnNumber: uint,
    category: String,
    timeStamp: uint,
    warning: bool,
    error: bool,
    exception: bool,
    strict: bool,
    private: bool,
}

#[derive(RustcEncodable)]
#[allow(dead_code)]
struct LogMessage {
    _type: String, //FIXME: should this be __type__ instead?
    timeStamp: uint,
    message: String,
}

#[derive(RustcEncodable)]
#[allow(dead_code)]
enum ConsoleMessageType {
    ConsoleAPIType(ConsoleAPIMessage),
    PageErrorType(PageErrorMessage),
    LogMessageType(LogMessage),
}

#[derive(RustcEncodable)]
struct GetCachedMessagesReply {
    from: String,
    messages: Vec<json::Object>,
}

#[derive(RustcEncodable)]
struct StopListenersReply {
    from: String,
    stoppedListeners: Vec<String>,
}

#[derive(RustcEncodable)]
struct AutocompleteReply {
    from: String,
    matches: Vec<String>,
    matchProp: String,
}

#[derive(RustcEncodable)]
struct EvaluateJSReply {
    from: String,
    input: String,
    result: Json,
    timestamp: uint,
    exception: Json,
    exceptionMessage: String,
    helperResult: Json,
}

pub struct ConsoleActor {
    pub name: String,
    pub pipeline: PipelineId,
    pub script_chan: Sender<DevtoolScriptControlMsg>,
    pub streams: RefCell<Vec<TcpStream>>,
}

impl Actor for ConsoleActor {
    fn name(&self) -> String {
        self.name.clone()
    }

    fn handle_message(&self,
                      _registry: &ActorRegistry,
                      msg_type: &String,
                      msg: &json::Object,
                      stream: &mut TcpStream) -> Result<bool, ()> {
        Ok(match msg_type.as_slice() {
            "getCachedMessages" => {
                let types = msg.get(&"messageTypes".to_string()).unwrap().as_array().unwrap();
                let /*mut*/ messages = vec!();
                for msg_type in types.iter() {
                    let msg_type = msg_type.as_string().unwrap();
                    match msg_type.as_slice() {
                        "ConsoleAPI" => {
                            //TODO: figure out all consoleapi properties from FFOX source
                        }

                        "PageError" => {
                            //TODO: make script error reporter pass all reported errors
                            //      to devtools and cache them for returning here.

                            /*let message = PageErrorMessage {
                                _type: msg_type.to_string(),
                                sourceName: "".to_string(),
                                lineText: "".to_string(),
                                lineNumber: 0,
                                columnNumber: 0,
                                category: "".to_string(),
                                warning: false,
                                error: true,
                                exception: false,
                                strict: false,
                                private: false,
                                timeStamp: 0,
                                errorMessage: "page error test".to_string(),
                            };
                            messages.push(json::from_str(json::encode(&message).as_slice()).unwrap().as_object().unwrap().clone());*/
                        }

                        "LogMessage" => {
                            //TODO: figure out when LogMessage is necessary
                            /*let message = LogMessage {
                                _type: msg_type.to_string(),
                                timeStamp: 0,
                                message: "log message test".to_string(),
                            };
                            messages.push(json::from_str(json::encode(&message).as_slice()).unwrap().as_object().unwrap().clone());*/
                        }

                        s => println!("unrecognized message type requested: \"{}\"", s),
                    }
                }

                let msg = GetCachedMessagesReply {
                    from: self.name(),
                    messages: messages,
                };
                stream.write_json_packet(&msg);
                true
            }

            "startListeners" => {
                //TODO: actually implement listener filters that support starting/stopping
                let msg = StartedListenersReply {
                    from: self.name(),
                    nativeConsoleAPI: true,
                    startedListeners:
                        vec!("PageError".to_string(), "ConsoleAPI".to_string()),
                    traits: StartedListenersTraits {
                        customNetworkRequest: true,
                    }
                };
                stream.write_json_packet(&msg);
                true
            }

            "stopListeners" => {
                //TODO: actually implement listener filters that support starting/stopping
                let msg = StopListenersReply {
                    from: self.name(),
                    stoppedListeners: msg.get(&"listeners".to_string())
                                         .unwrap()
                                         .as_array()
                                         .unwrap_or(&vec!())
                                         .iter()
                                         .map(|listener| listener.as_string().unwrap().to_string())
                                         .collect(),
                };
                stream.write_json_packet(&msg);
                true
            }

            //TODO: implement autocompletion like onAutocomplete in
            //      http://mxr.mozilla.org/mozilla-central/source/toolkit/devtools/server/actors/webconsole.js
            "autocomplete" => {
                let msg = AutocompleteReply {
                    from: self.name(),
                    matches: vec!(),
                    matchProp: "".to_string(),
                };
                stream.write_json_packet(&msg);
                true
            }

            "evaluateJS" => {
                let input = msg.get(&"text".to_string()).unwrap().as_string().unwrap().to_string();
                let (chan, port) = channel();
                self.script_chan.send(DevtoolScriptControlMsg::EvaluateJS(
                    self.pipeline, input.clone(), chan)).unwrap();

                //TODO: extract conversion into protocol module or some other useful place
                let result = match try!(port.recv().map_err(|_| ())) {
                    devtools_traits::EvaluateJSReply::VoidValue => {
                        let mut m = BTreeMap::new();
                        m.insert("type".to_string(), "undefined".to_string().to_json());
                        Json::Object(m)
                    }
                    devtools_traits::EvaluateJSReply::NullValue => {
                        let mut m = BTreeMap::new();
                        m.insert("type".to_string(), "null".to_string().to_json());
                        Json::Object(m)
                    }
                    devtools_traits::EvaluateJSReply::BooleanValue(val) => val.to_json(),
                    devtools_traits::EvaluateJSReply::NumberValue(val) => {
                        if val.is_nan() {
                            let mut m = BTreeMap::new();
                            m.insert("type".to_string(), "NaN".to_string().to_json());
                            Json::Object(m)
                        } else if val.is_infinite() {
                            let mut m = BTreeMap::new();
                            if val < 0. {
                                m.insert("type".to_string(), "-Infinity".to_string().to_json());
                            } else {
                                m.insert("type".to_string(), "Infinity".to_string().to_json());
                            }
                            Json::Object(m)
                        } else if val == Float::neg_zero() {
                            let mut m = BTreeMap::new();
                            m.insert("type".to_string(), "-0".to_string().to_json());
                            Json::Object(m)
                        } else {
                            val.to_json()
                        }
                    }
                    devtools_traits::EvaluateJSReply::StringValue(s) => s.to_json(),
                    devtools_traits::EvaluateJSReply::ActorValue(s) => {
                        //TODO: make initial ActorValue message include these properties.
                        let mut m = BTreeMap::new();
                        m.insert("type".to_string(), "object".to_string().to_json());
                        m.insert("class".to_string(), "???".to_string().to_json());
                        m.insert("actor".to_string(), s.to_json());
                        m.insert("extensible".to_string(), true.to_json());
                        m.insert("frozen".to_string(), false.to_json());
                        m.insert("sealed".to_string(), false.to_json());
                        Json::Object(m)
                    }
                };

                //TODO: catch and return exception values from JS evaluation
                let msg = EvaluateJSReply {
                    from: self.name(),
                    input: input,
                    result: result,
                    timestamp: 0,
                    exception: Json::Object(BTreeMap::new()),
                    exceptionMessage: "".to_string(),
                    helperResult: Json::Object(BTreeMap::new()),
                };
                stream.write_json_packet(&msg);
                true
            }

            _ => false
        })
    }
}
<commit_msg>Import EvaluateJS variants directly.<commit_after>/* This Source Code Form is subject to the terms of the Mozilla Public
 * License, v. 2.0. If a copy of the MPL was not distributed with this
 * file, You can obtain one at http://mozilla.org/MPL/2.0/. */

/// Liberally derived from the [Firefox JS implementation](http://mxr.mozilla.org/mozilla-central/source/toolkit/devtools/server/actors/webconsole.js).
/// Mediates interaction between the remote web console and equivalent functionality (object
/// inspection, JS evaluation, autocompletion) in Servo.

use actor::{Actor, ActorRegistry};
use protocol::JsonPacketStream;

use devtools_traits::EvaluateJSReply::{NullValue, VoidValue, NumberValue};
use devtools_traits::EvaluateJSReply::{StringValue, BooleanValue, ActorValue};
use devtools_traits::DevtoolScriptControlMsg;
use msg::constellation_msg::PipelineId;

use collections::BTreeMap;
use core::cell::RefCell;
use serialize::json::{self, Json, ToJson};
use std::old_io::TcpStream;
use std::num::Float;
use std::sync::mpsc::{channel, Sender};

#[derive(RustcEncodable)]
struct StartedListenersTraits {
    customNetworkRequest: bool,
}

#[derive(RustcEncodable)]
struct StartedListenersReply {
    from: String,
    nativeConsoleAPI: bool,
    startedListeners: Vec<String>,
    traits: StartedListenersTraits,
}

#[derive(RustcEncodable)]
#[allow(dead_code)]
struct ConsoleAPIMessage {
    _type: String, //FIXME: should this be __type__ instead?
}

#[derive(RustcEncodable)]
#[allow(dead_code)]
struct PageErrorMessage {
    _type: String, //FIXME: should this be __type__ instead?
    errorMessage: String,
    sourceName: String,
    lineText: String,
    lineNumber: uint,
    columnNumber: uint,
    category: String,
    timeStamp: uint,
    warning: bool,
    error: bool,
    exception: bool,
    strict: bool,
    private: bool,
}

#[derive(RustcEncodable)]
#[allow(dead_code)]
struct LogMessage {
    _type: String, //FIXME: should this be __type__ instead?
    timeStamp: uint,
    message: String,
}

#[derive(RustcEncodable)]
#[allow(dead_code)]
enum ConsoleMessageType {
    ConsoleAPIType(ConsoleAPIMessage),
    PageErrorType(PageErrorMessage),
    LogMessageType(LogMessage),
}

#[derive(RustcEncodable)]
struct GetCachedMessagesReply {
    from: String,
    messages: Vec<json::Object>,
}

#[derive(RustcEncodable)]
struct StopListenersReply {
    from: String,
    stoppedListeners: Vec<String>,
}

#[derive(RustcEncodable)]
struct AutocompleteReply {
    from: String,
    matches: Vec<String>,
    matchProp: String,
}

#[derive(RustcEncodable)]
struct EvaluateJSReply {
    from: String,
    input: String,
    result: Json,
    timestamp: uint,
    exception: Json,
    exceptionMessage: String,
    helperResult: Json,
}

pub struct ConsoleActor {
    pub name: String,
    pub pipeline: PipelineId,
    pub script_chan: Sender<DevtoolScriptControlMsg>,
    pub streams: RefCell<Vec<TcpStream>>,
}

impl Actor for ConsoleActor {
    fn name(&self) -> String {
        self.name.clone()
    }

    fn handle_message(&self,
                      _registry: &ActorRegistry,
                      msg_type: &String,
                      msg: &json::Object,
                      stream: &mut TcpStream) -> Result<bool, ()> {
        Ok(match msg_type.as_slice() {
            "getCachedMessages" => {
                let types = msg.get(&"messageTypes".to_string()).unwrap().as_array().unwrap();
                let /*mut*/ messages = vec!();
                for msg_type in types.iter() {
                    let msg_type = msg_type.as_string().unwrap();
                    match msg_type.as_slice() {
                        "ConsoleAPI" => {
                            //TODO: figure out all consoleapi properties from FFOX source
                        }

                        "PageError" => {
                            //TODO: make script error reporter pass all reported errors
                            //      to devtools and cache them for returning here.

                            /*let message = PageErrorMessage {
                                _type: msg_type.to_string(),
                                sourceName: "".to_string(),
                                lineText: "".to_string(),
                                lineNumber: 0,
                                columnNumber: 0,
                                category: "".to_string(),
                                warning: false,
                                error: true,
                                exception: false,
                                strict: false,
                                private: false,
                                timeStamp: 0,
                                errorMessage: "page error test".to_string(),
                            };
                            messages.push(json::from_str(json::encode(&message).as_slice()).unwrap().as_object().unwrap().clone());*/
                        }

                        "LogMessage" => {
                            //TODO: figure out when LogMessage is necessary
                            /*let message = LogMessage {
                                _type: msg_type.to_string(),
                                timeStamp: 0,
                                message: "log message test".to_string(),
                            };
                            messages.push(json::from_str(json::encode(&message).as_slice()).unwrap().as_object().unwrap().clone());*/
                        }

                        s => println!("unrecognized message type requested: \"{}\"", s),
                    }
                }

                let msg = GetCachedMessagesReply {
                    from: self.name(),
                    messages: messages,
                };
                stream.write_json_packet(&msg);
                true
            }

            "startListeners" => {
                //TODO: actually implement listener filters that support starting/stopping
                let msg = StartedListenersReply {
                    from: self.name(),
                    nativeConsoleAPI: true,
                    startedListeners:
                        vec!("PageError".to_string(), "ConsoleAPI".to_string()),
                    traits: StartedListenersTraits {
                        customNetworkRequest: true,
                    }
                };
                stream.write_json_packet(&msg);
                true
            }

            "stopListeners" => {
                //TODO: actually implement listener filters that support starting/stopping
                let msg = StopListenersReply {
                    from: self.name(),
                    stoppedListeners: msg.get(&"listeners".to_string())
                                         .unwrap()
                                         .as_array()
                                         .unwrap_or(&vec!())
                                         .iter()
                                         .map(|listener| listener.as_string().unwrap().to_string())
                                         .collect(),
                };
                stream.write_json_packet(&msg);
                true
            }

            //TODO: implement autocompletion like onAutocomplete in
            //      http://mxr.mozilla.org/mozilla-central/source/toolkit/devtools/server/actors/webconsole.js
            "autocomplete" => {
                let msg = AutocompleteReply {
                    from: self.name(),
                    matches: vec!(),
                    matchProp: "".to_string(),
                };
                stream.write_json_packet(&msg);
                true
            }

            "evaluateJS" => {
                let input = msg.get(&"text".to_string()).unwrap().as_string().unwrap().to_string();
                let (chan, port) = channel();
                self.script_chan.send(DevtoolScriptControlMsg::EvaluateJS(
                    self.pipeline, input.clone(), chan)).unwrap();

                //TODO: extract conversion into protocol module or some other useful place
                let result = match try!(port.recv().map_err(|_| ())) {
                    VoidValue => {
                        let mut m = BTreeMap::new();
                        m.insert("type".to_string(), "undefined".to_string().to_json());
                        Json::Object(m)
                    }
                    NullValue => {
                        let mut m = BTreeMap::new();
                        m.insert("type".to_string(), "null".to_string().to_json());
                        Json::Object(m)
                    }
                    BooleanValue(val) => val.to_json(),
                    NumberValue(val) => {
                        if val.is_nan() {
                            let mut m = BTreeMap::new();
                            m.insert("type".to_string(), "NaN".to_string().to_json());
                            Json::Object(m)
                        } else if val.is_infinite() {
                            let mut m = BTreeMap::new();
                            if val < 0. {
                                m.insert("type".to_string(), "-Infinity".to_string().to_json());
                            } else {
                                m.insert("type".to_string(), "Infinity".to_string().to_json());
                            }
                            Json::Object(m)
                        } else if val == Float::neg_zero() {
                            let mut m = BTreeMap::new();
                            m.insert("type".to_string(), "-0".to_string().to_json());
                            Json::Object(m)
                        } else {
                            val.to_json()
                        }
                    }
                    StringValue(s) => s.to_json(),
                    ActorValue(s) => {
                        //TODO: make initial ActorValue message include these properties.
                        let mut m = BTreeMap::new();
                        m.insert("type".to_string(), "object".to_string().to_json());
                        m.insert("class".to_string(), "???".to_string().to_json());
                        m.insert("actor".to_string(), s.to_json());
                        m.insert("extensible".to_string(), true.to_json());
                        m.insert("frozen".to_string(), false.to_json());
                        m.insert("sealed".to_string(), false.to_json());
                        Json::Object(m)
                    }
                };

                //TODO: catch and return exception values from JS evaluation
                let msg = EvaluateJSReply {
                    from: self.name(),
                    input: input,
                    result: result,
                    timestamp: 0,
                    exception: Json::Object(BTreeMap::new()),
                    exceptionMessage: "".to_string(),
                    helperResult: Json::Object(BTreeMap::new()),
                };
                stream.write_json_packet(&msg);
                true
            }

            _ => false
        })
    }
}
<|endoftext|>


<commit_before>// xfail-test

type quad = { a: u64, b: u64, c: u64, d: u64 };
type floats = { a: f64, b: u8, c: f64 };

#[nolink]
native mod rustrt {
    fn debug_abi_1(++q: quad) -> quad;
    fn debug_abi_2(++f: floats) -> floats;
}

fn main() {

    // First check
    let q = { a: 0xaaaa_aaaa_aaaa_aaaa_u64,
             b: 0xbbbb_bbbb_bbbb_bbbb_u64,
             c: 0xcccc_cccc_cccc_cccc_u64,
             d: 0xdddd_dddd_dddd_dddd_u64 };
    let qq = rustrt::debug_abi_1(q);
    #error("a: %x", qq.a as uint);
    #error("b: %x", qq.b as uint);
    #error("c: %x", qq.c as uint);
    #error("d: %x", qq.d as uint);
    assert qq.a == q.c + 1u64;
    assert qq.b == q.d - 1u64;
    assert qq.c == q.a + 1u64;
    assert qq.d == q.b - 1u64;

    // Second check
    let f = { a: 1.234567890e-15_f64,
             b: 0b_1010_1010_u8,
             c: 1.0987654321e-15_f64 };
    let ff = rustrt::debug_abi_2(f);
    #error("a: %f", ff.a as float);
    #error("b: %u", ff.b as uint);
    #error("c: %f", ff.c as float);
    assert ff.a == f.c + 1.0;
    assert ff.b == 0xff_u8;
    assert ff.c == f.a - 1.0;
}<commit_msg>Conditionalize test to only run double/byte/double test on x64 for now.<commit_after>type quad = { a: u64, b: u64, c: u64, d: u64 };
type floats = { a: f64, b: u8, c: f64 };

#[nolink]
native mod rustrt {
    fn debug_abi_1(++q: quad) -> quad;
    fn debug_abi_2(++f: floats) -> floats;
}

fn test1() {
    let q = { a: 0xaaaa_aaaa_aaaa_aaaa_u64,
             b: 0xbbbb_bbbb_bbbb_bbbb_u64,
             c: 0xcccc_cccc_cccc_cccc_u64,
             d: 0xdddd_dddd_dddd_dddd_u64 };
    let qq = rustrt::debug_abi_1(q);
    #error("a: %x", qq.a as uint);
    #error("b: %x", qq.b as uint);
    #error("c: %x", qq.c as uint);
    #error("d: %x", qq.d as uint);
    assert qq.a == q.c + 1u64;
    assert qq.b == q.d - 1u64;
    assert qq.c == q.a + 1u64;
    assert qq.d == q.b - 1u64;
}

#[cfg(target_arch = "x86_64")]
fn test2() {
    let f = { a: 1.234567890e-15_f64,
             b: 0b_1010_1010_u8,
             c: 1.0987654321e-15_f64 };
    let ff = rustrt::debug_abi_2(f);
    #error("a: %f", ff.a as float);
    #error("b: %u", ff.b as uint);
    #error("c: %f", ff.c as float);
    assert ff.a == f.c + 1.0;
    assert ff.b == 0xff_u8;
    assert ff.c == f.a - 1.0;
}

#[cfg(target_arch = "x86")]
fn test2() {
}

fn main() {
    test1();
    test2();
}<|endoftext|>


<commit_before>use std::os::unix::process::CommandExt;
use std::process::Command;

// clap exposes the macro value_t!
use clap::value_t;

// chrono needs this to call Local.datetime_from_str().
use chrono::offset::TimeZone;

const SQLITE3: &'static str = "/usr/bin/sqlite3";
const DB_PATH: &'static str = "/home/kalvin/Documents/personal/expenditures.db";

const PEREXP: &'static str = "perexp";
const ESSEXP: &'static str = "essexp";

const CLAP_AMOUNT: &'static str = "amount";
const CLAP_DESCRIPTION: &'static str = "description";
const CLAP_EXPENDITURE_TYPE: &'static str = "expenditure-type";
const CLAP_TARGET_DATE: &'static str = "target-date";

#[derive(Debug)]
enum ExpenditureType {
    Personal,
    Essential,
}

#[derive(Debug)]
struct SqlOptions {
    // Expenditure type is always required in transacting expenditures.
    expenditure_type: ExpenditureType,

    // Target date is always required in transacting expenditures.
    target_date: chrono::Date<chrono::Local>,

    // Amount and description are required for insertion.
    // They are meaningless for read-only queries.
    amount: Option<f64>,
    description: Option<String>,
}

#[derive(Debug)]
enum SqlCommand {
    Edit,
    Show(SqlOptions),
    Insert(SqlOptions),
}

fn expenditure_type_from_clap(matches: &clap::ArgMatches) -> ExpenditureType {
    let symbolic_type = matches.value_of(CLAP_EXPENDITURE_TYPE).unwrap();

    match symbolic_type {
        "p" => return ExpenditureType::Personal,
        "e" => return ExpenditureType::Essential,
        _ => (),
    };
    panic!(format!(
        "invalid {} ``{}''",
        CLAP_EXPENDITURE_TYPE, symbolic_type
    ));
}

fn expenditure_target_date_from_clap(
    matches: &clap::ArgMatches,
) -> Option<chrono::Date<chrono::Local>> {
    if let Some(cli_target_date) = matches.value_of(CLAP_TARGET_DATE) {
        return Some(
            chrono::Local
                .datetime_from_str(
                    &format!("{} 00:00:00", cli_target_date).to_string(),
                    "%Y-%m-%d %H:%M:%S",
                )
                .unwrap()
                .date(),
        );
    }
    None
}

fn expenditure_amount_from_clap(matches: &clap::ArgMatches) -> f64 {
    clap::value_t!(matches, CLAP_AMOUNT, f64).unwrap_or_else(|e| e.exit())
}

fn expenditure_description_from_clap(matches: &clap::ArgMatches) -> String {
    clap::value_t!(matches, CLAP_DESCRIPTION, String).unwrap_or_else(|e| e.exit())
}

fn build_show_options(matches: &clap::ArgMatches) -> SqlOptions {
    let target_date = match expenditure_target_date_from_clap(matches) {
        Some(date) => date,
        // Aribtrary choice: peeks back 6 months.
        None => (chrono::Local::now() - chrono::Duration::days(183)).date(),
    };

    SqlOptions {
        expenditure_type: expenditure_type_from_clap(matches),
        target_date: target_date,
        amount: None,
        description: None,
    }
}

fn build_insert_options(matches: &clap::ArgMatches) -> SqlOptions {
    let target_date = match expenditure_target_date_from_clap(matches) {
        Some(date) => date,
        None => chrono::Local::now().date(),
    };

    SqlOptions {
        expenditure_type: expenditure_type_from_clap(matches),
        target_date: target_date,
        amount: Some(expenditure_amount_from_clap(matches)),
        description: Some(expenditure_description_from_clap(matches)),
    }
}

fn parse_clap_matches(matches: clap::ArgMatches) -> SqlCommand {
    match matches.subcommand() {
        ("edit", _) => return SqlCommand::Edit,
        ("show", Some(show_matches)) => return SqlCommand::Show(build_show_options(show_matches)),
        ("insert", Some(insert_matches)) => {
            return SqlCommand::Insert(build_insert_options(insert_matches))
        }
        (&_, _) => panic!("no subcommand"),
    }
}

fn parse_command_line() -> SqlCommand {
    let matches = clap::App::new("tangerine-tailleferre")
        .version("0.1.0")
        .author("j39m")
        .about("manipulates expenditures")
        .subcommand(clap::App::new("edit").about("opens sqlite3 directly"))
        .subcommand(
            clap::App::new("show")
                .about("shows expenditures")
                .arg(
                    clap::Arg::with_name(CLAP_TARGET_DATE)
                        .takes_value(true)
                        .short("t"),
                )
                .arg(clap::Arg::with_name(CLAP_EXPENDITURE_TYPE).required(true)),
        )
        .subcommand(
            clap::App::new("insert")
                .about("inserts an expenditure")
                .arg(
                    clap::Arg::with_name(CLAP_AMOUNT)
                        .takes_value(true)
                        .required(true)
                        .short("a"),
                )
                .arg(
                    clap::Arg::with_name(CLAP_DESCRIPTION)
                        .takes_value(true)
                        .required(true)
                        .short("d"),
                )
                .arg(
                    clap::Arg::with_name(CLAP_TARGET_DATE)
                        .takes_value(true)
                        .short("t"),
                )
                .arg(clap::Arg::with_name(CLAP_EXPENDITURE_TYPE).required(true)),
        )
        .get_matches();

    parse_clap_matches(matches)
}

fn expenditure_type_name_from_enum(type_: &ExpenditureType) -> &'static str {
    match type_ {
        ExpenditureType::Personal => return PEREXP,
        ExpenditureType::Essential => return ESSEXP,
    }
}

fn build_show_command(options: SqlOptions) -> String {
    let wordy_display_command = format!(
        r#"select {} from {} where date >= date("{}");"#,
        "*",
        expenditure_type_name_from_enum(&options.expenditure_type),
        options.target_date.format("%Y-%m-%d").to_string()
    );
    let summation_command = format!(
        r#"select {} from {} where date >= date("{}");"#,
        "sum(amount)",
        expenditure_type_name_from_enum(&options.expenditure_type),
        options.target_date.format("%Y-%m-%d").to_string()
    );

    format!("{} {}", wordy_display_command, summation_command)
}

fn build_insert_command(options: SqlOptions) -> String {
    format!(
        r#"insert into {} values(date("{}"), "{}", {});"#,
        expenditure_type_name_from_enum(&options.expenditure_type),
        options.target_date.format("%Y-%m-%d"),
        options.description.unwrap(),
        options.amount.unwrap()
    )
}

fn build_extra_sqlite_arg() -> Option<String> {
    let sql_command = parse_command_line();

    match sql_command {
        SqlCommand::Edit => return None,
        SqlCommand::Show(options) => return Some(build_show_command(options)),
        SqlCommand::Insert(options) => return Some(build_insert_command(options)),
    };
}

fn main() {
    let mut command = Command::new(SQLITE3);
    command.arg(DB_PATH);

    if let Some(extra_arg) = build_extra_sqlite_arg() {
        command.arg(extra_arg);
    }

    eprintln!("{:#?}", command);
    command.exec();
}
<commit_msg>tt: mod from_clap<commit_after>use std::os::unix::process::CommandExt;
use std::process::Command;

// clap exposes the macro value_t!
use clap::value_t;

// chrono needs this to call Local.datetime_from_str().
use chrono::offset::TimeZone;

const SQLITE3: &'static str = "/usr/bin/sqlite3";
const DB_PATH: &'static str = "/home/kalvin/Documents/personal/expenditures.db";

const PEREXP: &'static str = "perexp";
const ESSEXP: &'static str = "essexp";

const CLAP_AMOUNT: &'static str = "amount";
const CLAP_DESCRIPTION: &'static str = "description";
const CLAP_EXPENDITURE_TYPE: &'static str = "expenditure-type";
const CLAP_TARGET_DATE: &'static str = "target-date";

#[derive(Debug)]
pub enum ExpenditureType {
    Personal,
    Essential,
}

#[derive(Debug)]
pub struct SqlOptions {
    // Expenditure type is always required in transacting expenditures.
    expenditure_type: ExpenditureType,

    // Target date is always required in transacting expenditures.
    target_date: chrono::Date<chrono::Local>,

    // Amount and description are required for insertion.
    // They are meaningless for read-only queries.
    amount: Option<f64>,
    description: Option<String>,
}

#[derive(Debug)]
pub enum SqlCommand {
    Edit,
    Show(SqlOptions),
    Insert(SqlOptions),
}

mod from_clap {
    use super::*;

    pub fn expenditure_type(matches: &clap::ArgMatches) -> ExpenditureType {
        let symbolic_type = matches.value_of(CLAP_EXPENDITURE_TYPE).unwrap();

        match symbolic_type {
            "p" => return ExpenditureType::Personal,
            "e" => return ExpenditureType::Essential,
            _ => (),
        };
        panic!(format!(
            "invalid {} ``{}''",
            CLAP_EXPENDITURE_TYPE, symbolic_type
        ));
    }

    pub fn target_date(matches: &clap::ArgMatches) -> Option<chrono::Date<chrono::Local>> {
        if let Some(cli_target_date) = matches.value_of(CLAP_TARGET_DATE) {
            return Some(
                chrono::Local
                    .datetime_from_str(
                        &format!("{} 00:00:00", cli_target_date).to_string(),
                        "%Y-%m-%d %H:%M:%S",
                    )
                    .unwrap()
                    .date(),
            );
        }
        None
    }

    pub fn amount(matches: &clap::ArgMatches) -> f64 {
        clap::value_t!(matches, CLAP_AMOUNT, f64).unwrap_or_else(|e| e.exit())
    }

    pub fn description(matches: &clap::ArgMatches) -> String {
        clap::value_t!(matches, CLAP_DESCRIPTION, String).unwrap_or_else(|e| e.exit())
    }
} // mod from_clap

fn build_show_options(matches: &clap::ArgMatches) -> SqlOptions {
    let target_date = match from_clap::target_date(matches) {
        Some(date) => date,
        // Aribtrary choice: peeks back 6 months.
        None => (chrono::Local::now() - chrono::Duration::days(183)).date(),
    };

    SqlOptions {
        expenditure_type: from_clap::expenditure_type(matches),
        target_date: target_date,
        amount: None,
        description: None,
    }
}

fn build_insert_options(matches: &clap::ArgMatches) -> SqlOptions {
    let target_date = match from_clap::target_date(matches) {
        Some(date) => date,
        None => chrono::Local::now().date(),
    };

    SqlOptions {
        expenditure_type: from_clap::expenditure_type(matches),
        target_date: target_date,
        amount: Some(from_clap::amount(matches)),
        description: Some(from_clap::description(matches)),
    }
}

fn parse_clap_matches(matches: clap::ArgMatches) -> SqlCommand {
    match matches.subcommand() {
        ("edit", _) => return SqlCommand::Edit,
        ("show", Some(show_matches)) => return SqlCommand::Show(build_show_options(show_matches)),
        ("insert", Some(insert_matches)) => {
            return SqlCommand::Insert(build_insert_options(insert_matches))
        }
        (&_, _) => panic!("no subcommand"),
    }
}

fn parse_command_line() -> SqlCommand {
    let matches = clap::App::new("tangerine-tailleferre")
        .version("0.1.0")
        .author("j39m")
        .about("manipulates expenditures")
        .subcommand(clap::App::new("edit").about("opens sqlite3 directly"))
        .subcommand(
            clap::App::new("show")
                .about("shows expenditures")
                .arg(
                    clap::Arg::with_name(CLAP_TARGET_DATE)
                        .takes_value(true)
                        .short("t"),
                )
                .arg(clap::Arg::with_name(CLAP_EXPENDITURE_TYPE).required(true)),
        )
        .subcommand(
            clap::App::new("insert")
                .about("inserts an expenditure")
                .arg(
                    clap::Arg::with_name(CLAP_AMOUNT)
                        .takes_value(true)
                        .required(true)
                        .short("a"),
                )
                .arg(
                    clap::Arg::with_name(CLAP_DESCRIPTION)
                        .takes_value(true)
                        .required(true)
                        .short("d"),
                )
                .arg(
                    clap::Arg::with_name(CLAP_TARGET_DATE)
                        .takes_value(true)
                        .short("t"),
                )
                .arg(clap::Arg::with_name(CLAP_EXPENDITURE_TYPE).required(true)),
        )
        .get_matches();

    parse_clap_matches(matches)
}

fn expenditure_type_name_from_enum(type_: &ExpenditureType) -> &'static str {
    match type_ {
        ExpenditureType::Personal => return PEREXP,
        ExpenditureType::Essential => return ESSEXP,
    }
}

fn build_show_command(options: SqlOptions) -> String {
    let wordy_display_command = format!(
        r#"select {} from {} where date >= date("{}");"#,
        "*",
        expenditure_type_name_from_enum(&options.expenditure_type),
        options.target_date.format("%Y-%m-%d").to_string()
    );
    let summation_command = format!(
        r#"select {} from {} where date >= date("{}");"#,
        "sum(amount)",
        expenditure_type_name_from_enum(&options.expenditure_type),
        options.target_date.format("%Y-%m-%d").to_string()
    );

    format!("{} {}", wordy_display_command, summation_command)
}

fn build_insert_command(options: SqlOptions) -> String {
    format!(
        r#"insert into {} values(date("{}"), "{}", {});"#,
        expenditure_type_name_from_enum(&options.expenditure_type),
        options.target_date.format("%Y-%m-%d"),
        options.description.unwrap(),
        options.amount.unwrap()
    )
}

fn build_extra_sqlite_arg() -> Option<String> {
    let sql_command = parse_command_line();

    match sql_command {
        SqlCommand::Edit => return None,
        SqlCommand::Show(options) => return Some(build_show_command(options)),
        SqlCommand::Insert(options) => return Some(build_insert_command(options)),
    };
}

fn main() {
    let mut command = Command::new(SQLITE3);
    command.arg(DB_PATH);

    if let Some(extra_arg) = build_extra_sqlite_arg() {
        command.arg(extra_arg);
    }

    eprintln!("{:#?}", command);
    command.exec();
}
<|endoftext|>


<commit_before>use super::validator_record::ValidatorRecord;
use super::crosslink_record::CrosslinkRecord;
use super::shard_and_committee::ShardAndCommittee;
use super::Hash256;

pub struct CrystallizedState {
    pub validator_set_change_slot: u64,
    pub validators: Vec<ValidatorRecord>,
    pub crosslinks: Vec<CrosslinkRecord>,
    pub last_state_recalculation_slot: u64,
    pub last_finalized_slot: u64,
    pub last_justified_slot: u64,
    pub justified_streak: u64,
    pub shard_and_committee_for_slots: Vec<Vec<ShardAndCommittee>>,
    pub deposits_penalized_in_period: u32,
    pub validator_set_delta_hash_chain: Hash256,
    pub pre_fork_version: u32,
    pub post_fork_version: u32,
    pub fork_slot_number: u32,
}
<commit_msg>Fix error in CrystallizedState definition<commit_after>use super::validator_record::ValidatorRecord;
use super::crosslink_record::CrosslinkRecord;
use super::shard_and_committee::ShardAndCommittee;
use super::Hash256;

pub struct CrystallizedState {
    pub validator_set_change_slot: u64,
    pub validators: Vec<ValidatorRecord>,
    pub crosslinks: Vec<CrosslinkRecord>,
    pub last_state_recalculation_slot: u64,
    pub last_finalized_slot: u64,
    pub last_justified_slot: u64,
    pub justified_streak: u64,
    pub shard_and_committee_for_slots: Vec<Vec<ShardAndCommittee>>,
    pub deposits_penalized_in_period: Vec<u32>,
    pub validator_set_delta_hash_chain: Hash256,
    pub pre_fork_version: u32,
    pub post_fork_version: u32,
    pub fork_slot_number: u32,
}
<|endoftext|>


<commit_before>//! This module deals with configuration data including the management of the list of secrets

#![allow(clippy::filter_map)]
extern crate rand;
extern crate serde;
extern crate serde_yaml;
use crate::image::CONF_LINE_IDENTIFIER__CONTROL;
use crate::image::CONF_LINE_IDENTIFIER__IMAGE;
use rand::Rng;
use serde::Deserialize;
use thiserror::Error;

/// Tags comment lines in the configuration file.
pub const CONF_LINE_IDENTIFIER__COMMENT: char = '#';

/// Optionally tags secret strings in config-file. Can be omitted.
pub const CONF_LINE_IDENTIFIER__WORD: char = '-';

/// A tag to enclose parts of the secret to be visible from the start, e.g.
/// "guess_-me_" will be displayed in the game as "_ _ _ _ _ - m e"
pub const CONF_LINE_SECRET_MODIFIER__VISIBLE: char = '_';

/// A tag to insert a linebreak when the secret is displayed.
pub const CONF_LINE_SECRET_MODIFIER__LINEBREAK1: char = '\n';
pub const CONF_LINE_SECRET_MODIFIER__LINEBREAK2: char = '|';

// Custom error type used expressing potential syntax errors when parsing the configuration file.
#[derive(Error, Debug)]
pub enum ConfigParseError {
    #[error(
        "Syntax error in line {line_number:?}: `{line}`\n\n\
    The game modifier must be one of the following:\n\
        :traditional-rewarding\n\
        :success-rewarding\n\n\
        Edit config file and start again.\n"
    )]
    GameModifier { line_number: usize, line: String },
    #[error(
        "Syntax error in line {line_number:?}: `{line}`\n\n\
    The first character of every non-empty line has to be one of the following:\n\
        any letter or digit (secret string),\n\
        '#' (comment line),\n\
        '-' (secret string),\n\
        '|' (ASCII-Art image) or\n\
        ':' (game modifier).\n\n\
    Edit config file and start again.\n"
    )]
    LineIdentifier { line_number: usize, line: String },
    #[error["No image data found."]]
    NoImageData,
    #[error["A config file must have a least one secret string, which is\n\
    a non-empty line starting with a letter, digit, '_' or '-'."]]
    NoSecretString,
    #[error["Could not parse the proprietary format, because this is\n\
    meant to be in (erroneous) YAML format."]]
    NotInProprietaryFormat,
    #[error("Invalid format:\n{0}")]
    NotInYamlFormat(#[from] serde_yaml::Error),
    #[error["Frist line must be: `secrets:` (no spaces allowed before)."]]
    YamlSecretsLineMissing,
}

/// We need this because `serde_yaml::Error` does not implement `PartialEq`.
/// We compare only types.
impl PartialEq for ConfigParseError {
    fn eq(&self, other: &Self) -> bool {
        std::mem::discriminant(self) == std::mem::discriminant(other)
            && (self.to_string() == other.to_string())
    }
}

/// A dictionary holding all secret sentences from among whom one is chosen randomly at the
/// beginning of the game.
#[derive(Debug, PartialEq, Deserialize)]
pub struct Dict {
    secrets: Vec<String>,
}

impl Dict {
    /// First try ot parse YAML, if it fails try the depreciated proprietary format and populate the dictionary
    /// with secrets.
    pub fn from(lines: &str) -> Result<Self, ConfigParseError> {
        // If both return an error, return the first one here.
        Self::from_yaml(&lines).or_else(|e| Self::from_proprietary(&lines).or(Err(e)))
    }

    /// Parse configuration file as toml data.
    pub fn from_yaml(lines: &str) -> Result<Self, ConfigParseError> {
        // Trim BOM
        let lines = lines.trim_start_matches('\u{feff}');

        for l in lines
            .lines()
            .filter(|s| !s.trim_start().starts_with('#'))
            .filter(|s| s.trim() != "")
        {
            if l.trim_end() == "secrets:" {
                break;
            } else {
                return Err(ConfigParseError::YamlSecretsLineMissing);
            }
        }

        let dict: Dict = serde_yaml::from_str(&lines)?;

        Ok(dict)
    }

    /// Parse the old configuration data format.
    fn from_proprietary(lines: &str) -> Result<Self, ConfigParseError> {
        if lines
            .lines()
            .any(|s| s.trim().starts_with("secrets:") || s.trim().starts_with("image:"))
        {
            return Err(ConfigParseError::NotInProprietaryFormat {});
        };

        let mut file_syntax_test2: Result<(), ConfigParseError> = Ok(());

        let wordlist =
          // remove Unicode BOM if present (\u{feff} has in UTF8 3 bytes).
          if lines.starts_with('\u{feff}') { &lines[3..] } else { &lines[..] }
            // interpret identifier line
            .lines()
            .enumerate()
            .filter(|&(_,l)|!( l.trim().is_empty() ||
                          l.starts_with(CONF_LINE_IDENTIFIER__COMMENT) ||
                          l.starts_with(CONF_LINE_IDENTIFIER__CONTROL) ||
                          l.starts_with(CONF_LINE_IDENTIFIER__IMAGE)
                        )
            )
            .map(|(n,l)| if l.starts_with(CONF_LINE_IDENTIFIER__WORD) {
                             l[1..].trim().to_string()
                        } else {
                             // Lines starting alphanumerically are secret strings also.
                             // We can safely unwrap here since all empty lines had been filtered.
                             let c = l.chars().next().unwrap();
                             if c.is_alphanumeric() || c == CONF_LINE_SECRET_MODIFIER__VISIBLE {
                                l.trim().to_string()
                             } else {
                                 // we only save the first error
                                 if file_syntax_test2.is_ok() {
                                    file_syntax_test2 = Err(ConfigParseError::LineIdentifier {
                                        line_number: n+1, line: l.to_string() });
                                 };
                                // This will never be used but we have to return something
                                "".to_string()
                             }
                        }
            )
            .collect::<Vec<String>>();

        file_syntax_test2?;

        if wordlist.is_empty() {
            return Err(ConfigParseError::NoSecretString {});
        }

        Ok(Dict { secrets: wordlist })
    }

    /// Chooses randomly one secret from the dictionary and removes the secret from list
    pub fn get_random_secret(&mut self) -> Option<String> {
        match self.secrets.len() {
            0 => None,
            1 => Some(self.secrets.swap_remove(0)),
            _ => {
                let mut rng = rand::thread_rng();
                let i = rng.gen_range(0, &self.secrets.len() - 1);
                Some(self.secrets.swap_remove(i))
            }
        }
    }

    /// Is there exactly one secret left?
    pub fn is_empty(&self) -> bool {
        self.secrets.is_empty()
    }

    /// Add a secret to the list.
    pub fn add(&mut self, secret: String) {
        self.secrets.push(secret);
    }
}

// ***********************

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

    /// parse all 3 data types in configuration file format
    #[test]
    fn test_new_proprietary() {
        let config: &str = "
#  comment

guess me
hang_man_
_good l_uck
:traditional-rewarding
";
        let dict = Dict::from(&config).unwrap();

        let expected = Dict {
            secrets: vec![
                "guess me".to_string(),
                "hang_man_".to_string(),
                "_good l_uck".to_string(),
            ],
        };
        assert_eq!(dict, expected);

        let config: &str = "guess me";
        let dict = Dict::from(&config);
        let expected = Ok(Dict {
            secrets: vec!["guess me".to_string()],
            // this is default
        });
        assert_eq!(dict, expected);

        // indent of comments is not allowed
        let config = "\n\n\n   # comment";
        let dict = Dict::from_proprietary(&config);
        let expected = Err(ConfigParseError::LineIdentifier {
            line_number: 4,
            line: "   # comment".to_string(),
        });
        assert_eq!(dict, expected);

        // configuration must define at least one secret
        let config = "# nothing but comment";
        let dict = Dict::from_proprietary(&config);
        let expected = Err(ConfigParseError::NoSecretString {});
        assert_eq!(dict, expected);

        let config = "one secret\n\n :traditional-rewarding";
        let dict = Dict::from_proprietary(&config);
        let expected = Err(ConfigParseError::LineIdentifier {
            line_number: 3,
            line: " :traditional-rewarding".to_string(),
        });
        assert_eq!(dict, expected);
    }

    #[test]
    fn test_new_toml() {
        let config = "# comment\nsecrets:\n  - guess me\n";
        let dict = Dict::from_yaml(&config);
        let expected = Ok(Dict {
            secrets: vec!["guess me".to_string()],
        });
        assert_eq!(dict, expected);

        let config = "# comment\nsecrets:\n- guess me\n";
        let dict = Dict::from_yaml(&config);
        let expected = Ok(Dict {
            secrets: vec!["guess me".to_string()],
        });
        assert_eq!(dict, expected);

        let config = "# comment\nsecrets:\n- 222\n";
        let dict = Dict::from_yaml(&config);
        let expected = Ok(Dict {
            secrets: vec!["222".to_string()],
        });
        assert_eq!(dict, expected);

        let config = "sxxxecrets:";
        let dict = Dict::from_yaml(&config).unwrap_err();
        assert!(matches!(dict, ConfigParseError::YamlSecretsLineMissing));

        let config = "  - guess me\nsecrets:\n";
        let dict = Dict::from_yaml(&config).unwrap_err();
        assert!(matches!(dict, ConfigParseError::YamlSecretsLineMissing));

        let config = "# comment\nsecrets:\n   guess me\n";
        let dict = Dict::from_yaml(&config).unwrap_err();
        assert!(matches!(dict, ConfigParseError::NotInYamlFormat(_)));
    }
}
<commit_msg>relax backwards compatibility mode<commit_after>//! This module deals with configuration data including the management of the list of secrets

#![allow(clippy::filter_map)]
extern crate rand;
extern crate serde;
extern crate serde_yaml;
use crate::image::CONF_LINE_IDENTIFIER__CONTROL;
use crate::image::CONF_LINE_IDENTIFIER__IMAGE;
use rand::Rng;
use serde::Deserialize;
use thiserror::Error;

/// Tags comment lines in the configuration file.
pub const CONF_LINE_IDENTIFIER__COMMENT: char = '#';

/// Optionally tags secret strings in config-file. Can be omitted.
pub const CONF_LINE_IDENTIFIER__WORD: char = '-';

/// A tag to enclose parts of the secret to be visible from the start, e.g.
/// "guess_-me_" will be displayed in the game as "_ _ _ _ _ - m e"
pub const CONF_LINE_SECRET_MODIFIER__VISIBLE: char = '_';

/// A tag to insert a linebreak when the secret is displayed.
pub const CONF_LINE_SECRET_MODIFIER__LINEBREAK1: char = '\n';
pub const CONF_LINE_SECRET_MODIFIER__LINEBREAK2: char = '|';

// Custom error type used expressing potential syntax errors when parsing the configuration file.
#[derive(Error, Debug)]
pub enum ConfigParseError {
    #[error(
        "Syntax error in line {line_number:?}: `{line}`\n\n\
    The game modifier must be one of the following:\n\
        :traditional-rewarding\n\
        :success-rewarding\n\n\
        Edit config file and start again.\n"
    )]
    GameModifier { line_number: usize, line: String },
    #[error(
        "Syntax error in line {line_number:?}: `{line}`\n\n\
    The first character of every non-empty line has to be one of the following:\n\
        any letter or digit (secret string),\n\
        '#' (comment line),\n\
        '-' (secret string),\n\
        '|' (ASCII-Art image) or\n\
        ':' (game modifier).\n\n\
    Edit config file and start again.\n"
    )]
    LineIdentifier { line_number: usize, line: String },
    #[error["No image data found."]]
    NoImageData,
    #[error["A config file must have a least one secret string, which is\n\
    a non-empty line starting with a letter, digit, '_' or '-'."]]
    NoSecretString,
    #[error["Could not parse the proprietary format, because this is\n\
    meant to be in (erroneous) YAML format."]]
    NotInProprietaryFormat,
    #[error("Invalid format:\n\
             Hint: If a secret contains a colon (:), like in\n\
             -  animal: giraffe\n\
             you must enclose it with double quotes (\"):\n\
             -  \"animal: giraffe\"\n\n\
             {0}")]
    NotInYamlFormat(#[from] serde_yaml::Error),
    #[error["First line must be: `secrets:` (no spaces allowed before)."]]
    YamlSecretsLineMissing,
}

/// We need this because `serde_yaml::Error` does not implement `PartialEq`.
/// We compare only types.
impl PartialEq for ConfigParseError {
    fn eq(&self, other: &Self) -> bool {
        std::mem::discriminant(self) == std::mem::discriminant(other)
            && (self.to_string() == other.to_string())
    }
}

/// A dictionary holding all secret sentences from among whom one is chosen randomly at the
/// beginning of the game.
#[derive(Debug, PartialEq, Deserialize)]
pub struct Dict {
    secrets: Vec<String>,
}

impl Dict {
    /// First try ot parse YAML, if it fails try the depreciated proprietary format and populate the dictionary
    /// with secrets.
    pub fn from(lines: &str) -> Result<Self, ConfigParseError> {
        // If both return an error, return the first one here.
        Self::from_yaml(&lines).or_else(|e| Self::from_proprietary(&lines).or(Err(e)))
    }

    /// Parse configuration file as toml data.
    pub fn from_yaml(lines: &str) -> Result<Self, ConfigParseError> {
        // Trim BOM
        let lines = lines.trim_start_matches('\u{feff}');

        for l in lines
            .lines()
            .filter(|s| !s.trim_start().starts_with('#'))
            .filter(|s| s.trim() != "")
        {
            if l.trim_end() == "secrets:" {
                break;
            } else {
                return Err(ConfigParseError::YamlSecretsLineMissing);
            }
        }

        let dict: Dict = serde_yaml::from_str(&lines)?;

        Ok(dict)
    }

    /// Parse the old configuration data format.
    fn from_proprietary(lines: &str) -> Result<Self, ConfigParseError> {
        if lines
            .lines()
            .any(|s| s.trim().starts_with("secrets:") || s.trim().starts_with("image:"))
        {
            return Err(ConfigParseError::NotInProprietaryFormat {});
        };

        let mut file_syntax_test2: Result<(), ConfigParseError> = Ok(());

        let wordlist =
          // remove Unicode BOM if present (\u{feff} has in UTF8 3 bytes).
          if lines.starts_with('\u{feff}') { &lines[3..] } else { &lines[..] }
            // interpret identifier line
            .lines()
            .enumerate()
            .filter(|&(_,l)|!( l.trim().is_empty() ||
                          l.starts_with(CONF_LINE_IDENTIFIER__COMMENT) ||
                          l.starts_with(CONF_LINE_IDENTIFIER__CONTROL) ||
                          l.starts_with(CONF_LINE_IDENTIFIER__IMAGE)
                        )
            )
            .map(|(n,l)| if l.starts_with(CONF_LINE_IDENTIFIER__WORD) {
                             l[1..].trim().to_string()
                        } else {
                             // Lines starting alphanumerically are secret strings also.
                             // We can safely unwrap here since all empty lines had been filtered.
                             let c = l.trim().chars().next().unwrap();
                             if c.is_alphanumeric() || c == CONF_LINE_SECRET_MODIFIER__VISIBLE {
                                l.trim().to_string()
                             } else {
                                 // we only save the first error
                                 if file_syntax_test2.is_ok() {
                                    file_syntax_test2 = Err(ConfigParseError::LineIdentifier {
                                        line_number: n+1, line: l.to_string() });
                                 };
                                // This will never be used but we have to return something
                                "".to_string()
                             }
                        }
            )
            .collect::<Vec<String>>();

        file_syntax_test2?;

        if wordlist.is_empty() {
            return Err(ConfigParseError::NoSecretString {});
        }

        Ok(Dict { secrets: wordlist })
    }

    /// Chooses randomly one secret from the dictionary and removes the secret from list
    pub fn get_random_secret(&mut self) -> Option<String> {
        match self.secrets.len() {
            0 => None,
            1 => Some(self.secrets.swap_remove(0)),
            _ => {
                let mut rng = rand::thread_rng();
                let i = rng.gen_range(0, &self.secrets.len() - 1);
                Some(self.secrets.swap_remove(i))
            }
        }
    }

    /// Is there exactly one secret left?
    pub fn is_empty(&self) -> bool {
        self.secrets.is_empty()
    }

    /// Add a secret to the list.
    pub fn add(&mut self, secret: String) {
        self.secrets.push(secret);
    }
}

// ***********************

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

    /// parse all 3 data types in configuration file format
    #[test]
    fn test_new_proprietary() {
        let config: &str = "
#  comment

guess me
hang_man_
_good l_uck
:traditional-rewarding
";
        let dict = Dict::from(&config).unwrap();

        let expected = Dict {
            secrets: vec![
                "guess me".to_string(),
                "hang_man_".to_string(),
                "_good l_uck".to_string(),
            ],
        };
        assert_eq!(dict, expected);

        let config: &str = "guess me";
        let dict = Dict::from(&config);
        let expected = Ok(Dict {
            secrets: vec!["guess me".to_string()],
            // this is default
        });
        assert_eq!(dict, expected);

        // indent of comments is not allowed
        let config = "\n\n\n   # comment";
        let dict = Dict::from_proprietary(&config);
        let expected = Err(ConfigParseError::LineIdentifier {
            line_number: 4,
            line: "   # comment".to_string(),
        });
        assert_eq!(dict, expected);

        // configuration must define at least one secret
        let config = "# nothing but comment";
        let dict = Dict::from_proprietary(&config);
        let expected = Err(ConfigParseError::NoSecretString {});
        assert_eq!(dict, expected);

        let config = "one secret\n\n :traditional-rewarding";
        let dict = Dict::from_proprietary(&config);
        let expected = Err(ConfigParseError::LineIdentifier {
            line_number: 3,
            line: " :traditional-rewarding".to_string(),
        });
        assert_eq!(dict, expected);
    }

    #[test]
    fn test_new_toml() {
        let config = "# comment\nsecrets:\n  - guess me\n";
        let dict = Dict::from_yaml(&config);
        let expected = Ok(Dict {
            secrets: vec!["guess me".to_string()],
        });
        assert_eq!(dict, expected);

        let config = "# comment\nsecrets:\n- guess me\n";
        let dict = Dict::from_yaml(&config);
        let expected = Ok(Dict {
            secrets: vec!["guess me".to_string()],
        });
        assert_eq!(dict, expected);

        let config = "# comment\nsecrets:\n- 222\n";
        let dict = Dict::from_yaml(&config);
        let expected = Ok(Dict {
            secrets: vec!["222".to_string()],
        });
        assert_eq!(dict, expected);

        let config = "sxxxecrets:";
        let dict = Dict::from_yaml(&config).unwrap_err();
        assert!(matches!(dict, ConfigParseError::YamlSecretsLineMissing));

        let config = "  - guess me\nsecrets:\n";
        let dict = Dict::from_yaml(&config).unwrap_err();
        assert!(matches!(dict, ConfigParseError::YamlSecretsLineMissing));

        let config = "# comment\nsecrets:\n   guess me\n";
        let dict = Dict::from_yaml(&config).unwrap_err();
        assert!(matches!(dict, ConfigParseError::NotInYamlFormat(_)));
    }
}
<|endoftext|>


<commit_before>use std::sync::{Arc, Mutex};

use wasm_bindgen::prelude::*;
use wasm_bindgen::JsCast;
use web_sys::{
    self, Document, Element, HtmlCanvasElement, KeyboardEvent, MouseEvent, Node, UiEvent, Window,
};

use crate::input::prelude::{InputEvent, MouseButton};
use crate::window::prelude::{Event, WindowEvent, WindowParams};

use crate::math::prelude::Vector2;
use crate::errors::*;

use super::{types, Visitor};

#[allow(dead_code)]
pub struct WebVisitor {
    window: Window,
    document: Document,
    canvas: HtmlCanvasElement,
    events: Arc<Mutex<Vec<Event>>>,
    on_mouse_move: Closure<FnMut(MouseEvent)>,
    on_mouse_down: Closure<FnMut(MouseEvent)>,
    on_mouse_up: Closure<FnMut(MouseEvent)>,
    on_key_down: Closure<FnMut(KeyboardEvent)>,
    on_key_up: Closure<FnMut(KeyboardEvent)>,
    on_resize: Closure<FnMut(UiEvent)>,
    on_focus: Closure<FnMut(UiEvent)>,
    on_lost_focus: Closure<FnMut(UiEvent)>,
}

impl WebVisitor {
    pub fn new(params: WindowParams) -> Result<Self> {
        let window = web_sys::window().expect("no global `window` exists");
        let document = window.document().expect("should have a document on window");
        let body = document.body().expect("document should have a body");

        let header = document.create_element("p").unwrap();
        header.set_inner_html(&params.title);
        AsRef::<Node>::as_ref(&body)
            .append_child(header.as_ref())
            .unwrap();

        let canvas = document
            .create_element("canvas")
            .unwrap()
            .dyn_into::<HtmlCanvasElement>()
            .map_err(|_| ())
            .unwrap();

        canvas
            .unchecked_ref::<Element>()
            .set_attribute("id", "canvas")
            .unwrap();

        AsRef::<Node>::as_ref(&body)
            .append_child(canvas.as_ref())
            .unwrap();

        let events = Arc::new(Mutex::new(Vec::new()));

        let on_mouse_down = {
            let clone = events.clone();
            Closure::wrap(Box::new(move |v: MouseEvent| {
                let button = match v.button() {
                    0 => MouseButton::Left,
                    1 => MouseButton::Middle,
                    2 => MouseButton::Right,
                    i => MouseButton::Other(i as u8),
                };

                let evt = Event::InputDevice(InputEvent::MousePressed { button });
                clone.lock().unwrap().push(evt);
            }) as Box<FnMut(_)>)
        };

        canvas
            .add_event_listener_with_callback("mousedown", on_mouse_down.as_ref().unchecked_ref())
            .unwrap();

        let on_mouse_up = {
            let clone = events.clone();
            Closure::wrap(Box::new(move |v: MouseEvent| {
                let button = match v.button() {
                    0 => MouseButton::Left,
                    1 => MouseButton::Middle,
                    2 => MouseButton::Right,
                    i => MouseButton::Other(i as u8),
                };

                let evt = Event::InputDevice(InputEvent::MouseReleased { button });
                clone.lock().unwrap().push(evt);
            }) as Box<FnMut(_)>)
        };

        canvas
            .add_event_listener_with_callback("mouseup", on_mouse_up.as_ref().unchecked_ref())
            .unwrap();

        let on_mouse_move = {
            let clone = events.clone();
            let window = window.clone();
            let canvas = canvas.clone();
            Closure::wrap(Box::new(move |v: MouseEvent| {
                let dpr = window.device_pixel_ratio() as f32;
                let height = canvas.height() as f32 / dpr;
                let rect = canvas.get_bounding_client_rect();

                let position = (
                    v.layer_x() as f32 - rect.x() as f32,
                    height - v.layer_y() as f32 + rect.y() as f32,
                );

                let evt = Event::InputDevice(InputEvent::MouseMoved { position });
                clone.lock().unwrap().push(evt);
            }) as Box<FnMut(_)>)
        };

        canvas
            .add_event_listener_with_callback("mousemove", on_mouse_move.as_ref().unchecked_ref())
            .unwrap();

        let on_key_down = {
            let clone = events.clone();
            Closure::wrap(Box::new(move |v: KeyboardEvent| {
                if let Some(key) = types::from_virtual_key_code(&v.key()) {
                    let evt = Event::InputDevice(InputEvent::KeyboardPressed { key });
                    clone.lock().unwrap().push(evt);
                }
            }) as Box<FnMut(_)>)
        };

        canvas
            .add_event_listener_with_callback("keydown", on_key_down.as_ref().unchecked_ref())
            .unwrap();

        let on_key_up = {
            let clone = events.clone();
            Closure::wrap(Box::new(move |v: KeyboardEvent| {
                if let Some(key) = types::from_virtual_key_code(&v.key()) {
                    let evt = Event::InputDevice(InputEvent::KeyboardReleased { key });
                    clone.lock().unwrap().push(evt);
                }
            }) as Box<FnMut(_)>)
        };

        canvas
            .add_event_listener_with_callback("keyup", on_key_up.as_ref().unchecked_ref())
            .unwrap();

        let on_focus = {
            let clone = events.clone();
            Closure::wrap(Box::new(move |_: UiEvent| {
                let evt = Event::Window(WindowEvent::GainFocus);
                clone.lock().unwrap().push(evt);
            }) as Box<FnMut(_)>)
        };

        canvas
            .add_event_listener_with_callback("focus", on_focus.as_ref().unchecked_ref())
            .unwrap();

        let on_lost_focus = {
            let clone = events.clone();
            Closure::wrap(Box::new(move |_: UiEvent| {
                let evt = Event::Window(WindowEvent::GainFocus);
                clone.lock().unwrap().push(evt);
            }) as Box<FnMut(_)>)
        };

        canvas
            .add_event_listener_with_callback("blur", on_lost_focus.as_ref().unchecked_ref())
            .unwrap();

        let on_resize = {
            let clone = events.clone();
            let canvas = canvas.clone();
            Closure::wrap(Box::new(move |_: UiEvent| {
                let evt = Event::Window(WindowEvent::Resized(canvas.width(), canvas.height()));
                clone.lock().unwrap().push(evt);
            }) as Box<FnMut(_)>)
        };

        canvas
            .add_event_listener_with_callback("resize", on_resize.as_ref().unchecked_ref())
            .unwrap();

        let visitor = WebVisitor {
            window: window,
            document: document,
            canvas: canvas,
            events: events,
            on_mouse_down: on_mouse_down,
            on_mouse_up: on_mouse_up,
            on_mouse_move: on_mouse_move,
            on_key_down: on_key_down,
            on_key_up: on_key_up,
            on_focus: on_focus,
            on_lost_focus: on_lost_focus,
            on_resize: on_resize,
        };

        let dpr = visitor.device_pixel_ratio();
        let dims = Vector2::new(
            (params.size.x as f32 * dpr) as u32,
            (params.size.y as f32 * dpr) as u32,
        );

        visitor.resize(dims);
        Ok(visitor)
    }
}

impl Visitor for WebVisitor {
    #[inline]
    fn show(&self) {
        warn!("It does not make sense to `show` window in browser.")
    }

    #[inline]
    fn hide(&self) {
        warn!("It does not make sense to `hide` window in browser.")
    }

    #[inline]
    fn position(&self) -> Vector2<i32> {
        (0, 0).into()
    }

    #[inline]
    fn dimensions(&self) -> Vector2<u32> {
        let dpr = self.window.device_pixel_ratio() as f32;
        Vector2::new(
            (self.canvas.width() as f32 / dpr) as u32,
            (self.canvas.height() as f32 / dpr) as u32,
        )
    }

    #[inline]
    fn device_pixel_ratio(&self) -> f32 {
        self.window.device_pixel_ratio() as f32
    }

    #[inline]
    fn resize(&self, dims: Vector2<u32>) {
        self.canvas.set_width(dims.x);
        self.canvas.set_height(dims.y);

        let dpr = self.device_pixel_ratio();
        self.canvas
            .unchecked_ref::<Element>()
            .set_attribute(
                "style",
                &format!(
                    "width: {}px; height: {}px;",
                    (dims.x as f32 / dpr) as u32,
                    (dims.y as f32 / dpr) as u32
                ),
            ).unwrap();
    }

    #[inline]
    fn poll_events(&mut self, v: &mut Vec<Event>) {
        let mut events = self.events.lock().unwrap();
        v.extend(events.drain(..));
    }

    #[inline]
    fn is_current(&self) -> bool {
        self.document.has_focus().unwrap_or(false)
    }

    #[inline]
    fn make_current(&self) -> Result<()> {
        warn!("You can not `make_current` in browser.");
        Ok(())
    }

    #[inline]
    fn swap_buffers(&self) -> Result<()> {
        Ok(())
    }
}
<commit_msg>Fix mouse position detection<commit_after>use std::sync::{Arc, Mutex};

use wasm_bindgen::prelude::*;
use wasm_bindgen::JsCast;
use web_sys::{
    self, Document, Element, HtmlCanvasElement, KeyboardEvent, MouseEvent, Node, UiEvent, Window,
};

use crate::input::prelude::{InputEvent, MouseButton};
use crate::window::prelude::{Event, WindowEvent, WindowParams};

use crate::math::prelude::Vector2;
use crate::errors::*;

use super::{types, Visitor};

#[allow(dead_code)]
pub struct WebVisitor {
    window: Window,
    document: Document,
    canvas: HtmlCanvasElement,
    events: Arc<Mutex<Vec<Event>>>,
    on_mouse_move: Closure<FnMut(MouseEvent)>,
    on_mouse_down: Closure<FnMut(MouseEvent)>,
    on_mouse_up: Closure<FnMut(MouseEvent)>,
    on_key_down: Closure<FnMut(KeyboardEvent)>,
    on_key_up: Closure<FnMut(KeyboardEvent)>,
    on_resize: Closure<FnMut(UiEvent)>,
    on_focus: Closure<FnMut(UiEvent)>,
    on_lost_focus: Closure<FnMut(UiEvent)>,
}

impl WebVisitor {
    pub fn new(params: WindowParams) -> Result<Self> {
        let window = web_sys::window().expect("no global `window` exists");
        let document = window.document().expect("should have a document on window");
        let body = document.body().expect("document should have a body");

        let header = document.create_element("p").unwrap();
        header.set_inner_html(&params.title);
        AsRef::<Node>::as_ref(&body)
            .append_child(header.as_ref())
            .unwrap();

        let canvas = document
            .create_element("canvas")
            .unwrap()
            .dyn_into::<HtmlCanvasElement>()
            .map_err(|_| ())
            .unwrap();

        canvas
            .unchecked_ref::<Element>()
            .set_attribute("id", "canvas")
            .unwrap();

        AsRef::<Node>::as_ref(&body)
            .append_child(canvas.as_ref())
            .unwrap();

        let events = Arc::new(Mutex::new(Vec::new()));

        let on_mouse_down = {
            let clone = events.clone();
            Closure::wrap(Box::new(move |v: MouseEvent| {
                let button = match v.button() {
                    0 => MouseButton::Left,
                    1 => MouseButton::Middle,
                    2 => MouseButton::Right,
                    i => MouseButton::Other(i as u8),
                };

                let evt = Event::InputDevice(InputEvent::MousePressed { button });
                clone.lock().unwrap().push(evt);
            }) as Box<FnMut(_)>)
        };

        canvas
            .add_event_listener_with_callback("mousedown", on_mouse_down.as_ref().unchecked_ref())
            .unwrap();

        let on_mouse_up = {
            let clone = events.clone();
            Closure::wrap(Box::new(move |v: MouseEvent| {
                let button = match v.button() {
                    0 => MouseButton::Left,
                    1 => MouseButton::Middle,
                    2 => MouseButton::Right,
                    i => MouseButton::Other(i as u8),
                };

                let evt = Event::InputDevice(InputEvent::MouseReleased { button });
                clone.lock().unwrap().push(evt);
            }) as Box<FnMut(_)>)
        };

        canvas
            .add_event_listener_with_callback("mouseup", on_mouse_up.as_ref().unchecked_ref())
            .unwrap();

        let on_mouse_move = {
            let clone = events.clone();
            let window = window.clone();
            let canvas = canvas.clone();
            Closure::wrap(Box::new(move |v: MouseEvent| {
                let dpr = window.device_pixel_ratio() as f32;
                let height = canvas.height() as f32 / dpr;
                let rect = canvas.get_bounding_client_rect();

                let position = (
                    v.client_x() as f32 - rect.x() as f32,
                    height - v.client_y() as f32 + rect.y() as f32,
                );

                let evt = Event::InputDevice(InputEvent::MouseMoved { position });
                clone.lock().unwrap().push(evt);
            }) as Box<FnMut(_)>)
        };

        canvas
            .add_event_listener_with_callback("mousemove", on_mouse_move.as_ref().unchecked_ref())
            .unwrap();

        let on_key_down = {
            let clone = events.clone();
            Closure::wrap(Box::new(move |v: KeyboardEvent| {
                if let Some(key) = types::from_virtual_key_code(&v.key()) {
                    let evt = Event::InputDevice(InputEvent::KeyboardPressed { key });
                    clone.lock().unwrap().push(evt);
                }
            }) as Box<FnMut(_)>)
        };

        canvas
            .add_event_listener_with_callback("keydown", on_key_down.as_ref().unchecked_ref())
            .unwrap();

        let on_key_up = {
            let clone = events.clone();
            Closure::wrap(Box::new(move |v: KeyboardEvent| {
                if let Some(key) = types::from_virtual_key_code(&v.key()) {
                    let evt = Event::InputDevice(InputEvent::KeyboardReleased { key });
                    clone.lock().unwrap().push(evt);
                }
            }) as Box<FnMut(_)>)
        };

        canvas
            .add_event_listener_with_callback("keyup", on_key_up.as_ref().unchecked_ref())
            .unwrap();

        let on_focus = {
            let clone = events.clone();
            Closure::wrap(Box::new(move |_: UiEvent| {
                let evt = Event::Window(WindowEvent::GainFocus);
                clone.lock().unwrap().push(evt);
            }) as Box<FnMut(_)>)
        };

        canvas
            .add_event_listener_with_callback("focus", on_focus.as_ref().unchecked_ref())
            .unwrap();

        let on_lost_focus = {
            let clone = events.clone();
            Closure::wrap(Box::new(move |_: UiEvent| {
                let evt = Event::Window(WindowEvent::GainFocus);
                clone.lock().unwrap().push(evt);
            }) as Box<FnMut(_)>)
        };

        canvas
            .add_event_listener_with_callback("blur", on_lost_focus.as_ref().unchecked_ref())
            .unwrap();

        let on_resize = {
            let clone = events.clone();
            let canvas = canvas.clone();
            Closure::wrap(Box::new(move |_: UiEvent| {
                let evt = Event::Window(WindowEvent::Resized(canvas.width(), canvas.height()));
                clone.lock().unwrap().push(evt);
            }) as Box<FnMut(_)>)
        };

        canvas
            .add_event_listener_with_callback("resize", on_resize.as_ref().unchecked_ref())
            .unwrap();

        let visitor = WebVisitor {
            window: window,
            document: document,
            canvas: canvas,
            events: events,
            on_mouse_down: on_mouse_down,
            on_mouse_up: on_mouse_up,
            on_mouse_move: on_mouse_move,
            on_key_down: on_key_down,
            on_key_up: on_key_up,
            on_focus: on_focus,
            on_lost_focus: on_lost_focus,
            on_resize: on_resize,
        };

        let dpr = visitor.device_pixel_ratio();
        let dims = Vector2::new(
            (params.size.x as f32 * dpr) as u32,
            (params.size.y as f32 * dpr) as u32,
        );

        visitor.resize(dims);
        Ok(visitor)
    }
}

impl Visitor for WebVisitor {
    #[inline]
    fn show(&self) {
        warn!("It does not make sense to `show` window in browser.")
    }

    #[inline]
    fn hide(&self) {
        warn!("It does not make sense to `hide` window in browser.")
    }

    #[inline]
    fn position(&self) -> Vector2<i32> {
        (0, 0).into()
    }

    #[inline]
    fn dimensions(&self) -> Vector2<u32> {
        let dpr = self.window.device_pixel_ratio() as f32;
        Vector2::new(
            (self.canvas.width() as f32 / dpr) as u32,
            (self.canvas.height() as f32 / dpr) as u32,
        )
    }

    #[inline]
    fn device_pixel_ratio(&self) -> f32 {
        self.window.device_pixel_ratio() as f32
    }

    #[inline]
    fn resize(&self, dims: Vector2<u32>) {
        self.canvas.set_width(dims.x);
        self.canvas.set_height(dims.y);

        let dpr = self.device_pixel_ratio();
        self.canvas
            .unchecked_ref::<Element>()
            .set_attribute(
                "style",
                &format!(
                    "width: {}px; height: {}px;",
                    (dims.x as f32 / dpr) as u32,
                    (dims.y as f32 / dpr) as u32
                ),
            ).unwrap();
    }

    #[inline]
    fn poll_events(&mut self, v: &mut Vec<Event>) {
        let mut events = self.events.lock().unwrap();
        v.extend(events.drain(..));
    }

    #[inline]
    fn is_current(&self) -> bool {
        self.document.has_focus().unwrap_or(false)
    }

    #[inline]
    fn make_current(&self) -> Result<()> {
        warn!("You can not `make_current` in browser.");
        Ok(())
    }

    #[inline]
    fn swap_buffers(&self) -> Result<()> {
        Ok(())
    }
}
<|endoftext|>