<commit_before>// MIT License
//
// Copyright (c) 2016 Alexander Thaller <alexander@thaller.ws>
//
// Permission is hereby granted, free of charge, to any person obtaining a copy
// of this software and associated documentation files (the "Software"), to deal
// in the Software without restriction, including without limitation the rights
// to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
// copies of the Software, and to permit persons to whom the Software is
// furnished to do so, subject to the following conditions:
//
// The above copyright notice and this permission notice shall be included in all
// copies or substantial portions of the Software.
//
// THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
// IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
// FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
// AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
// LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
// OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
// SOFTWARE.
#![feature(insert_str)]

extern crate chrono;
extern crate githelper;
extern crate lablog_lib;
extern crate loggerv;
extern crate regex;
extern crate tempdir;
extern crate xdg;

#[macro_use]
extern crate clap;

#[macro_use]
extern crate log;

use std::io::prelude::*;
use chrono::*;
use clap::App;
use clap::ArgMatches as Args;
use lablog_lib::archive_project;
use lablog_lib::file_to_string;
use lablog_lib::filter_notes_by_timestamp;
use lablog_lib::format_projects_notes;
use lablog_lib::get_projects;
use lablog_lib::get_projects_notes;
use lablog_lib::get_timeline_for_notes;
use lablog_lib::git_commit_note;
use lablog_lib::Note;
use lablog_lib::ProjectsNotes;
use lablog_lib::try_multiple_time_parser;
use lablog_lib::write_note;
use lablog_lib::write_project;
use log::LogLevel;
use regex::Regex;
use std::collections::BTreeMap as DataMap;
use std::collections::BTreeSet as DataSet;
use std::env;
use std::fs::File;
use std::path::Path;
use std::path::PathBuf;
use std::process::Command;
use tempdir::TempDir;
use xdg::BaseDirectories;

#[derive(Debug)]
struct Options {
    datadir: PathBuf,
}

fn main() {
    let yaml = load_yaml!("cli.yml");
    let app = App::from_yaml(yaml)
        .version(crate_version!())
        .get_matches();

    let matches = match app.subcommand.clone() {
        Some(subcommand) => subcommand.matches,
        None => app.clone(),
    };

    let loglevel: LogLevel = app.value_of("loglevel").unwrap().parse().unwrap();
    loggerv::init_with_level(loglevel).unwrap();

    let options = get_options(&app);

    trace!("app: {:#?}", app);
    trace!("matches: {:#?}", matches);
    trace!("options: {:#?}", options);

    run(app, options)
}

fn run(args: Args, options: Options) {
    match args.subcommand.clone() {
        Some(command) => {
            match command.name.as_str() {
                "dates" => run_dates(command.matches, options),
                "note" => run_note(command.matches, options),
                "notes" => run_notes(command.matches, options),
                "timestamps" => run_timestamps(command.matches, options),
                "projects" => run_projects(command.matches, options),
                "repo" => run_repo(command.matches, options),
                "search" => run_search(command.matches, options),
                "timeline" => run_timeline(command.matches, options),
                "archive" => run_archive(command.matches, options),
                "edit" => run_edit(command.matches, options),
                _ => {
                    error!("do not know what to do with this command: {}",
                           command.name.as_str())
                }
            }
        }
        None => run_projects(args, options),
    }
}

fn run_dates(args: Args, options: Options) {
    let notes = get_filtered_notes(&args, &options);

    let mut dates = DataMap::default();
    for notes in notes.values() {
        for note in notes {
            *dates.entry(note.time_stamp.date()).or_insert(0) += 1;
        }
    }

    for (date, count) in dates {
        println!("{} {}", date, count)
    }
}

fn run_note(args: Args, options: Options) {
    trace!("run_note args: {:#?}", args);
    let project = Some(String::from(args.value_of("project").unwrap()));

    let text = if args.is_present("editor") {
        string_from_editor("")
    } else {
        match args.value_of("file") {
            Some(file_path) => {
                let file_text = file_to_string(Path::new(file_path)).unwrap();
                trace!("text from file: {}", file_text);
                file_text
            }
            None => {
                match args.value_of("text") {
                    Some(text) => String::from(text),
                    None => String::from(""),
                }
            }
        }
    };

    debug!("project: {:#?}", project);
    debug!("text: {}", text);

    let timestamp: DateTime<UTC> = match args.value_of("timestamp") {
        None => UTC::now().into(),
        Some(ts) => ts.parse().unwrap(),
    };

    let note = Note {
        time_stamp: timestamp,
        value: text.into(),
    };

    if write_note(&options.datadir, project.clone(), &note).is_some() {
        git_commit_note(&options.datadir, project.clone(), Some(&note), "added")
    }
}

fn run_notes(args: Args, options: Options) {
    let notes = get_filtered_notes(&args, &options);
    println!("{}", format_projects_notes(notes));
}

fn run_timestamps(args: Args, options: Options) {
    let notes = get_filtered_notes(&args, &options);

    for (project, notes) in notes {
        println!("{}", project);

        for (i, note) in notes.iter().enumerate() {
            println!("{}\t{}", i, note.time_stamp)
        }

        println!("");
    }
}

fn run_archive(args: Args, options: Options) {
    let project = Some(String::from(args.value_of("project").unwrap()));
    let recursive = args.is_present("recursive");

    let projects = get_projects(&options.datadir, project.clone());
    archive_project(&options.datadir, projects, project, recursive);
}

fn run_projects(args: Args, options: Options) {
    let projects = get_projects(&options.datadir,
                                Some(String::from(args.value_of("project").unwrap_or(""))));
    trace!("projects: {:#?}", projects);

    for project in projects {
        println!("{}", project)
    }
}

fn run_repo(args: Args, options: Options) {
    if let Some(command) = args.subcommand.clone() {
        match command.name.as_str() {
            "init" => run_repo_init(&args, options),
            "pull" => run_repo_pull(options),
            "push" => run_repo_push(options),
            "sync" => run_repo_sync(options),
            _ => {
                error!("do not know what to do with this command: {}",
                       command.name.as_str())
            }
        }
    }
}

fn run_repo_pull(options: Options) {
    info!("pulling from remote repository");
    githelper::pull(options.datadir.as_path()).expect("can not pull from repo");
}

fn run_repo_push(options: Options) {
    info!("pushing to remote repository");
    githelper::push(options.datadir.as_path()).expect("can not push to repo");
}

fn run_repo_sync(options: Options) {
    info!("syncing with remote repository");
    githelper::sync(options.datadir.as_path()).expect("can not sync with repo");
}

fn run_repo_init(args: &Args, options: Options) {
    let datadir = options.datadir;

    match args.value_of("remote") {
        Some(remote) => {
            match githelper::clone(datadir.as_path(), remote) {
                Err(err) => error!("can not clone git repository from remote: {}", err),
                Ok(info) => info!("repo was cloned: {}", info),
            }
        }
        None => {
            match githelper::status(datadir.as_path()) {
                Ok(status) => error!("repository is already initialized: {}", status),
                Err(_) => {
                    githelper::init(datadir.as_path()).expect("can not initialize git repo");
                    githelper::commit(datadir.as_path(), "initial commit")
                        .expect("can not add initial commit to git repo");
                }
            };
        }
    }
}

fn run_search(args: Args, options: Options) {
    let text = args.value_of("text").unwrap();
    let notes = get_filtered_notes(&args, &options);

    let re = Regex::new(text).unwrap();

    let mut searched = DataMap::default();
    for (project, notes) in notes {
        for note in notes {
            for line in note.value.lines() {
                if re.is_match(line) {
                    debug!("match on line: {}", line);

                    let find = re.find(line).unwrap();

                    debug!("find: {:#?}", find);

                    let mut replaced = String::from(line);
                    replaced.insert_str(find.end(), "\x1B[0m\x1B[0m");
                    replaced.insert_str(find.start(), "\x1B[1m\x1B[31m");

                    debug!("replaced: {}", replaced);

                    searched.entry(project.clone())
                        .or_insert_with(DataSet::default)
                        .insert(replaced);
                }
            }
        }
    }

    let mut out = String::new();

    for (project, notes) in searched {
        out.push_str(format!("== {}\n", project).as_str());
        for note in notes {
            out.push_str(note.as_str());
            out.push_str("\n");
        }

        out.push_str("\n\n");
    }

    println!("{}", out);
}

fn run_timeline(args: Args, options: Options) {
    let notes = get_filtered_notes(&args, &options);
    println!("{}", get_timeline_for_notes(notes));
}

fn run_edit(args: Args, options: Options) {
    let mut project_notes = get_filtered_notes(&args, &options);

    if project_notes.len() != 1 {
        panic!("can not edit multiple projects at once")
    }

    let note_id: usize =
        args.value_of("note_id").unwrap().parse().expect("can not convert note_id to integer");

    for (project, mut notes) in project_notes.clone() {
        let mut oldnote = Note::default();
        let mut newnote = Note::default();
        if notes.len() - 1 < note_id {
            panic!("selected note_id is bigger than the available notes")
        }

        for (i, note) in notes.clone().into_iter().enumerate() {
            if i == note_id {
                let newval = string_from_editor(note.value.as_str());

                newnote = Note {
                    time_stamp: note.time_stamp,
                    value: newval,
                };

                oldnote = note.clone();
            }
        }

        notes.remove(&oldnote);
        notes.insert(newnote);

        project_notes.remove(&project);
        project_notes.insert(project, notes);
    }

    for (project, notes) in project_notes.clone() {
        write_project(&options.datadir, Some(project.clone()), &notes, true);
        git_commit_note(&options.datadir, Some(project.clone()), None, "edited")
    }
}

fn string_from_editor(prepoluate: &str) -> String {
    let tmpdir = TempDir::new("lablog_tmp").unwrap();
    let tmppath = tmpdir.path().join("note.asciidoc");
    let editor = match env::var("VISUAL") {
        Ok(val) => val,
        Err(_) => {
            match env::var("EDITOR") {
                Ok(val) => val,
                Err(_) => panic!("Neither $VISUAL nor $EDITOR is set."),
            }
        }
    };

    {
        let mut f = File::create(tmppath.display().to_string())
            .expect("can not open tmp editor file to prepoluate with string");
        f.write_all(prepoluate.as_bytes()).expect("can not prepoluate editor tmp file");
    }

    let mut editor_command = Command::new(editor);
    editor_command.arg(tmppath.display().to_string());

    let editor_proc = editor_command.spawn();
    if editor_proc.expect("Couldn't launch editor").wait().is_ok() {
        file_to_string(&tmppath).unwrap()
    } else {
        panic!("The editor broke")
    }
}

fn get_options(args: &Args) -> Options {
    let datadir = match args.value_of("datadir").unwrap() {
        "$XDG_DATA_HOME/lablog" => {
            let xdg = BaseDirectories::new().unwrap();
            xdg.create_data_directory("lablog").unwrap()
        }
        _ => PathBuf::from(args.value_of("datadir").unwrap()),
    };

    let options = Options { datadir: datadir };

    debug!("options: {:?}", options);
    options
}

fn get_filtered_notes(args: &Args, options: &Options) -> ProjectsNotes {
    let projects = get_projects(&options.datadir,
                                Some(String::from(args.value_of("project").unwrap_or(""))));
    let project_notes = get_projects_notes(&options.datadir, projects);

    let after_notes = match args.value_of("filter_after") {
        Some(filter) => {
            debug!("filter notes after {}", filter);
            let timestamp = try_multiple_time_parser(filter)
                .expect("can not parse timestamp from after parameter");
            debug!("filter notes after timestamp: {:#?}", timestamp);

            filter_notes_by_timestamp(project_notes, timestamp, true)
        }
        None => project_notes,
    };

    match args.value_of("filter_before") {
        Some(filter) => {
            debug!("filter notes before timestamp {}", filter);
            let timestamp = try_multiple_time_parser(filter)
                .expect("can not parse timestamp from parameter");
            debug!("filter notes before timestamp parsed: {:#?}", timestamp);

            filter_notes_by_timestamp(after_notes, timestamp, false)
        }
        None => after_notes,
    }
}
<commit_msg>Changed timstamps id to count from one upward to make it similar to what is seen in the web application.<commit_after>// MIT License
//
// Copyright (c) 2016 Alexander Thaller <alexander@thaller.ws>
//
// Permission is hereby granted, free of charge, to any person obtaining a copy
// of this software and associated documentation files (the "Software"), to deal
// in the Software without restriction, including without limitation the rights
// to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
// copies of the Software, and to permit persons to whom the Software is
// furnished to do so, subject to the following conditions:
//
// The above copyright notice and this permission notice shall be included in all
// copies or substantial portions of the Software.
//
// THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
// IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
// FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
// AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
// LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
// OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
// SOFTWARE.
#![feature(insert_str)]

extern crate chrono;
extern crate githelper;
extern crate lablog_lib;
extern crate loggerv;
extern crate regex;
extern crate tempdir;
extern crate xdg;

#[macro_use]
extern crate clap;

#[macro_use]
extern crate log;

use std::io::prelude::*;
use chrono::*;
use clap::App;
use clap::ArgMatches as Args;
use lablog_lib::archive_project;
use lablog_lib::file_to_string;
use lablog_lib::filter_notes_by_timestamp;
use lablog_lib::format_projects_notes;
use lablog_lib::get_projects;
use lablog_lib::get_projects_notes;
use lablog_lib::get_timeline_for_notes;
use lablog_lib::git_commit_note;
use lablog_lib::Note;
use lablog_lib::ProjectsNotes;
use lablog_lib::try_multiple_time_parser;
use lablog_lib::write_note;
use lablog_lib::write_project;
use log::LogLevel;
use regex::Regex;
use std::collections::BTreeMap as DataMap;
use std::collections::BTreeSet as DataSet;
use std::env;
use std::fs::File;
use std::path::Path;
use std::path::PathBuf;
use std::process::Command;
use tempdir::TempDir;
use xdg::BaseDirectories;

#[derive(Debug)]
struct Options {
    datadir: PathBuf,
}

fn main() {
    let yaml = load_yaml!("cli.yml");
    let app = App::from_yaml(yaml)
        .version(crate_version!())
        .get_matches();

    let matches = match app.subcommand.clone() {
        Some(subcommand) => subcommand.matches,
        None => app.clone(),
    };

    let loglevel: LogLevel = app.value_of("loglevel").unwrap().parse().unwrap();
    loggerv::init_with_level(loglevel).unwrap();

    let options = get_options(&app);

    trace!("app: {:#?}", app);
    trace!("matches: {:#?}", matches);
    trace!("options: {:#?}", options);

    run(app, options)
}

fn run(args: Args, options: Options) {
    match args.subcommand.clone() {
        Some(command) => {
            match command.name.as_str() {
                "dates" => run_dates(command.matches, options),
                "note" => run_note(command.matches, options),
                "notes" => run_notes(command.matches, options),
                "timestamps" => run_timestamps(command.matches, options),
                "projects" => run_projects(command.matches, options),
                "repo" => run_repo(command.matches, options),
                "search" => run_search(command.matches, options),
                "timeline" => run_timeline(command.matches, options),
                "archive" => run_archive(command.matches, options),
                "edit" => run_edit(command.matches, options),
                _ => {
                    error!("do not know what to do with this command: {}",
                           command.name.as_str())
                }
            }
        }
        None => run_projects(args, options),
    }
}

fn run_dates(args: Args, options: Options) {
    let notes = get_filtered_notes(&args, &options);

    let mut dates = DataMap::default();
    for notes in notes.values() {
        for note in notes {
            *dates.entry(note.time_stamp.date()).or_insert(0) += 1;
        }
    }

    for (date, count) in dates {
        println!("{} {}", date, count)
    }
}

fn run_note(args: Args, options: Options) {
    trace!("run_note args: {:#?}", args);
    let project = Some(String::from(args.value_of("project").unwrap()));

    let text = if args.is_present("editor") {
        string_from_editor("")
    } else {
        match args.value_of("file") {
            Some(file_path) => {
                let file_text = file_to_string(Path::new(file_path)).unwrap();
                trace!("text from file: {}", file_text);
                file_text
            }
            None => {
                match args.value_of("text") {
                    Some(text) => String::from(text),
                    None => String::from(""),
                }
            }
        }
    };

    debug!("project: {:#?}", project);
    debug!("text: {}", text);

    let timestamp: DateTime<UTC> = match args.value_of("timestamp") {
        None => UTC::now().into(),
        Some(ts) => ts.parse().unwrap(),
    };

    let note = Note {
        time_stamp: timestamp,
        value: text.into(),
    };

    if write_note(&options.datadir, project.clone(), &note).is_some() {
        git_commit_note(&options.datadir, project.clone(), Some(&note), "added")
    }
}

fn run_notes(args: Args, options: Options) {
    let notes = get_filtered_notes(&args, &options);
    println!("{}", format_projects_notes(notes));
}

fn run_timestamps(args: Args, options: Options) {
    let notes = get_filtered_notes(&args, &options);

    for (project, notes) in notes {
        println!("{}", project);

        for (i, note) in notes.iter().enumerate() {
            // present timestamp numbering from 1 upwards
            println!("{}\t{}", i + 1, note.time_stamp)
        }

        println!("");
    }
}

fn run_archive(args: Args, options: Options) {
    let project = Some(String::from(args.value_of("project").unwrap()));
    let recursive = args.is_present("recursive");

    let projects = get_projects(&options.datadir, project.clone());
    archive_project(&options.datadir, projects, project, recursive);
}

fn run_projects(args: Args, options: Options) {
    let projects = get_projects(&options.datadir,
                                Some(String::from(args.value_of("project").unwrap_or(""))));
    trace!("projects: {:#?}", projects);

    for project in projects {
        println!("{}", project)
    }
}

fn run_repo(args: Args, options: Options) {
    if let Some(command) = args.subcommand.clone() {
        match command.name.as_str() {
            "init" => run_repo_init(&args, options),
            "pull" => run_repo_pull(options),
            "push" => run_repo_push(options),
            "sync" => run_repo_sync(options),
            _ => {
                error!("do not know what to do with this command: {}",
                       command.name.as_str())
            }
        }
    }
}

fn run_repo_pull(options: Options) {
    info!("pulling from remote repository");
    githelper::pull(options.datadir.as_path()).expect("can not pull from repo");
}

fn run_repo_push(options: Options) {
    info!("pushing to remote repository");
    githelper::push(options.datadir.as_path()).expect("can not push to repo");
}

fn run_repo_sync(options: Options) {
    info!("syncing with remote repository");
    githelper::sync(options.datadir.as_path()).expect("can not sync with repo");
}

fn run_repo_init(args: &Args, options: Options) {
    let datadir = options.datadir;

    match args.value_of("remote") {
        Some(remote) => {
            match githelper::clone(datadir.as_path(), remote) {
                Err(err) => error!("can not clone git repository from remote: {}", err),
                Ok(info) => info!("repo was cloned: {}", info),
            }
        }
        None => {
            match githelper::status(datadir.as_path()) {
                Ok(status) => error!("repository is already initialized: {}", status),
                Err(_) => {
                    githelper::init(datadir.as_path()).expect("can not initialize git repo");
                    githelper::commit(datadir.as_path(), "initial commit")
                        .expect("can not add initial commit to git repo");
                }
            };
        }
    }
}

fn run_search(args: Args, options: Options) {
    let text = args.value_of("text").unwrap();
    let notes = get_filtered_notes(&args, &options);

    let re = Regex::new(text).unwrap();

    let mut searched = DataMap::default();
    for (project, notes) in notes {
        for note in notes {
            for line in note.value.lines() {
                if re.is_match(line) {
                    debug!("match on line: {}", line);

                    let find = re.find(line).unwrap();

                    debug!("find: {:#?}", find);

                    let mut replaced = String::from(line);
                    replaced.insert_str(find.end(), "\x1B[0m\x1B[0m");
                    replaced.insert_str(find.start(), "\x1B[1m\x1B[31m");

                    debug!("replaced: {}", replaced);

                    searched.entry(project.clone())
                        .or_insert_with(DataSet::default)
                        .insert(replaced);
                }
            }
        }
    }

    let mut out = String::new();

    for (project, notes) in searched {
        out.push_str(format!("== {}\n", project).as_str());
        for note in notes {
            out.push_str(note.as_str());
            out.push_str("\n");
        }

        out.push_str("\n\n");
    }

    println!("{}", out);
}

fn run_timeline(args: Args, options: Options) {
    let notes = get_filtered_notes(&args, &options);
    println!("{}", get_timeline_for_notes(notes));
}

fn run_edit(args: Args, options: Options) {
    let mut project_notes = get_filtered_notes(&args, &options);

    if project_notes.len() != 1 {
        panic!("can not edit multiple projects at once")
    }

    let note_id: usize =
        args.value_of("note_id").unwrap().parse().expect("can not convert note_id to integer");

    // the user selects from 1 upwards we have to count from 0 upwards
    let note_id = note_id - 1;

    for (project, mut notes) in project_notes.clone() {
        let mut oldnote = Note::default();
        let mut newnote = Note::default();
        if notes.len() - 1 < note_id {
            panic!("selected note_id is bigger than the available notes")
        }

        for (i, note) in notes.clone().into_iter().enumerate() {
            if i == note_id {
                let newval = string_from_editor(note.value.as_str());

                newnote = Note {
                    time_stamp: note.time_stamp,
                    value: newval,
                };

                oldnote = note.clone();
            }
        }

        notes.remove(&oldnote);
        notes.insert(newnote);

        project_notes.remove(&project);
        project_notes.insert(project, notes);
    }

    for (project, notes) in project_notes.clone() {
        write_project(&options.datadir, Some(project.clone()), &notes, true);
        git_commit_note(&options.datadir, Some(project.clone()), None, "edited")
    }
}

fn string_from_editor(prepoluate: &str) -> String {
    let tmpdir = TempDir::new("lablog_tmp").unwrap();
    let tmppath = tmpdir.path().join("note.asciidoc");
    let editor = match env::var("VISUAL") {
        Ok(val) => val,
        Err(_) => {
            match env::var("EDITOR") {
                Ok(val) => val,
                Err(_) => panic!("Neither $VISUAL nor $EDITOR is set."),
            }
        }
    };

    {
        let mut f = File::create(tmppath.display().to_string())
            .expect("can not open tmp editor file to prepoluate with string");
        f.write_all(prepoluate.as_bytes()).expect("can not prepoluate editor tmp file");
    }

    let mut editor_command = Command::new(editor);
    editor_command.arg(tmppath.display().to_string());

    let editor_proc = editor_command.spawn();
    if editor_proc.expect("Couldn't launch editor").wait().is_ok() {
        file_to_string(&tmppath).unwrap()
    } else {
        panic!("The editor broke")
    }
}

fn get_options(args: &Args) -> Options {
    let datadir = match args.value_of("datadir").unwrap() {
        "$XDG_DATA_HOME/lablog" => {
            let xdg = BaseDirectories::new().unwrap();
            xdg.create_data_directory("lablog").unwrap()
        }
        _ => PathBuf::from(args.value_of("datadir").unwrap()),
    };

    let options = Options { datadir: datadir };

    debug!("options: {:?}", options);
    options
}

fn get_filtered_notes(args: &Args, options: &Options) -> ProjectsNotes {
    let projects = get_projects(&options.datadir,
                                Some(String::from(args.value_of("project").unwrap_or(""))));
    let project_notes = get_projects_notes(&options.datadir, projects);

    let after_notes = match args.value_of("filter_after") {
        Some(filter) => {
            debug!("filter notes after {}", filter);
            let timestamp = try_multiple_time_parser(filter)
                .expect("can not parse timestamp from after parameter");
            debug!("filter notes after timestamp: {:#?}", timestamp);

            filter_notes_by_timestamp(project_notes, timestamp, true)
        }
        None => project_notes,
    };

    match args.value_of("filter_before") {
        Some(filter) => {
            debug!("filter notes before timestamp {}", filter);
            let timestamp = try_multiple_time_parser(filter)
                .expect("can not parse timestamp from parameter");
            debug!("filter notes before timestamp parsed: {:#?}", timestamp);

            filter_notes_by_timestamp(after_notes, timestamp, false)
        }
        None => after_notes,
    }
}
<|endoftext|>


<commit_before>// Chapter 1
// ------------------ print macros start ------------------
// Macros: Macros look like functions, except that their name ends with a bang !,
// but instead of generating a function call, macros are expanded into source code
// that gets compiled with the rest of the program. eg. println!
// println! -> prints statement to console in a new line

// !!!--- start uncommenting from line below ---!!!
// fn main() {
//     println!("April has {} days.", 31);
//     println!("{month} has {numberOfDays} days.",
//         month = "January",
//         numberOfDays = "31");
//     // Special formating is used here
//     // {:b} -> converts 2 to 10 (binary form)
//     println!("{} of {:b} people know binary, the other half doesn't", 1, 2);
//     let width = 6;
//     println!("{number:>width$} {spaces} spaces and {number}", number = 1, width = width, spaces = width - 1);
//     println!("{}", format!("{:05}", 12));

//     // rounds off the number
//     let pi = 3.141592;
//     println!("Pi is roughly {:.3}", pi);
// }
// !!!--- end uncommenting from line above ---!!!
// ------------------ print macros end ------------------

// Chapter 2
// ------------------ Debugging start ------------------
// In JS we use console.log() for logging and debugging purposes.
// console.log() can be used to print pretty any type of object.
// However, in Rust this is not possible as all types which want to use
// std::fmt formatting traits require an implementation to be printable.
// The fmt::Debug trait makes this very straightforward.
// All types can derive (automatically create) the fmt::Debug implementation.
// This is not true for fmt::Display which must be manually implemented.

// !!!--- start uncommenting from line below ---!!!
#[derive(Debug)]
struct Person<'a> {
    name: &'a str,
    age: u8
}
fn main() {
    let name = "Peter";
    let age = 27;
    let peter = Person { name, age };

    // Pretty print
    println!("{:#?}", peter);
}
// !!!--- end uncommenting from line above ---!!!
// ------------------ Debugging end ------------------<commit_msg>add manual implementation for Display<commit_after>// Chapter 1
// ------------------ print macros start ------------------
// Macros: Macros look like functions, except that their name ends with a bang !,
// but instead of generating a function call, macros are expanded into source code
// that gets compiled with the rest of the program. eg. println!
// println! -> prints statement to console in a new line

// !!!--- start uncommenting from line below ---!!!
// fn main() {
//     println!("April has {} days.", 31);
//     println!("{month} has {numberOfDays} days.",
//         month = "January",
//         numberOfDays = "31");
//     // Special formating is used here
//     // {:b} -> converts 2 to 10 (binary form)
//     println!("{} of {:b} people know binary, the other half doesn't", 1, 2);
//     let width = 6;
//     println!("{number:>width$} {spaces} spaces and {number}", number = 1, width = width, spaces = width - 1);
//     println!("{}", format!("{:05}", 12));

//     // rounds off the number
//     let pi = 3.141592;
//     println!("Pi is roughly {:.3}", pi);
// }
// !!!--- end uncommenting from line above ---!!!
// ------------------ print macros end ------------------

// Chapter 2
// ------------------ Debugging start ------------------
// In JS we use console.log() for logging and debugging purposes.
// console.log() can be used to print pretty any type of object.
// However, in Rust this is not possible as all types which want to use
// std::fmt formatting traits require an implementation to be printable.
// The fmt::Debug trait makes this very straightforward.
// All types can derive (automatically create) the fmt::Debug implementation.
// This is not true for fmt::Display which must be manually implemented.
// Below given example shows automatic derive(Debug) implementation
// and manual Display implementation.

// !!!--- start uncommenting from line below ---!!!
// Import (via `use`) the `fmt` module to make it available.
use std::fmt; // Import `fmt`
#[derive(Debug)]
struct Person<'a> {
    name: &'a str,
    age: u8
}

// Implement `Display` for `Person`.
impl<'a> fmt::Display for Person<'a> {
    fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result {
        write!(f, "(Name is {} and Age is {})", self.name, self.age)
    }
}
fn main() {
    let name = "Peter";
    let age = 27;
    let peter = Person { name, age };

    println!("Compare Person");
    println!("Debug: Normal {:?}", peter);
    println!("Debug: Pretty {:#?}", peter);
    println!("Display: {}", peter);
}
// !!!--- end uncommenting from line above ---!!!
// ------------------ Debugging end ------------------<|endoftext|>


<commit_before>//! online reinforcement q learner (kind of double q)
#![allow(dead_code)]

extern crate rand;
extern crate nn;

use std::fs::File;
use std::io::{BufReader, BufWriter};
use std::io::prelude::*;
use self::rand::Rng;
use self::nn::{NN, HaltCondition};
use super::Player;
use super::super::field::Field;

const GAMMA:f64 = 0.99; //q gamma (action-reward time difference high) //1.0?
const LR:f64 = 0.5; //neural net learning rate
const LR_DECAY:f64 = 200000f64; //NN learning rate decrease (half every DECAY games)
const LR_MIN:f64 = 0.1; //minimum NN LR
const MOM:f64 = 0.1; //neural net momentum
const EPOCHS_PER_STEP:u32 = 1; //epochs to learn from each turn
const RND_PICK_START:f64 = 0.5; //exploration factor start
const RND_PICK_DEC:f64 = 250000f64; //random exploration decrease (half every DEC games)
const RND_PICK_MIN:f64 = 0.05; //exploration rate minimum

pub struct PlayerAIQ
{
	initialized: bool,
	fixed: bool, //should the agent learn or not (fixed => dont learn)
	filename: String,
	pid: i32, //player ID
	nn: Option<NN>, //online network
	targetnn: Option<NN>, //target network (temporarely fixed value network)
	games_played: u32,
	lr: f64,
	exploration: f64,
	startp: f64, //for NN input (1 = self starting, -1 = enemy starting)
	memstate: Vec<f64>, //memorize state learning next turn
	memqval: Vec<f64>, //same
	memreward: f64, //same
	memplay: u32, //same
}

impl PlayerAIQ
{
	pub fn new(fix:bool) -> Box<PlayerAIQ>
	{
		Box::new(PlayerAIQ { initialized: false, fixed: fix, filename: String::new(), pid: 0,
				nn: None, targetnn: None, games_played: 0, lr: LR, exploration: RND_PICK_START,
				startp: 0.0, memstate: Vec::new(), memqval: Vec::new(), memreward: -1.0, memplay: 0 })
	}

	fn get_exploration(&self) -> f64
	{
		RND_PICK_MIN.max(RND_PICK_START * (2f64).powf(-(self.games_played as f64)/RND_PICK_DEC))
	}

	fn get_lr(&self) -> f64
	{
		LR_MIN.max(LR * (2f64).powf(-(self.games_played as f64)/LR_DECAY))
	}

	fn argmax(slice:&[f64]) -> u32
	{
		let mut x:u32 = 0;
		let mut max = slice[0];
		for i in 1..slice.len()
		{
			if max<slice[i]
			{
				x = i as u32;
				max = slice[i];
			}
		}
		x
	}

	fn field_to_input(field:&mut Field, p:i32, startp:f64) -> Vec<f64>
	{
		let op:i32 = if p == 1 { 2 } else { 1 }; //other player
		let mut input:Vec<f64> = Vec::with_capacity((2*field.get_size() + field.get_w() + 1) as usize);
		for (i, val) in field.get_field().iter().enumerate()
		{ //2 nodes for every square: -1 enemy, 0 free, 1 own; 0 square will not be reached with one move, 1 square can be directly filled
			if *val == p { input.push(1f64); input.push(0f64); }
			else if *val == op { input.push(-1f64); input.push(0f64); }
			else
			{ //empty square
				input.push(0f64);
				if (i as u32) < (field.get_size()-field.get_w()) { input.push(if field.get_field()[i+field.get_w() as usize] != 0 { 1f64 } else { 0f64 }); }
				else { input.push(1f64); }
			}
		}
		for x in 0..field.get_w()
		{ //1 node for every column: 1 own win, -1 enemy win, 0 none (which consistent order of the nodes does not matter, fully connected)
			if field.play(p, x)
			{ //valid play
				match field.get_state()
				{
					-1 | 0 => input.push(0f64),
					pid => input.push(if pid == p {1f64} else {-1f64}),
				}
				field.undo();
			}
			else { input.push(0f64); } //illegal move, nobody can win
		}
		//1 node for starting player (-1 enemy, 1 self)
		input.push(startp);
		//return
		input
	}
}

impl Player for PlayerAIQ
{
	fn init(&mut self, field:&Field, p:i32) -> bool
	{
		self.pid = p;

		self.filename = format!("AIQ-{}x{}.NN", field.get_w(), field.get_h());
		let file = File::open(&self.filename);
		if file.is_err()
		{
			//create new neural net, as it could not be loaded
			let n = field.get_size();
			let w = field.get_w();
			self.nn = Some(NN::new(&[2*n+w+1, 4*n, 2*n, n, n, n/2, w])); //set size of NN layers here
			//games_played, exploration, lr already set
		}
		else
		{
			//load neural net from file (and games played)
			let mut reader = BufReader::new(file.unwrap());
			let mut datas = String::new();
			let mut nns = String::new();

			let res1 = reader.read_line(&mut datas);
			let res2 = reader.read_to_string(&mut nns);
			if res1.is_err() || res2.is_err() { return false; }

			let res = datas.trim().parse::<u32>();
			if res.is_err() { return false; }
			self.games_played = res.unwrap();
			self.nn = Some(NN::from_json(&nns));

			self.lr = self.get_lr();
			self.exploration = self.get_exploration();
		}

		self.targetnn = self.nn.clone();
		self.initialized = true;
		true
	}

	fn startp(&mut self, p:i32)
	{
		if p == self.pid
		{
			self.startp = 1.0;
		}
		else
		{
			self.startp = -1.0;
		}
	}

	fn play(&mut self, field:&mut Field) -> bool
	{
		if !self.initialized { return false; }
		//variables
		let mut rng = rand::thread_rng();
		let nn = self.nn.as_mut().unwrap();
		let targetnn = self.targetnn.as_mut().unwrap();

		//get current state formatted for the neural net
		let state = PlayerAIQ::field_to_input(field, self.pid, self.startp);

		//learn if not first move (reward is already set, won/loose would be outcome)
		if self.memreward != -1.0
		{
			//get Q values for next state
			let qval2 = targetnn.run(&state); //use double q learning target nn, to decouple action and value a bit
			let max = qval2[PlayerAIQ::argmax(&qval2) as usize];
			//calculate q update
			self.memqval[self.memplay as usize] = (self.memreward + GAMMA * max) / (1.0 + GAMMA); //Q learning (divide to stay in [0,1] for sigmoid)
			//train on the latest experience (q update)
			nn.train(&[(self.memstate.clone(), self.memqval.clone())])
				.halt_condition(HaltCondition::Epochs(EPOCHS_PER_STEP))
				.log_interval(None)
				//.log_interval(Some(2)) //debug
				.momentum(MOM)
				.rate(self.lr)
				.go();
		}

		//choose action by e-greedy (no exploration when fixed AI version+)
		self.memqval = nn.run(&state);
		self.memplay = PlayerAIQ::argmax(&self.memqval);
		if !self.fixed && rng.gen::<f64>() < self.exploration //random exploration if agent should learn.
		{
			self.memplay = rng.gen::<u32>() % field.get_w();
		}

		//perform action and set reward
		self.memreward = 0.5;
		let mut res = field.play(self.pid, self.memplay);
		if !res { self.memqval[self.memplay as usize] = 0.1; } //move did not meet the rules, but learn anyway, even if another random move is made

		//random play when it was not rule conform, also modify q-value for it
		while !res //infinite if it is already draw! (cannot happen without bug)
		{
			self.memplay = rng.gen::<u32>() % field.get_w();
			res = field.play(self.pid, self.memplay);
		}

		//save state for next turn
		self.memstate = state;

		//return
		res
	}

	#[allow(unused_variables)]
	fn outcome(&mut self, field:&mut Field, state:i32)
	{
		//learn
		let nn = self.nn.as_mut().unwrap();
		let op:i32 = if self.pid == 1 { 2 } else { 1 }; //other player
		//set reward (if draw, reward already set properly)
		if state == self.pid { self.memreward = 1.0; }
		else if state == op { self.memreward = 0.0; }
		//end-values of network should meet reward exactly
		self.memqval[self.memplay as usize] = self.memreward;
		//train NN
		nn.train(&[(self.memstate.clone(), self.memqval.clone())])
			.halt_condition(HaltCondition::Epochs(EPOCHS_PER_STEP))
			.log_interval(None)
			//.log_interval(Some(2)) //debug
			.momentum(MOM)
			.rate(self.lr)
			.go();

		//parameters
		self.games_played += 1;
		self.lr = self.get_lr();
		self.exploration = self.get_exploration();
		self.targetnn = self.nn.clone();
		self.memreward = -1.0;
	}
}

impl Drop for PlayerAIQ
{
	fn drop(&mut self)
	{
		//write neural net to file, if it was allowed to learn and was initialized
		if self.initialized && !self.fixed
		{
			let file = File::create(&self.filename);
			if file.is_err() { println!("Warning: Could not write AIQ NN file!"); return; }
			let mut writer = BufWriter::new(file.unwrap());

			let res1 = writeln!(&mut writer, "{}", self.games_played);
			let res2 = write!(&mut writer, "{}", self.nn.as_mut().unwrap().to_json());
			if res1.is_err() || res2.is_err() { println!("Warning: There was an error while writing AIQ NN file!"); return; }
		}
	}
}
<commit_msg>bugfix<commit_after>//! online reinforcement q learner (kind of double q)
#![allow(dead_code)]

extern crate rand;
extern crate nn;

use std::fs::File;
use std::io::{BufReader, BufWriter};
use std::io::prelude::*;
use self::rand::Rng;
use self::nn::{NN, HaltCondition};
use super::Player;
use super::super::field::Field;

const GAMMA:f64 = 0.99; //q gamma (action-reward time difference high) //1.0?
const LR:f64 = 0.5; //neural net learning rate
const LR_DECAY:f64 = 200000f64; //NN learning rate decrease (half every DECAY games)
const LR_MIN:f64 = 0.1; //minimum NN LR
const MOM:f64 = 0.1; //neural net momentum
const EPOCHS_PER_STEP:u32 = 1; //epochs to learn from each turn
const RND_PICK_START:f64 = 0.5; //exploration factor start
const RND_PICK_DEC:f64 = 250000f64; //random exploration decrease (half every DEC games)
const RND_PICK_MIN:f64 = 0.05; //exploration rate minimum

pub struct PlayerAIQ
{
	initialized: bool,
	fixed: bool, //should the agent learn or not (fixed => dont learn)
	filename: String,
	pid: i32, //player ID
	nn: Option<NN>, //online network
	targetnn: Option<NN>, //target network (temporarely fixed value network)
	games_played: u32,
	lr: f64,
	exploration: f64,
	startp: f64, //for NN input (1 = self starting, -1 = enemy starting)
	memstate: Vec<f64>, //memorize state learning next turn
	memqval: Vec<f64>, //same
	memreward: f64, //same
	memplay: u32, //same
}

impl PlayerAIQ
{
	pub fn new(fix:bool) -> Box<PlayerAIQ>
	{
		Box::new(PlayerAIQ { initialized: false, fixed: fix, filename: String::new(), pid: 0,
				nn: None, targetnn: None, games_played: 0, lr: LR, exploration: RND_PICK_START,
				startp: 0.0, memstate: Vec::new(), memqval: Vec::new(), memreward: -1.0, memplay: 0 })
	}

	fn get_exploration(&self) -> f64
	{
		RND_PICK_MIN.max(RND_PICK_START * (2f64).powf(-(self.games_played as f64)/RND_PICK_DEC))
	}

	fn get_lr(&self) -> f64
	{
		LR_MIN.max(LR * (2f64).powf(-(self.games_played as f64)/LR_DECAY))
	}

	fn argmax(slice:&[f64]) -> u32
	{
		let mut x:u32 = 0;
		let mut max = slice[0];
		for i in 1..slice.len()
		{
			if max<slice[i]
			{
				x = i as u32;
				max = slice[i];
			}
		}
		x
	}

	fn field_to_input(field:&mut Field, p:i32, startp:f64) -> Vec<f64>
	{
		let op:i32 = if p == 1 { 2 } else { 1 }; //other player
		let mut input:Vec<f64> = Vec::with_capacity((2*field.get_size() + field.get_w() + 1) as usize);
		for (i, val) in field.get_field().iter().enumerate()
		{ //2 nodes for every square: -1 enemy, 0 free, 1 own; 0 square will not be reached with one move, 1 square can be directly filled
			if *val == p { input.push(1f64); input.push(0f64); }
			else if *val == op { input.push(-1f64); input.push(0f64); }
			else
			{ //empty square
				input.push(0f64);
				if (i as u32) < (field.get_size()-field.get_w()) { input.push(if field.get_field()[i+field.get_w() as usize] != 0 { 1f64 } else { 0f64 }); }
				else { input.push(1f64); }
			}
		}
		for x in 0..field.get_w()
		{ //1 node for every column: 1 own win, -1 enemy win, 0 none (which consistent order of the nodes does not matter, fully connected)
			if field.play(p, x)
			{ //valid play
				match field.get_state()
				{
					-1 | 0 => input.push(0f64),
					pid => input.push(if pid == p {1f64} else {-1f64}),
				}
				field.undo();
			}
			else { input.push(0f64); } //illegal move, nobody can win
		}
		//1 node for starting player (-1 enemy, 1 self)
		input.push(startp);
		//return
		input
	}
}

impl Player for PlayerAIQ
{
	fn init(&mut self, field:&Field, p:i32) -> bool
	{
		self.pid = p;

		self.filename = format!("AIQ-{}x{}.NN", field.get_w(), field.get_h());
		let file = File::open(&self.filename);
		if file.is_err()
		{
			//create new neural net, as it could not be loaded
			let n = field.get_size();
			let w = field.get_w();
			self.nn = Some(NN::new(&[2*n+w+1, 4*n, 2*n, n, n, n/2, w])); //set size of NN layers here
			//games_played, exploration, lr already set
		}
		else
		{
			//load neural net from file (and games played)
			let mut reader = BufReader::new(file.unwrap());
			let mut datas = String::new();
			let mut nns = String::new();

			let res1 = reader.read_line(&mut datas);
			let res2 = reader.read_to_string(&mut nns);
			if res1.is_err() || res2.is_err() { return false; }

			let res = datas.trim().parse::<u32>();
			if res.is_err() { return false; }
			self.games_played = res.unwrap();
			self.nn = Some(NN::from_json(&nns));

			self.lr = self.get_lr();
			self.exploration = self.get_exploration();
		}

		self.targetnn = self.nn.clone();
		self.initialized = true;
		true
	}

	fn startp(&mut self, p:i32)
	{
		if p == self.pid
		{
			self.startp = 1.0;
		}
		else
		{
			self.startp = -1.0;
		}
	}

	fn play(&mut self, field:&mut Field) -> bool
	{
		if !self.initialized { return false; }
		//variables
		let mut rng = rand::thread_rng();
		let nn = self.nn.as_mut().unwrap();
		let targetnn = self.targetnn.as_mut().unwrap();

		//get current state formatted for the neural net
		let state = PlayerAIQ::field_to_input(field, self.pid, self.startp);

		//learn if not first move (reward is already set, won/loose would be outcome)
		if self.memreward != -1.0
		{
			//get Q values for next state
			let qval2 = targetnn.run(&state); //use double q learning target nn, to decouple action and value a bit
			let max = qval2[PlayerAIQ::argmax(&qval2) as usize];
			//calculate q update
			self.memqval[self.memplay as usize] = (self.memreward + GAMMA * max) / (1.0 + GAMMA); //Q learning (divide to stay in [0,1] for sigmoid)
			//train on the latest experience (q update)
			nn.train(&[(self.memstate.clone(), self.memqval.clone())])
				.halt_condition(HaltCondition::Epochs(EPOCHS_PER_STEP))
				.log_interval(None)
				//.log_interval(Some(2)) //debug
				.momentum(MOM)
				.rate(self.lr)
				.go();
		}

		//choose action by e-greedy (no exploration when fixed AI version+)
		self.memqval = nn.run(&state);
		self.memplay = PlayerAIQ::argmax(&self.memqval);
		if !self.fixed && rng.gen::<f64>() < self.exploration //random exploration if agent should learn.
		{
			self.memplay = rng.gen::<u32>() % field.get_w();
		}

		//perform action and set reward
		self.memreward = 0.5;
		let mut res = field.play(self.pid, self.memplay);
		if !res { self.memqval[self.memplay as usize] = 0.1; } //move did not meet the rules, but learn anyway, even if another random move is made

		//random play when it was not rule conform, also modify q-value for it
		while !res //infinite if it is already draw! (cannot happen without bug)
		{
			self.memplay = rng.gen::<u32>() % field.get_w();
			res = field.play(self.pid, self.memplay);
		}

		//save state for next turn
		self.memstate = state;

		//return
		res
	}

	#[allow(unused_variables)]
	fn outcome(&mut self, field:&mut Field, state:i32)
	{
		{ //learn, scope for "let nn" shortcut
		let nn = self.nn.as_mut().unwrap();
		let op:i32 = if self.pid == 1 { 2 } else { 1 }; //other player
		//set reward (if draw, reward already set properly)
		if state == self.pid { self.memreward = 1.0; }
		else if state == op { self.memreward = 0.0; }
		//end-values of network should meet reward exactly
		self.memqval[self.memplay as usize] = self.memreward;
		//train NN
		nn.train(&[(self.memstate.clone(), self.memqval.clone())])
			.halt_condition(HaltCondition::Epochs(EPOCHS_PER_STEP))
			.log_interval(None)
			//.log_interval(Some(2)) //debug
			.momentum(MOM)
			.rate(self.lr)
			.go();
		}

		//parameters
		self.games_played += 1;
		self.lr = self.get_lr();
		self.exploration = self.get_exploration();
		self.targetnn = self.nn.clone();
		self.memreward = -1.0;
	}
}

impl Drop for PlayerAIQ
{
	fn drop(&mut self)
	{
		//write neural net to file, if it was allowed to learn and was initialized
		if self.initialized && !self.fixed
		{
			let file = File::create(&self.filename);
			if file.is_err() { println!("Warning: Could not write AIQ NN file!"); return; }
			let mut writer = BufWriter::new(file.unwrap());

			let res1 = writeln!(&mut writer, "{}", self.games_played);
			let res2 = write!(&mut writer, "{}", self.nn.as_mut().unwrap().to_json());
			if res1.is_err() || res2.is_err() { println!("Warning: There was an error while writing AIQ NN file!"); return; }
		}
	}
}
<|endoftext|>


<commit_before>use std::rc::Rc;

use gdk::{EventMotion, EventScroll};
use gtk::prelude::*;
use gtk::DrawingArea;

use sounding_analysis;
use sounding_base::{DataRow, Profile};

use app::{config, config::Rgba, AppContext, AppContextPointer};
use coords::{
    convert_pressure_to_y, convert_y_to_pressure, DeviceCoords, LPCoords, ScreenCoords, ScreenRect,
    XYCoords,
};
use errors::SondeError;
use gui::plot_context::{GenericContext, HasGenericContext, PlotContext, PlotContextExt};
use gui::utility::{check_overlap_then_add, plot_curve_from_points, DrawingArgs};
use gui::{Drawable, SlaveProfileDrawable};

const LR_RANGE: f64 = config::MAX_LAPSE_RATE - config::MIN_LAPSE_RATE;

#[derive(Debug)]
pub struct LapseRateContext {
    generic: GenericContext,
}

impl LapseRateContext {
    pub fn new() -> Self {
        LapseRateContext {
            generic: GenericContext::new(),
        }
    }

    pub fn convert_lp_to_xy(coords: LPCoords) -> XYCoords {
        let y = convert_pressure_to_y(coords.press);
        let x = (coords.lapse_rate - config::MIN_LAPSE_RATE) / LR_RANGE;
        XYCoords { x, y }
    }

    pub fn convert_xy_to_lp(coords: XYCoords) -> LPCoords {
        let press = convert_y_to_pressure(coords.y);
        let lapse_rate = coords.x * LR_RANGE + config::MIN_LAPSE_RATE;

        LPCoords { lapse_rate, press }
    }

    pub fn convert_lp_to_screen(&self, coords: LPCoords) -> ScreenCoords {
        let xy = Self::convert_lp_to_xy(coords);
        self.convert_xy_to_screen(xy)
    }

    pub fn convert_screen_to_lp(&self, coords: ScreenCoords) -> LPCoords {
        let xy = self.convert_screen_to_xy(coords);
        Self::convert_xy_to_lp(xy)
    }

    pub fn convert_device_to_lp(&self, coords: DeviceCoords) -> LPCoords {
        let xy = self.convert_device_to_xy(coords);
        Self::convert_xy_to_lp(xy)
    }
}

impl HasGenericContext for LapseRateContext {
    fn get_generic_context(&self) -> &GenericContext {
        &self.generic
    }
}

impl PlotContextExt for LapseRateContext {
    fn zoom_to_envelope(&self) {}

    fn bound_view(&self) {
        let device_rect = self.get_device_rect();

        let bounds = DeviceCoords {
            col: device_rect.width,
            row: device_rect.height,
        };
        let lower_right = self.convert_device_to_xy(bounds);
        let upper_left = self.convert_device_to_xy(device_rect.upper_left);
        let height = upper_left.y - lower_right.y;

        let mut translate = self.get_translate();
        if height < 1.0 {
            if translate.y < 0.0 {
                translate.y = 0.0;
            }
            let max_y = 1.0 - height;
            if translate.y > max_y {
                translate.y = max_y;
            }
        } else {
            translate.y = -(height - 1.0) / 2.0;
        }
        self.set_translate(translate);
    }

    fn convert_xy_to_screen(&self, coords: XYCoords) -> ScreenCoords {
        // Apply translation first
        let x = coords.x;
        let y = coords.y - self.get_translate().y;

        // Apply scaling
        let x = x;
        let y = y * self.get_zoom_factor();

        ScreenCoords { x, y }
    }

    fn convert_screen_to_xy(&self, coords: ScreenCoords) -> XYCoords {
        // Unapply scaling first
        let x = coords.x;
        let y = coords.y / self.get_zoom_factor();

        // Unapply translation
        let x = x;
        let y = y + self.get_translate().y;

        XYCoords { x, y }
    }
}

impl Drawable for LapseRateContext {
    /***********************************************************************************************
     * Initialization
     **********************************************************************************************/
    fn set_up_drawing_area(acp: &AppContextPointer) -> Result<(), SondeError> {
        let da: DrawingArea = acp.fetch_widget("lapse_rate_area")?;

        let ac = Rc::clone(acp);
        da.connect_draw(move |_da, cr| ac.lapse_rate.draw_callback(cr, &ac));

        let ac = Rc::clone(acp);
        da.connect_motion_notify_event(move |da, ev| ac.lapse_rate.mouse_motion_event(da, ev, &ac));

        let ac = Rc::clone(acp);
        da.connect_leave_notify_event(move |_da, _ev| ac.lapse_rate.leave_event(&ac));

        let ac = Rc::clone(acp);
        da.connect_key_press_event(move |_da, ev| LapseRateContext::key_press_event(ev, &ac));

        let ac = Rc::clone(acp);
        da.connect_configure_event(move |_da, ev| ac.lapse_rate.configure_event(ev));

        let ac = Rc::clone(acp);
        da.connect_size_allocate(move |da, _ev| ac.lapse_rate.size_allocate_event(da));

        Ok(())
    }

    /***********************************************************************************************
     * Background Drawing.
     **********************************************************************************************/
    fn draw_background_fill(&self, args: DrawingArgs) {
        let (cr, config) = (args.cr, args.ac.config.borrow());

        if config.show_background_bands {
            let rgba = config.background_band_rgba;
            cr.set_source_rgba(rgba.0, rgba.1, rgba.2, rgba.3);

            let mut lines = config::PROFILE_LAPSE_RATE_PNTS.iter();
            let mut draw = true;
            let mut prev = lines.next();
            while let Some(prev_val) = prev {
                let curr = lines.next();
                if let Some(curr_val) = curr {
                    if draw {
                        let ll = self.convert_xy_to_screen(prev_val[0]);
                        let ur = self.convert_xy_to_screen(curr_val[1]);
                        let ScreenCoords { x: xmin, y: ymin } = ll;
                        let ScreenCoords { x: xmax, y: ymax } = ur;
                        cr.rectangle(xmin, ymin, xmax - xmin, ymax - ymin);
                        cr.fill();
                        draw = false;
                    } else {
                        draw = true;
                    }
                }
                prev = curr;
            }
        }

        self.draw_hail_growth_zone(args);
    }

    fn draw_background_lines(&self, args: DrawingArgs) {
        let (cr, config) = (args.cr, args.ac.config.borrow());

        if config.show_isobars {
            for pnts in config::ISOBAR_PNTS.iter() {
                let pnts = pnts.iter()
                    .map(|xy_coords| self.convert_xy_to_screen(*xy_coords));
                plot_curve_from_points(cr, config.background_line_width, config.isobar_rgba, pnts);
            }
        }

        for line in config::PROFILE_LAPSE_RATE_PNTS.iter() {
            let pnts = line.iter()
                .map(|xy_coord| self.convert_xy_to_screen(*xy_coord));
            plot_curve_from_points(cr, config.background_line_width, config.isobar_rgba, pnts);
        }
    }

    fn build_legend_strings(ac: &AppContext) -> Vec<(String, Rgba)> {
        let config = ac.config.borrow();

        let mut to_return = vec![];

        if config.show_lapse_rate_profile {
            to_return.push(("Lapse rate".to_owned(), config.lapse_rate_profile_rgba));
        }

        if config.show_sfc_avg_lapse_rate_profile {
            to_return.push((
                "Sfc to * lapse rate".to_owned(),
                config.sfc_avg_lapse_rate_profile_rgba,
            ));
        }

        to_return
    }

    fn collect_labels(&self, args: DrawingArgs) -> Vec<(String, ScreenRect)> {
        let (ac, cr) = (args.ac, args.cr);

        let mut labels = vec![];

        let screen_edges = self.calculate_plot_edges(cr, ac);
        let ScreenRect { lower_left, .. } = screen_edges;

        let LPCoords {
            press: screen_max_p,
            ..
        } = self.convert_screen_to_lp(lower_left);

        for lapse_rate in &config::PROFILE_LAPSE_RATES {
            let label = format!("{:.1}", *lapse_rate);

            let extents = cr.text_extents(&label);

            let ScreenCoords {
                x: mut xpos,
                y: mut ypos,
            } = self.convert_lp_to_screen(LPCoords {
                lapse_rate: *lapse_rate,
                press: screen_max_p,
            });
            xpos -= extents.width / 2.0; // Center
            ypos -= extents.height / 2.0; // Center
            ypos += extents.height; // Move up off bottom axis.

            let ScreenRect {
                lower_left: ScreenCoords { x: xmin, .. },
                upper_right: ScreenCoords { x: xmax, .. },
            } = screen_edges;

            if xpos < xmin || xpos + extents.width > xmax {
                continue;
            }

            let label_lower_left = ScreenCoords { x: xpos, y: ypos };
            let label_upper_right = ScreenCoords {
                x: xpos + extents.width,
                y: ypos + extents.height,
            };

            let pair = (
                label,
                ScreenRect {
                    lower_left: label_lower_left,
                    upper_right: label_upper_right,
                },
            );
            check_overlap_then_add(cr, ac, &mut labels, &screen_edges, pair);
        }

        labels
    }

    /***********************************************************************************************
     * Data Drawing.
     **********************************************************************************************/
    fn draw_data(&self, args: DrawingArgs) {
        let ac = args.ac;

        let mut some_data = draw_lapse_rate_profile(args);
        some_data = draw_sfc_avg_lapse_rate_profile(args) || some_data;

        if some_data {
            ac.lapse_rate.set_has_data(true);
        } else {
            ac.lapse_rate.set_has_data(false);
            ac.lapse_rate.draw_no_data(args);
        }
    }

    /***********************************************************************************************
     * Overlays Drawing.
     **********************************************************************************************/
    fn create_active_readout_text(vals: &DataRow, ac: &AppContext) -> Vec<(String, Rgba)> {
        let config = ac.config.borrow();

        let mut results = vec![];

        if config.show_lapse_rate_profile || config.show_sfc_avg_lapse_rate_profile {
            if let (Some(anal), Some(other_profiles), Some(tgt_pres)) = (
                ac.get_sounding_for_display(),
                ac.get_extra_profiles_for_display(),
                vals.pressure.into(),
            ) {
                let pres = anal.sounding().get_profile(Profile::Pressure);

                if config.show_lapse_rate_profile {
                    let lapse_rate = &other_profiles.lapse_rate;

                    if let Some(lr) = Into::<Option<f64>>::into(
                        sounding_analysis::linear_interpolate(pres, lapse_rate, tgt_pres),
                    ) {
                        results.push((
                            format!("{:.1}\u{00b0}C/km\n", lr),
                            config.lapse_rate_profile_rgba,
                        ));
                    }
                }

                if config.show_sfc_avg_lapse_rate_profile {
                    let lapse_rate = &other_profiles.sfc_avg_lapse_rate;

                    if let Some(lr) = Into::<Option<f64>>::into(
                        sounding_analysis::linear_interpolate(pres, lapse_rate, tgt_pres),
                    ) {
                        results.push((
                            format!("{:.1}\u{00b0}C/km\n", lr),
                            config.sfc_avg_lapse_rate_profile_rgba,
                        ));
                    }
                }
            }
        }

        results
    }

    /***********************************************************************************************
     * Events
     **********************************************************************************************/
    fn scroll_event(&self, _event: &EventScroll, _ac: &AppContextPointer) -> Inhibit {
        Inhibit(false)
    }

    fn mouse_motion_event(
        &self,
        da: &DrawingArea,
        event: &EventMotion,
        ac: &AppContextPointer,
    ) -> Inhibit {
        da.grab_focus();

        if ac.plottable() && self.has_data() {
            let position: DeviceCoords = event.get_position().into();

            self.set_last_cursor_position(Some(position));
            let sp_position = self.convert_device_to_lp(position);
            let sample = ::sounding_analysis::linear_interpolate_sounding(
                // ac.plottable() call ensures unwrap below panic
                &ac.get_sounding_for_display().unwrap().sounding(),
                sp_position.press,
            );
            ac.set_sample(sample.ok());
            ac.mark_overlay_dirty();
            ac.update_all_gui();
        }
        Inhibit(false)
    }
}

impl SlaveProfileDrawable for LapseRateContext {
    fn get_master_zoom(&self, acp: &AppContextPointer) -> f64 {
        acp.skew_t.get_zoom_factor()
    }

    fn set_translate_y(&self, new_translate: XYCoords) {
        let mut translate = self.get_translate();
        translate.y = new_translate.y;
        self.set_translate(translate);
    }
}

fn draw_lapse_rate_profile(args: DrawingArgs) -> bool {
    let (ac, cr) = (args.ac, args.cr);
    let config = ac.config.borrow();

    if !config.show_lapse_rate_profile {
        return false;
    }

    if let (Some(anal), Some(profiles)) = (
        ac.get_sounding_for_display(),
        ac.get_extra_profiles_for_display(),
    ) {
        use sounding_base::Profile::Pressure;

        let sndg = anal.sounding();

        let pres_data = sndg.get_profile(Pressure);
        let lr_data = &profiles.lapse_rate;
        let mut profile = izip!(pres_data, lr_data)
            .filter_map(|(p, lr)| {
                if let (Some(p), Some(lr)) = (p.into(), lr.into()) {
                    Some((p, lr))
                } else {
                    None
                }
            })
            .take_while(|&(press, _)| press >= config::MINP)
            .filter_map(|(press, lapse_rate)| {
                Some(
                    ac.lapse_rate
                        .convert_lp_to_screen(LPCoords { lapse_rate, press }),
                )
            });

        plot_curve_from_points(
            cr,
            config.profile_line_width,
            config.lapse_rate_profile_rgba,
            profile,
        );

        true
    } else {
        false
    }
}

fn draw_sfc_avg_lapse_rate_profile(args: DrawingArgs) -> bool {
    let (ac, cr) = (args.ac, args.cr);
    let config = ac.config.borrow();

    if !config.show_sfc_avg_lapse_rate_profile {
        return false;
    }

    if let (Some(anal), Some(profiles)) = (
        ac.get_sounding_for_display(),
        ac.get_extra_profiles_for_display(),
    ) {
        use sounding_base::Profile::Pressure;

        let sndg = anal.sounding();

        let pres_data = sndg.get_profile(Pressure);
        let lr_data = &profiles.sfc_avg_lapse_rate;
        let mut profile = izip!(pres_data, lr_data)
            .filter_map(|(p, lr)| {
                if let (Some(p), Some(s)) = (p.into(), lr.into()) {
                    Some((p, s))
                } else {
                    None
                }
            })
            .take_while(|&(press, _)| press >= config::MINP)
            .filter_map(|(press, lapse_rate)| {
                Some(
                    ac.lapse_rate
                        .convert_lp_to_screen(LPCoords { lapse_rate, press }),
                )
            });

        plot_curve_from_points(
            cr,
            config.profile_line_width,
            config.sfc_avg_lapse_rate_profile_rgba,
            profile,
        );

        true
    } else {
        false
    }
}
<commit_msg>Removed lapse rate profiles.<commit_after><|endoftext|>


<commit_before>use engine::Game;
use gui::screens::{self, Screen, ScreenChange};
use gui::{Console, Colors, Key};
use gui::chars;
use util::Rectangle;
use util::units::{Direction, Point, Size};

#[allow(missing_copy_implementations)]
pub struct GameScreen {
    map_frame: Rectangle,
    map_view: Point,
    info_frame: Rectangle,
    message_frame: Rectangle,
}

impl GameScreen {
    pub fn new() -> Box<Screen> {
        Box::new(
            GameScreen {
                map_frame: Rectangle::new(Point::new(16, 1), Size::new(63, 38)),
                map_view: Point::new(0, 0),
                info_frame: Rectangle::new(Point::new(1, 1), Size::new(13, 48)),
                message_frame: Rectangle::new(Point::new(16, 41), Size::new(63, 8)),
            }
        )
    }
}

impl Screen for GameScreen {

    #[allow(unused)]
    fn input(&mut self, game: &mut Game, console: &mut Console) -> Option<ScreenChange> {
        if let Some(key) = console.check_for_keypress() {
            match key {
                Key::Up => {
                    game.world.walk(Direction::Up);
                },
                Key::Down => {
                    game.world.walk(Direction::Down);
                },
                Key::Left => {
                    game.world.walk(Direction::Left);
                },
                Key::Right => {
                    game.world.walk(Direction::Right);
                },
                Key::Escape => {
                    return Some(ScreenChange::AddScreen(screens::PauseScreen::new()));
                },
                _ => {}
            }
        }

        None
    }

    #[allow(unused)]
    fn update(&mut self, game: &mut Game, console: &mut Console) -> Option<ScreenChange> {
        None
    }

    #[allow(unused)]
    fn render(&mut self, game: &mut Game, console: &mut Console) {
        self.draw_borders(game, console);
        self.draw_map(game, console);
        self.draw_player(game, console);
    }
}

impl GameScreen {

    #[allow(unused)]
    fn draw_borders(&self, game: &mut Game, console: &mut Console) {
        self.draw_box(console, self.map_frame.move_dir(Point::new(-1, -1)).resize(Size::new(1, 1)));
        console.print_plain(self.map_frame.move_dir(Point::new(0, -1)).location(), "Map");

        self.draw_box(console, self.info_frame.move_dir(Point::new(-1, -1)).resize(Size::new(1, 1)));
        console.print_plain(self.info_frame.move_dir(Point::new(0, -1)).location(), "Info");

        self.draw_box(console, self.message_frame.move_dir(Point::new(-1, -1)).resize(Size::new(1, 1)));
        console.print_plain(self.message_frame.move_dir(Point::new(0, -1)).location(), "Messages");
    }

    fn draw_box(&self, console: &mut Console, rect: Rectangle) {
        console.put_plain(rect.location() + Point::new(0, 0), chars::NW);
        console.put_plain(rect.location() + Point::new(rect.width(), 0), chars::NE);
        console.put_plain(rect.location() + Point::new(0, rect.height()), chars::SW);
        console.put_plain(rect.location() + Point::new(rect.width(), rect.height()), chars::SE);

        for x in 1..rect.width() {
            console.put_plain(rect.location() + Point::new(x, 0), chars::HLINE);
            console.put_plain(rect.location() + Point::new(x, rect.height()), chars::HLINE);
        }

        for y in 1..rect.height() {
            console.put_plain(rect.location() + Point::new(0, y), chars::VLINE);
            console.put_plain(rect.location() + Point::new(rect.width(), y), chars::VLINE);
        }
    }

    #[allow(unused)]
    fn draw_map(&self, game: &mut Game, console: &mut Console) {
        let map = &game.world.map;

        let (width, height) = (63, 38);

        for (y, line) in map.tiles[self.map_view.y as usize .. self.map_view.y as usize + height].iter().enumerate() {
            for (x, cell) in line[self.map_view.x as usize .. self.map_view.x as usize + width].iter().enumerate() {
                console.put(self.map_frame.location() + Point::new(x as i32, y as i32), ' ', Colors::white, cell.b_color());
            }
        }
    }

    #[allow(unused)]
    fn draw_player(&mut self, game: &mut Game, console: &mut Console) {
        let repr = game.world.player.repr;
        let pos = game.world.player.pos;

        let adjusted_pos = pos + self.map_frame.location() - self.map_view;

        if adjusted_pos.x >= self.map_frame.location().x + self.map_frame.width() - 10 {
            if self.view_can_move_right(game) {
                self.map_view = self.map_view.right(1);
            }
        }

        if adjusted_pos.y >= self.map_frame.location().y + self.map_frame.height() - 5 {
            if self.view_can_move_down(game) {
                self.map_view = self.map_view.down(1);
            }
        }

        if adjusted_pos.y <= self.map_frame.location().y + 5 {
            if self.view_can_move_up(game) {
                self.map_view = self.map_view.up(1);
            }
        }

        if adjusted_pos.x <= self.map_frame.location().x + 10 {
            if self.view_can_move_left(game) {
                self.map_view = self.map_view.left(1);
            }
        }

        if adjusted_pos.x >= self.map_frame.location().x && adjusted_pos.y >= self.map_frame.location().y {
            console.put_plain(self.map_frame.location() - self.map_view + pos, repr);
        }
    }

    fn view_can_move_up(&self, game: &Game) -> bool {
        self.map_view.y  > 0
    }

    fn view_can_move_down(&self, game: &Game) -> bool {
        self.map_view.y + self.map_frame.height() < game.world.map.height()
    }

    fn view_can_move_left(&self, game: &Game) -> bool {
        self.map_view.x > 0
    }

    fn view_can_move_right(&self, game: &Game) -> bool {
        self.map_view.x + self.map_frame.width() < game.world.map.width()
    }

}
<commit_msg>Add some annotatations to allow unused variables<commit_after>use engine::Game;
use gui::screens::{self, Screen, ScreenChange};
use gui::{Console, Colors, Key};
use gui::chars;
use util::Rectangle;
use util::units::{Direction, Point, Size};

#[allow(missing_copy_implementations)]
pub struct GameScreen {
    map_frame: Rectangle,
    map_view: Point,
    info_frame: Rectangle,
    message_frame: Rectangle,
}

impl GameScreen {
    pub fn new() -> Box<Screen> {
        Box::new(
            GameScreen {
                map_frame: Rectangle::new(Point::new(16, 1), Size::new(63, 38)),
                map_view: Point::new(0, 0),
                info_frame: Rectangle::new(Point::new(1, 1), Size::new(13, 48)),
                message_frame: Rectangle::new(Point::new(16, 41), Size::new(63, 8)),
            }
        )
    }
}

impl Screen for GameScreen {

    #[allow(unused)]
    fn input(&mut self, game: &mut Game, console: &mut Console) -> Option<ScreenChange> {
        if let Some(key) = console.check_for_keypress() {
            match key {
                Key::Up => {
                    game.world.walk(Direction::Up);
                },
                Key::Down => {
                    game.world.walk(Direction::Down);
                },
                Key::Left => {
                    game.world.walk(Direction::Left);
                },
                Key::Right => {
                    game.world.walk(Direction::Right);
                },
                Key::Escape => {
                    return Some(ScreenChange::AddScreen(screens::PauseScreen::new()));
                },
                _ => {}
            }
        }

        None
    }

    #[allow(unused)]
    fn update(&mut self, game: &mut Game, console: &mut Console) -> Option<ScreenChange> {
        None
    }

    #[allow(unused)]
    fn render(&mut self, game: &mut Game, console: &mut Console) {
        self.draw_borders(game, console);
        self.draw_map(game, console);
        self.draw_player(game, console);
    }
}

impl GameScreen {

    #[allow(unused)]
    fn draw_borders(&self, game: &mut Game, console: &mut Console) {
        self.draw_box(console, self.map_frame.move_dir(Point::new(-1, -1)).resize(Size::new(1, 1)));
        console.print_plain(self.map_frame.move_dir(Point::new(0, -1)).location(), "Map");

        self.draw_box(console, self.info_frame.move_dir(Point::new(-1, -1)).resize(Size::new(1, 1)));
        console.print_plain(self.info_frame.move_dir(Point::new(0, -1)).location(), "Info");

        self.draw_box(console, self.message_frame.move_dir(Point::new(-1, -1)).resize(Size::new(1, 1)));
        console.print_plain(self.message_frame.move_dir(Point::new(0, -1)).location(), "Messages");
    }

    fn draw_box(&self, console: &mut Console, rect: Rectangle) {
        console.put_plain(rect.location() + Point::new(0, 0), chars::NW);
        console.put_plain(rect.location() + Point::new(rect.width(), 0), chars::NE);
        console.put_plain(rect.location() + Point::new(0, rect.height()), chars::SW);
        console.put_plain(rect.location() + Point::new(rect.width(), rect.height()), chars::SE);

        for x in 1..rect.width() {
            console.put_plain(rect.location() + Point::new(x, 0), chars::HLINE);
            console.put_plain(rect.location() + Point::new(x, rect.height()), chars::HLINE);
        }

        for y in 1..rect.height() {
            console.put_plain(rect.location() + Point::new(0, y), chars::VLINE);
            console.put_plain(rect.location() + Point::new(rect.width(), y), chars::VLINE);
        }
    }

    #[allow(unused)]
    fn draw_map(&self, game: &mut Game, console: &mut Console) {
        let map = &game.world.map;

        let (width, height) = (63, 38);

        for (y, line) in map.tiles[self.map_view.y as usize .. self.map_view.y as usize + height].iter().enumerate() {
            for (x, cell) in line[self.map_view.x as usize .. self.map_view.x as usize + width].iter().enumerate() {
                console.put(self.map_frame.location() + Point::new(x as i32, y as i32), ' ', Colors::white, cell.b_color());
            }
        }
    }

    #[allow(unused)]
    fn draw_player(&mut self, game: &mut Game, console: &mut Console) {
        let repr = game.world.player.repr;
        let pos = game.world.player.pos;

        let adjusted_pos = pos + self.map_frame.location() - self.map_view;

        if adjusted_pos.x >= self.map_frame.location().x + self.map_frame.width() - 10 {
            if self.view_can_move_right(game) {
                self.map_view = self.map_view.right(1);
            }
        }

        if adjusted_pos.y >= self.map_frame.location().y + self.map_frame.height() - 5 {
            if self.view_can_move_down(game) {
                self.map_view = self.map_view.down(1);
            }
        }

        if adjusted_pos.y <= self.map_frame.location().y + 5 {
            if self.view_can_move_up(game) {
                self.map_view = self.map_view.up(1);
            }
        }

        if adjusted_pos.x <= self.map_frame.location().x + 10 {
            if self.view_can_move_left(game) {
                self.map_view = self.map_view.left(1);
            }
        }

        if adjusted_pos.x >= self.map_frame.location().x && adjusted_pos.y >= self.map_frame.location().y {
            console.put_plain(self.map_frame.location() - self.map_view + pos, repr);
        }
    }

    #[allow(unused)]
    fn view_can_move_up(&self, game: &Game) -> bool {
        self.map_view.y  > 0
    }

    #[allow(unused)]
    fn view_can_move_down(&self, game: &Game) -> bool {
        self.map_view.y + self.map_frame.height() < game.world.map.height()
    }

    #[allow(unused)]
    fn view_can_move_left(&self, game: &Game) -> bool {
        self.map_view.x > 0
    }

    #[allow(unused)]
    fn view_can_move_right(&self, game: &Game) -> bool {
        self.map_view.x + self.map_frame.width() < game.world.map.width()
    }

}
<|endoftext|>


<commit_before>use super::ast::*;

pub trait Visitor<'v> : Sized {
    fn visit_module(&mut self, module: &'v Module) {
        walk_module(self, module)
    }

    fn visit_item(&mut self, item: &'v Item) {
        walk_item(self, item)
    }

    fn visit_data(&mut self, inductive: &'v Inductive) {
        walk_inductive(self, inductive)
    }

    fn visit_extern(&mut self, ext: &'v Extern) {
        walk_extern(self, ext)
    }

    fn visit_def(&mut self, def: &'v Def) {
        walk_def(self, def)
    }

    fn visit_term(&mut self, term: &'v Term) {
        walk_term(self, term)
    }

    fn visit_binder(&mut self, binder: &'v Binder) {
        walk_binder(self, binder)
    }

    fn visit_span(&mut self, _span: Span) {}

    fn visit_case(&mut self, case: &'v Case) {
        panic!();
    }

    fn visit_pattern(&mut self, pattern: &'v Pattern) {
        panic!();
    }

    fn visit_literal(&mut self, lit: &'v Literal) {
        panic!();
    }

    fn visit_name(&mut self, name: &'v Name) {
        walk_name(self, name)
    }
}

pub fn walk_module<'v, V: Visitor<'v>>(visitor: &mut V, module: &'v Module) {
    visitor.visit_span(module.span);
    visitor.visit_name(&module.name);

    for item in &module.items {
        visitor.visit_item(item);
    }
}

pub fn walk_item<'v, V: Visitor<'v>>(visitor: &mut V, item: &'v Item) {
    use ast::Item::*;

    match item {
        &Item::Inductive(ref d) => visitor.visit_data(d),
        &Item::Def(ref def) => visitor.visit_def(def),
        &Item::Extern(ref ext) => visitor.visit_extern(ext),
        &Item::Comment(ref s) => panic!(),
        &Item::Import(ref n) => visitor.visit_name(n),
    }
}

pub fn walk_inductive<'v, V: Visitor<'v>>(visitor: &mut V, inductive: &'v Inductive) {
    visitor.visit_span(inductive.span);
    visitor.visit_name(&inductive.name);

    for binder in &inductive.parameters {
        visitor.visit_binder(binder);
    }

    visitor.visit_term(&inductive.ty);

    for &(ref n, ref t) in &inductive.ctors {
        visitor.visit_name(n);
        visitor.visit_term(t);
    }
}

pub fn walk_def<'v, V: Visitor<'v>>(visitor: &mut V, def: &'v Def) {
    visitor.visit_span(def.span);
    visitor.visit_name(&def.name);

    for binder in &def.args {
        visitor.visit_binder(binder);
    }

    visitor.visit_term(&def.ty);
    visitor.visit_term(&def.body);
}

pub fn walk_extern<'v, V: Visitor<'v>>(visitor: &mut V, ext: &'v Extern) {
    visitor.visit_span(ext.span);
    visitor.visit_name(&ext.name);
    visitor.visit_term(&ext.term);
}

pub fn walk_term<'v, V: Visitor<'v>>(visitor: &mut V, term: &'v Term) {
    use ast::Term::*;

    match term {
        &Literal { ref span, ref lit } =>
            panic!(),
        &Var { ref name } =>
            visitor.visit_name(name),
        &Match { ref span, ref scrutinee, ref cases } =>
            panic!(),
        &App { span, ref fun, ref arg } => {
            visitor.visit_span(span);
            visitor.visit_term(fun);
            visitor.visit_term(arg);
        }
        &Forall { span, ref binders, ref term } => {
            visitor.visit_span(span);
            for binder in binders {
                visitor.visit_binder(binder);
            }

            visitor.visit_term(term);
        }
        &Lambda { span, ref args, ref ret_ty, ref body } => {
            visitor.visit_span(span);
            for binder in args {
                visitor.visit_binder(binder);
            }

            match **ret_ty {
                None => {}
                Some(ref rt) =>
                    visitor.visit_term(rt),
            }

            visitor.visit_term(body);
        }
        &Let { span, ref bindings, ref body } => {
            visitor.visit_span(span);
            for &(ref binding, ref body) in bindings {
                visitor.visit_binder(binding);
                visitor.visit_term(body);
            }
            visitor.visit_term(body);
        }
        &Type => {}
    }
}

pub fn walk_name<'v, V: Visitor<'v>>(visitor: &mut V, name: &'v Name) {
    visitor.visit_span(name.span);
}

pub fn walk_binder<'v, V: Visitor<'v>>(visitor: &mut V, binder: &'v Binder) {
    visitor.visit_span(binder.span);
    visitor.visit_name(&binder.name);
    visitor.visit_term(&binder.ty);
}
<commit_msg>Experimental visitor approach<commit_after>use super::ast::*;

pub enum Mut {}
pub enum Imm {}

trait MutType<'v, T> {
    type Output;
}

impl<'v, T: 'v> MutType<'v, T> for Mut {
    type Output = &'v mut T;
}

impl<'v, T: 'v> MutType<'v, T> for Imm {
    type Output = &'v T;
}

pub trait Ref<'v, M: MutType<'v, Self>> : Sized {
    type T;
}

impl<'v, T: 'v, M: MutType<'v, T>> Ref<'v, M> for T {
    type T = M::Output;
}

// fn as_ref<'v, M, U: Ref<'v, M>>(x: U) -> U::T {
//     panic!()
// }

pub trait Visitor<'v, M> : Sized {
    fn visit_module(&mut self, module: <Module as Ref<'v, M>>::T)
    where M : MutType<'v, Module> {
        walk_module(self, module)
    }

    fn visit_item(&mut self, item: <Item as Ref<'v, M>>::T)
    where M : MutType<'v, Item> {
        walk_item(self, item)
    }

    fn visit_data(&mut self, inductive: <Inductive as Ref<'v, M>>::T)
    where M : MutType<'v, Inductive> {
        walk_inductive(self, inductive)
    }

    fn visit_extern(&mut self, ext: <Extern as Ref<'v, M>>::T)
    where M : MutType<'v, Extern> {
        walk_extern(self, ext)
    }

    fn visit_def(&mut self, def: <Def as Ref<'v, M>>::T)
    where M : MutType<'v, Def> {
        walk_def(self, def)
    }

    fn visit_term(&mut self, term: <Term as Ref<'v, M>>::T)
    where M : MutType<'v, Term> {
        walk_term(self, term)
    }

    fn visit_binder(&mut self, binder: <Binder as Ref<'v, M>>::T)
    where M : MutType<'v, Binder> {
        walk_binder(self, binder)
    }

    fn visit_span(&mut self, span: <Span as Ref<'v, M>>::T)
    where M : MutType<'v, Span> {}

    fn visit_case(&mut self, _case: <Case as Ref<'v, M>>::T)
    where M : MutType<'v, Case> {
        panic!();
    }

    fn visit_pattern(&mut self, _pattern: <Pattern as Ref<'v, M>>::T)
    where M : MutType<'v, Pattern> {
        panic!();
    }

    fn visit_literal(&mut self, _lit: <Literal as Ref<'v, M>>::T)
    where M : MutType<'v, Literal> {
        panic!();
    }

    fn visit_name(&mut self, name: <Name as Ref<'v, M>>::T)
    where M : MutType<'v, Name> {
        walk_name(self, name)
    }
}

pub fn walk_module<'v, M: MutType<'v, Module>, V: Visitor<'v, M>>(
    visitor: &mut V,
    module: <Module as Ref<'v, M>>::T) {

    visitor.visit_span(module.span);
    visitor.visit_name(&module.name);

    for item in &module.items {
        visitor.visit_item(item);
    }
}

pub fn walk_item<'v, M: MutType<'v, Item>, V: Visitor<'v, M>>(
    visitor: &mut V,
    item: <Item as Ref<'v, M>>::T) {
    use ast::Item::*;

    match item {
        &Item::Inductive(ref d) => visitor.visit_data(d),
        &Item::Def(ref def) => visitor.visit_def(def),
        &Item::Extern(ref ext) => visitor.visit_extern(ext),
        &Item::Comment(ref s) => panic!(),
        &Item::Import(ref n) => visitor.visit_name(n),
    }
}

pub fn walk_inductive<'v, M: MutType<'v, Inductive>, V: Visitor<'v, M>>(
    visitor: &mut V,
    inductive: <Inductive as Ref<'v, M>>::T) {
    visitor.visit_span(inductive.span);
    visitor.visit_name(&inductive.name);

    for binder in &inductive.parameters {
        visitor.visit_binder(binder);
    }

    visitor.visit_term(&inductive.ty);

    for &(ref n, ref t) in &inductive.ctors {
        visitor.visit_name(n);
        visitor.visit_term(t);
    }
}

pub fn walk_def<'v, M: MutType<'v, Def>, V: Visitor<'v, M>>(visitor: &mut V, def: <Def as Ref<'v, M>>::T) {
    visitor.visit_span(def.span);
    visitor.visit_name(&def.name);

    for binder in &def.args {
        visitor.visit_binder(binder);
    }

    visitor.visit_term(&def.ty);
    visitor.visit_term(&def.body);
}

pub fn walk_extern<'v, M: MutType<'v, Extern>, V: Visitor<'v, M>>(
        visitor: &mut V,
        ext: <Extern as Ref<'v, M>>::T) {
    visitor.visit_span(ext.span);
    visitor.visit_name(&ext.name);
    visitor.visit_term(&ext.term);
}

pub fn walk_term<'v, M: MutType<'v, Term>, V: Visitor<'v, M>>(
        visitor: &mut V,
        term: <Term as Ref<'v, M>>::T) {
    use ast::Term::*;

    match term {
        &Literal { ref span, ref lit } =>
            panic!(),
        &Var { ref name } =>
            visitor.visit_name(name),
        &Match { ref span, ref scrutinee, ref cases } =>
            panic!(),
        &App { span, ref fun, ref arg } => {
            visitor.visit_span(span);
            visitor.visit_term(fun);
            visitor.visit_term(arg);
        }
        &Forall { span, ref binders, ref term } => {
            visitor.visit_span(span);
            for binder in binders {
                visitor.visit_binder(binder);
            }

            visitor.visit_term(term);
        }
        &Lambda { span, ref args, ref ret_ty, ref body } => {
            visitor.visit_span(span);
            for binder in args {
                visitor.visit_binder(binder);
            }

            match **ret_ty {
                None => {}
                Some(ref rt) =>
                    visitor.visit_term(rt),
            }

            visitor.visit_term(body);
        }
        &Let { span, ref bindings, ref body } => {
            visitor.visit_span(span);
            for &(ref binding, ref body) in bindings {
                visitor.visit_binder(binding);
                visitor.visit_term(body);
            }
            visitor.visit_term(body);
        }
        &Type => {}
    }
}

pub fn walk_name<'v, M: MutType<'v, Name>, V: Visitor<'v, M>>(visitor: &mut V, name: <Name as Ref<'v, M>>::T) {
    visitor.visit_span(name.span);
}

pub fn walk_binder<'v, M: MutType<'v, Binder>, V: Visitor<'v, M>>(
    visitor: &mut V,
    binder: <Binder as Ref<'v, M>>::T) {
    visitor.visit_span(binder.span);
    visitor.visit_name(&binder.name);
    visitor.visit_term(&binder.ty);
}
<|endoftext|>