<commit_before>use fixedbitset::FixedBitSet;

#[derive(Debug)]
pub struct Coverage {
    n: usize,
    rows: FixedBitSet,
    cols: FixedBitSet,
}

impl Coverage {
    pub fn n(&self) -> usize { self.n }

    pub fn new(n: usize) -> Coverage {
        Coverage {
            n: n,
            rows: FixedBitSet::with_capacity(n),
            cols: FixedBitSet::with_capacity(n),
        }
    }

    #[inline]
    /// find a single uncovered (row, col) pair. Iterates in col, row order.
    pub fn find_uncovered_col_row<F>(&self, mut f: F) -> Option<(usize, usize)>
        where F: FnMut((usize, usize)) -> bool {

        let n = self.n();

        for col in 0..n {
            if self.is_col_covered(col) {
                continue;
            }
            for row in 0..n {
                if self.is_row_covered(row) {
                    continue;
                }

                let pos = (row, col);
                if f(pos) {
                    return Some(pos);
                }
            }
        }

        return None;
    }

    #[inline]
    /// iterates over all uncovered (row, col) pairs in row, col order
    pub fn iter_uncovered_row_col<F>(&self, mut f: F)
        where F: FnMut((usize, usize)) {
        let n = self.n();

        for row in 0..n {
            if self.is_row_covered(row) {
                continue;
            }
            for col in 0..n {
                if self.is_col_covered(col) {
                    continue;
                }

                f((row, col));
            }
        }
    }

    #[inline]
    pub fn is_row_covered(&self, row: usize) -> bool {
        self.rows.contains(row)
    }

    #[inline]
    pub fn is_col_covered(&self, col: usize) -> bool {
        self.cols.contains(col)
    }

    #[inline]
    pub fn cover(&mut self, pos: (usize, usize)) {
        match pos {
            (row, col) => {
                self.cover_row(row);
                self.cover_col(col);
            }
        }
    }

    #[inline]
    pub fn cover_col(&mut self, col: usize) {
        self.cols.set(col, true);
    }

    #[inline]
    pub fn uncover_col(&mut self, col: usize) {
        self.cols.set(col, false);
    }

    #[inline]
    pub fn cover_row(&mut self, row: usize) {
        self.rows.set(row, true);
    }

    pub fn clear(&mut self) {
        self.rows.clear();
        self.cols.clear();
    }
}
<commit_msg>Operating directly on bit-representation is not much faster.<commit_after>use fixedbitset::FixedBitSet;

#[derive(Debug)]
pub struct Coverage {
    n: usize,
    rows: FixedBitSet,
    cols: FixedBitSet,
}

impl Coverage {
    pub fn n(&self) -> usize { self.n }

    pub fn new(n: usize) -> Coverage {
        Coverage {
            n: n,
            rows: FixedBitSet::with_capacity(n),
            cols: FixedBitSet::with_capacity(n),
        }
    }

    #[inline]
    /// find a single uncovered (row, col) pair. Iterates in col, row order.
    pub fn find_uncovered_col_row<F>(&self, mut f: F) -> Option<(usize, usize)>
        where F: FnMut((usize, usize)) -> bool {

        let n = self.n();

        for col in 0..n {
            if self.is_col_covered(col) {
                continue;
            }
            for row in 0..n {
                if self.is_row_covered(row) {
                    continue;
                }

                let pos = (row, col);
                if f(pos) {
                    return Some(pos);
                }
            }
        }

        return None;
    }

    #[inline]
    /// iterates over all uncovered (row, col) pairs in row, col order
    pub fn iter_uncovered_row_col<F>(&self, mut f: F)
        where F: FnMut((usize, usize)) {
        let n = self.n();

        for row in 0..n {
            if self.is_row_covered(row) {
                continue;
            }

            /*
            for (i, &b) in self.cols.as_slice().iter().enumerate() {
                // turn all 0th into 1th. we then look for all 1th.
                let mut col = i*32;
                let mut bits = !b;
                while bits != 0 && col < n {
                    if (bits & 1) == 1 {
                        f((row, col));
                    }
                    bits = bits >> 1;
                    col += 1;
                }
            }
            */

            for col in 0..n {
                if self.is_col_covered(col) {
                    continue;
                }

                f((row, col));
            }
        }
    }

    #[inline]
    pub fn is_row_covered(&self, row: usize) -> bool {
        self.rows.contains(row)
    }

    #[inline]
    pub fn is_col_covered(&self, col: usize) -> bool {
        self.cols.contains(col)
    }

    #[inline]
    pub fn cover(&mut self, pos: (usize, usize)) {
        match pos {
            (row, col) => {
                self.cover_row(row);
                self.cover_col(col);
            }
        }
    }

    #[inline]
    pub fn cover_col(&mut self, col: usize) {
        self.cols.set(col, true);
    }

    #[inline]
    pub fn uncover_col(&mut self, col: usize) {
        self.cols.set(col, false);
    }

    #[inline]
    pub fn cover_row(&mut self, row: usize) {
        self.rows.set(row, true);
    }

    pub fn clear(&mut self) {
        self.rows.clear();
        self.cols.clear();
    }
}
<|endoftext|>


<commit_before>//! This is the wasm web-gui.
//! This file is only compiled for the wasm32 target.

#![cfg(target_arch = "wasm32")]
#![recursion_limit = "512"]

use ascii_hangman_backend::game::State;
use ascii_hangman_backend::Backend;
use ascii_hangman_backend::HangmanBackend;
use ascii_hangman_backend::{AUTHOR, CONF_TEMPLATE, CONF_TEMPLATE_SHORT, TITLE, VERSION};
use wasm_bindgen::prelude::*;
use yew::events::KeyboardEvent;
use yew::prelude::*;
use yew::services::reader::{File, FileData, ReaderService, ReaderTask};
// Disable debugging code.
//use yew::services::ConsoleService;
use yew::services::DialogService;
use yew::{html, Component, ComponentLink, Html, InputData, ShouldRender};

#[derive(Debug)]
pub enum Scene {
    Playground(Backend),
    ConfigureGame,
    GameOver,
}

pub struct GuiState {
    config_text: String,
    guess: String,
}

pub struct Model {
    link: ComponentLink<Self>,
    reader: ReaderService,
    // Disable debugging code.
    //console: ConsoleService,
    filereader_tasks: Vec<ReaderTask>,
    scene: Scene,
    state: GuiState,
}

#[derive(Debug)]
pub enum Msg {
    SwitchTo(Scene),
    ConfigTextDelete,
    ConfigTextUpdate(String),
    ConfigReady,
    Files(Vec<File>),
    Loaded(FileData),
    UpdateGuess(String),
    Guess,
    Nope,
    NextRound,
}

impl Component for Model {
    type Message = Msg;
    type Properties = ();

    fn create(_: Self::Properties, link: ComponentLink<Self>) -> Self {
        let state = GuiState {
            config_text: String::from(CONF_TEMPLATE),
            guess: String::new(),
        };

        Model {
            link,
            reader: ReaderService::new(),
            // Disable debugging code.
            //console: ConsoleService::new(),
            filereader_tasks: vec![],
            scene: Scene::ConfigureGame,
            state,
        }
    }

    fn update(&mut self, msg: Self::Message) -> ShouldRender {
        let mut new_scene = None;
        match &mut self.scene {
            Scene::Playground(ref mut app) => match msg {
                Msg::SwitchTo(Scene::ConfigureGame) => {
                    new_scene = Some(Scene::ConfigureGame);
                }
                Msg::SwitchTo(Scene::GameOver) => {
                    new_scene = Some(Scene::GameOver);
                }
                Msg::UpdateGuess(val) => {
                    self.state.guess = val.chars().rev().take(1).collect();
                    // Disable debugging code.
                    //self.console.debug(&self.state.guess);
                }
                Msg::Guess => {
                    app.process_user_input(&self.state.guess);
                    self.state.guess = String::new();
                }
                Msg::Nope => {}
                unexpected => {
                    panic!(
                        "Unexpected message when configurations list shown: {:?}",
                        unexpected
                    );
                }
            },
            Scene::ConfigureGame => match msg {
                Msg::ConfigTextUpdate(val) => {
                    self.state.config_text = val;
                }
                Msg::ConfigTextDelete => {
                    self.state.config_text = CONF_TEMPLATE_SHORT.to_string();
                }
                Msg::SwitchTo(Scene::Playground(app)) => {
                    new_scene = Some(Scene::Playground(app));
                }

                Msg::SwitchTo(Scene::GameOver) => {
                    if DialogService::confirm("Do you really want to quit this game?") {
                        new_scene = Some(Scene::GameOver);
                    }
                }

                Msg::Loaded(file) => {
                    if let Ok(s) = std::str::from_utf8(&file.content) {
                        self.state.config_text.push_str(s);
                    } else {
                        DialogService::alert(&format!("Can not read text file: {}", file.name));
                    }
                }
                Msg::Files(files) => {
                    for file in files.into_iter() {
                        let task = {
                            let callback = self.link.callback(Msg::Loaded);
                            self.reader.read_file(file, callback).unwrap()
                        };
                        self.filereader_tasks.push(task);
                    }
                }
                Msg::ConfigReady => {
                    match Backend::new(self.state.config_text.as_str()) {
                        Ok(app) => {
                            self.link
                                .send_message(Msg::SwitchTo(Scene::Playground(app)));
                        }
                        Err(e) => {
                            DialogService::alert(&format!("Can not parse configuration:\n {}", e))
                        }
                    };
                }
                unexpected => {
                    panic!(
                        "Unexpected message during new config editing: {:?}",
                        unexpected
                    );
                }
            },
            Scene::GameOver => match msg {
                Msg::SwitchTo(Scene::ConfigureGame) => {
                    new_scene = Some(Scene::ConfigureGame);
                }
                unexpected => {
                    panic!("Unexpected message for settings scene: {:?}", unexpected);
                }
            },
        }
        if let Some(new_scene) = new_scene.take() {
            self.scene = new_scene;
        }
        true
    }

    fn change(&mut self, _: Self::Properties) -> ShouldRender {
        false
    }

    fn view(&self) -> Html {
        let header = move || -> Html {
            html! {
 {TITLE}
 }
        };
        let footer = move || -> Html {
            html! {
             {"User Manual"}
            {", "}  {"Desktop Version"}
            {", "}  {"Documentation"}
            {", "}  {"Source Code"}
            {", Version "} {VERSION.unwrap()} {", "} {AUTHOR}   }
        };
        match self.scene {
            Scene::ConfigureGame => html! { <>
                {header()}

 {"Enter your secrets here:"}

                    <textarea class="conf-text"
                        placeholder=CONF_TEMPLATE
                        cols=80
                        rows=25
                        value=&self.state.config_text
                        oninput=self.link.callback(|e: InputData| Msg::ConfigTextUpdate(e.value)) />

 { "or load secrets from files: "}
                        {"Upload Files ..."}
                        <input class="custom-file-input" type="file" id="upload" multiple=true onchange=self.link.callback(move |value| {
                                let mut result = Vec::new();
                                if let ChangeData::Files(files) = value {
                                    let files = js_sys::try_iter(&files)
                                        .unwrap()
                                        .unwrap()
                                        .into_iter()
                                        .map(|v| File::from(v.unwrap()));
                                    result.extend(files);
                                }
                                Msg::Files(result)
                            })/>

                    <button disabled=self.state.config_text.is_empty()
                            onclick=self.link.callback(|_| Msg::ConfigTextDelete)>{ "Delete Secrets" }</button>
                    <button disabled=self.state.config_text.is_empty()
                            onclick=self.link.callback(|_| Msg::ConfigReady)>{ "Start Game" }</button>

                {footer()}
                </>
            },
            Scene::Playground(ref app) => {
                let secret = app.render_secret();
                let (cols, rows) = dimensions(&secret);
                let secret = secret.trim_end_matches("\n");
                let image = app.render_image();
                let image = image.trim_end_matches("\n");
                html! { <>
                    {header()}

                            <textarea class=("image")
                                placeholder="Image"
                                cols=format!("{}", &app.get_image_dimension().0)
                                rows=format!("{}", &app.get_image_dimension().1)
                                value=image
                                readonly=true
                            />

                            { app.render_game_lifes() } { " " }

                            { app.render_game_last_guess() }

                            <textarea class=("secret")
                                cols=cols+1
                                rows=rows
                                value=secret
                                readonly=true
                            />

                            { app.render_instructions() }
                            <input class="guess"
                                type="text"
                                autofocus=true
                                size=1
                                value=self.state.guess
                                oninput=self.link.callback(|e: InputData| Msg::UpdateGuess(e.value))
                                onkeypress=self.link.callback(|e: KeyboardEvent| {
                                   if e.key() == "Enter" { Msg::Guess } else { Msg::Nope }
                                }) />

                        <button disabled={app.get_state() == State::Ongoing || app.get_state() == State::VictoryGameOver}
                                onclick=self.link.callback(|_| Msg::Guess)>{ "Continue Game" }</button>
                        <button disabled={app.get_state() != State::VictoryGameOver}
                                onclick=self.link.callback(|_| Msg::SwitchTo(Scene::ConfigureGame))>{ "Reset Game" }</button>
                        <button disabled={app.get_state() != State::VictoryGameOver}
                                onclick=self.link.callback(|_| Msg::SwitchTo(Scene::GameOver))>{ "End Game" }</button>

                    {footer()}
                    </>
                }
            }
            Scene::GameOver => html! { <>
                {header()}

 {"You can now close this window."}

 { "Bye bye!" }

                    <button onclick=self.link.callback(|_| Msg::SwitchTo(Scene::ConfigureGame))>{ "No, Continue Playing" }</button>

                {footer()}
                </>
            },
        }
    }
}

/// Returns the columns and lines of the smallest
/// grid that can display this multi-line string `s`.
pub fn dimensions(s: &str) -> (usize, usize) {
    let mut row = 0;
    let mut col = 0;
    for l in s.lines() {
        let c = l.chars().count();
        if c > col {
            col = c;
        };
        row += 1;
    }
    (col, row)
}

#[wasm_bindgen(start)]
pub fn run_app() {
    App::<Model>::new().mount_to_body();
}

#[cfg(test)]
mod tests {
    use super::*;
    use secret::Secret;
    #[test]
    fn test_dimensions() {
        let secret = Secret::new("Lorem ipsum dolor sit amet, consectetur adipiscing\
         elit, sed do eiusmod tempor incididunt ut labore et dolore magna aliqua. Ut enim ad minim veniam,\
         quis nostrud exercitation ullamco laboris nisi ut aliquip ex ea commodo consequat. Duis aute irure\
         dolor in reprehenderit in voluptate velit esse cillum dolore eu fugiat nulla pariatur. Excepteur\
         sint occaecat cupidatat non proident, sunt in culpa qui officia deserunt mollit anim id est\
         laborum.");

        //secret.disclose_all();

        assert_eq!(dimensions(format!("{}", secret).as_str()), (68, 22));
        //assert_eq!(format!("{}", secret), String::new());
    }
}
<commit_msg>present simpler start configuration<commit_after>//! This is the wasm web-gui.
//! This file is only compiled for the wasm32 target.

#![cfg(target_arch = "wasm32")]
#![recursion_limit = "512"]

use ascii_hangman_backend::game::State;
use ascii_hangman_backend::Backend;
use ascii_hangman_backend::HangmanBackend;
use ascii_hangman_backend::{AUTHOR, CONF_TEMPLATE, CONF_TEMPLATE_SHORT, TITLE, VERSION};
use wasm_bindgen::prelude::*;
use yew::events::KeyboardEvent;
use yew::prelude::*;
use yew::services::reader::{File, FileData, ReaderService, ReaderTask};
// Disable debugging code.
//use yew::services::ConsoleService;
use yew::services::DialogService;
use yew::{html, Component, ComponentLink, Html, InputData, ShouldRender};

#[derive(Debug)]
pub enum Scene {
    Playground(Backend),
    ConfigureGame,
    GameOver,
}

pub struct GuiState {
    config_text: String,
    guess: String,
}

pub struct Model {
    link: ComponentLink<Self>,
    reader: ReaderService,
    // Disable debugging code.
    //console: ConsoleService,
    filereader_tasks: Vec<ReaderTask>,
    scene: Scene,
    state: GuiState,
}

#[derive(Debug)]
pub enum Msg {
    SwitchTo(Scene),
    ConfigTextDelete,
    ConfigTextUpdate(String),
    ConfigReady,
    Files(Vec<File>),
    Loaded(FileData),
    UpdateGuess(String),
    Guess,
    Nope,
    NextRound,
}

impl Component for Model {
    type Message = Msg;
    type Properties = ();

    fn create(_: Self::Properties, link: ComponentLink<Self>) -> Self {
        let state = GuiState {
            config_text: String::from(CONF_TEMPLATE_SHORT),
            guess: String::new(),
        };

        Model {
            link,
            reader: ReaderService::new(),
            // Disable debugging code.
            //console: ConsoleService::new(),
            filereader_tasks: vec![],
            scene: Scene::ConfigureGame,
            state,
        }
    }

    fn update(&mut self, msg: Self::Message) -> ShouldRender {
        let mut new_scene = None;
        match &mut self.scene {
            Scene::Playground(ref mut app) => match msg {
                Msg::SwitchTo(Scene::ConfigureGame) => {
                    new_scene = Some(Scene::ConfigureGame);
                }
                Msg::SwitchTo(Scene::GameOver) => {
                    new_scene = Some(Scene::GameOver);
                }
                Msg::UpdateGuess(val) => {
                    self.state.guess = val.chars().rev().take(1).collect();
                    // Disable debugging code.
                    //self.console.debug(&self.state.guess);
                }
                Msg::Guess => {
                    app.process_user_input(&self.state.guess);
                    self.state.guess = String::new();
                }
                Msg::Nope => {}
                unexpected => {
                    panic!(
                        "Unexpected message when configurations list shown: {:?}",
                        unexpected
                    );
                }
            },
            Scene::ConfigureGame => match msg {
                Msg::ConfigTextUpdate(val) => {
                    self.state.config_text = val;
                }
                Msg::ConfigTextDelete => {
                    self.state.config_text = CONF_TEMPLATE_SHORT.to_string();
                }
                Msg::SwitchTo(Scene::Playground(app)) => {
                    new_scene = Some(Scene::Playground(app));
                }

                Msg::SwitchTo(Scene::GameOver) => {
                    if DialogService::confirm("Do you really want to quit this game?") {
                        new_scene = Some(Scene::GameOver);
                    }
                }

                Msg::Loaded(file) => {
                    if let Ok(s) = std::str::from_utf8(&file.content) {
                        self.state.config_text.push_str(s);
                    } else {
                        DialogService::alert(&format!("Can not read text file: {}", file.name));
                    }
                }
                Msg::Files(files) => {
                    for file in files.into_iter() {
                        let task = {
                            let callback = self.link.callback(Msg::Loaded);
                            self.reader.read_file(file, callback).unwrap()
                        };
                        self.filereader_tasks.push(task);
                    }
                }
                Msg::ConfigReady => {
                    match Backend::new(self.state.config_text.as_str()) {
                        Ok(app) => {
                            self.link
                                .send_message(Msg::SwitchTo(Scene::Playground(app)));
                        }
                        Err(e) => {
                            DialogService::alert(&format!("Can not parse configuration:\n {}", e))
                        }
                    };
                }
                unexpected => {
                    panic!(
                        "Unexpected message during new config editing: {:?}",
                        unexpected
                    );
                }
            },
            Scene::GameOver => match msg {
                Msg::SwitchTo(Scene::ConfigureGame) => {
                    new_scene = Some(Scene::ConfigureGame);
                }
                unexpected => {
                    panic!("Unexpected message for settings scene: {:?}", unexpected);
                }
            },
        }
        if let Some(new_scene) = new_scene.take() {
            self.scene = new_scene;
        }
        true
    }

    fn change(&mut self, _: Self::Properties) -> ShouldRender {
        false
    }

    fn view(&self) -> Html {
        let header = move || -> Html {
            html! {
 {TITLE}
 }
        };
        let footer = move || -> Html {
            html! {
             {"User Manual"}
            {", "}  {"Desktop Version"}
            {", "}  {"Documentation"}
            {", "}  {"Source Code"}
            {", Version "} {VERSION.unwrap()} {", "} {AUTHOR}   }
        };
        match self.scene {
            Scene::ConfigureGame => html! { <>
                {header()}

 {"Enter your secrets here:"}

                    <textarea class="conf-text"
                        placeholder=CONF_TEMPLATE
                        cols=80
                        rows=25
                        value=&self.state.config_text
                        oninput=self.link.callback(|e: InputData| Msg::ConfigTextUpdate(e.value)) />

 { "or load secrets from files: "}
                        {"Upload Files ..."}
                        <input class="custom-file-input" type="file" id="upload" multiple=true onchange=self.link.callback(move |value| {
                                let mut result = Vec::new();
                                if let ChangeData::Files(files) = value {
                                    let files = js_sys::try_iter(&files)
                                        .unwrap()
                                        .unwrap()
                                        .into_iter()
                                        .map(|v| File::from(v.unwrap()));
                                    result.extend(files);
                                }
                                Msg::Files(result)
                            })/>

                    <button disabled=self.state.config_text.is_empty()
                            onclick=self.link.callback(|_| Msg::ConfigTextDelete)>{ "Delete Secrets" }</button>
                    <button disabled=self.state.config_text.is_empty()
                            onclick=self.link.callback(|_| Msg::ConfigReady)>{ "Start Game" }</button>

                {footer()}
                </>
            },
            Scene::Playground(ref app) => {
                let secret = app.render_secret();
                let (cols, rows) = dimensions(&secret);
                let secret = secret.trim_end_matches("\n");
                let image = app.render_image();
                let image = image.trim_end_matches("\n");
                html! { <>
                    {header()}

                            <textarea class=("image")
                                placeholder="Image"
                                cols=format!("{}", &app.get_image_dimension().0)
                                rows=format!("{}", &app.get_image_dimension().1)
                                value=image
                                readonly=true
                            />

                            { app.render_game_lifes() } { " " }

                            { app.render_game_last_guess() }

                            <textarea class=("secret")
                                cols=cols+1
                                rows=rows
                                value=secret
                                readonly=true
                            />

                            { app.render_instructions() }
                            <input class="guess"
                                type="text"
                                autofocus=true
                                size=1
                                value=self.state.guess
                                oninput=self.link.callback(|e: InputData| Msg::UpdateGuess(e.value))
                                onkeypress=self.link.callback(|e: KeyboardEvent| {
                                   if e.key() == "Enter" { Msg::Guess } else { Msg::Nope }
                                }) />

                        <button disabled={app.get_state() == State::Ongoing || app.get_state() == State::VictoryGameOver}
                                onclick=self.link.callback(|_| Msg::Guess)>{ "Continue Game" }</button>
                        <button disabled={app.get_state() != State::VictoryGameOver}
                                onclick=self.link.callback(|_| Msg::SwitchTo(Scene::ConfigureGame))>{ "Reset Game" }</button>
                        <button disabled={app.get_state() != State::VictoryGameOver}
                                onclick=self.link.callback(|_| Msg::SwitchTo(Scene::GameOver))>{ "End Game" }</button>

                    {footer()}
                    </>
                }
            }
            Scene::GameOver => html! { <>
                {header()}

 {"You can now close this window."}

 { "Bye bye!" }

                    <button onclick=self.link.callback(|_| Msg::SwitchTo(Scene::ConfigureGame))>{ "No, Continue Playing" }</button>

                {footer()}
                </>
            },
        }
    }
}

/// Returns the columns and lines of the smallest
/// grid that can display this multi-line string `s`.
pub fn dimensions(s: &str) -> (usize, usize) {
    let mut row = 0;
    let mut col = 0;
    for l in s.lines() {
        let c = l.chars().count();
        if c > col {
            col = c;
        };
        row += 1;
    }
    (col, row)
}

#[wasm_bindgen(start)]
pub fn run_app() {
    App::<Model>::new().mount_to_body();
}

#[cfg(test)]
mod tests {
    use super::*;
    use secret::Secret;
    #[test]
    fn test_dimensions() {
        let secret = Secret::new("Lorem ipsum dolor sit amet, consectetur adipiscing\
         elit, sed do eiusmod tempor incididunt ut labore et dolore magna aliqua. Ut enim ad minim veniam,\
         quis nostrud exercitation ullamco laboris nisi ut aliquip ex ea commodo consequat. Duis aute irure\
         dolor in reprehenderit in voluptate velit esse cillum dolore eu fugiat nulla pariatur. Excepteur\
         sint occaecat cupidatat non proident, sunt in culpa qui officia deserunt mollit anim id est\
         laborum.");

        //secret.disclose_all();

        assert_eq!(dimensions(format!("{}", secret).as_str()), (68, 22));
        //assert_eq!(format!("{}", secret), String::new());
    }
}
<|endoftext|>


<commit_before>// pest. The Elegant Parser
// Copyright (C) 2017  Dragoș Tiselice
//
// 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/.

use std::rc::Rc;

use super::error::Error;
use super::inputs::{Input, Position, Span};
use super::inputs::position;
use super::iterators::{pairs, QueueableToken};
use super::RuleType;

/// An `enum` specifying the current lookahead status of a `ParserState`.
#[derive(Clone, Copy, Debug, Eq, PartialEq)]
pub enum Lookahead {
    Positive,
    Negative,
    None
}

/// A `struct` which contains the complete state of a `Parser`.
#[derive(Debug)]
pub struct ParserState<R: RuleType, I: Input> {
    queue: Vec<QueueableToken<R>>,
    lookahead: Lookahead,
    pos_attempts: Vec<R>,
    neg_attempts: Vec<R>,
    attempt_pos: usize,
    /// Specifies whether the current state is atomic
    pub is_atomic: bool,
    /// Stack of `Span`s
    pub stack: Vec<Span<I>>
}

/// Creates a `ParserState` from an `Input`, supplying it to a closure `f`.
///
/// # Examples
///
/// ```
/// # use std::rc::Rc;
/// # use pest;
/// # use pest::inputs::StringInput;
///
/// let input = Rc::new(StringInput::new("".to_owned()));
/// pest::state::<(), _, _>(input, |_, pos| {
///     Ok(pos)
/// }).unwrap();
/// ```
pub fn state<R: RuleType, I: Input, F>(
    input: Rc<I>,
    f: F
) -> Result<pairs::Pairs<R, I>, Error<R, I>>
where
    F: FnOnce(&mut ParserState<R, I>, Position<I>) -> Result<Position<I>, Position<I>>
{
    let mut state = ParserState {
        queue: vec![],
        lookahead: Lookahead::None,
        is_atomic: false,
        pos_attempts: vec![],
        neg_attempts: vec![],
        attempt_pos: 0,
        stack: vec![]
    };

    if f(&mut state, Position::from_start(input.clone())).is_ok() {
        let len = state.queue.len();
        Ok(pairs::new(Rc::new(state.queue), input, 0, len))
    } else {
        state.pos_attempts.sort();
        state.pos_attempts.dedup();
        state.neg_attempts.sort();
        state.neg_attempts.dedup();

        Err(Error::ParsingError {
            positives: state.pos_attempts,
            negatives: state.neg_attempts,
            // All attempted positions were legal.
            pos: unsafe { position::new(input, state.attempt_pos) }
        })
    }
}

impl<R: RuleType, I: Input> ParserState<R, I> {
    /// Wrapper needed to generate tokens.
    ///
    /// # Examples
    ///
    /// ```
    /// # use std::rc::Rc;
    /// # use pest;
    /// # use pest::inputs::StringInput;
    /// # #[allow(non_camel_case_types)]
    /// # #[derive(Clone, Copy, Debug, Eq, Hash, Ord, PartialEq, PartialOrd)]
    /// enum Rule {
    ///     a
    /// }
    ///
    /// let input = Rc::new(StringInput::new("a".to_owned()));
    /// let pairs: Vec<_> = pest::state(input, |state, pos| {
    ///     state.rule(Rule::a, pos, |_, p| Ok(p))
    /// }).unwrap().collect();
    ///
    /// assert_eq!(pairs.len(), 1);
    /// ```
    #[inline]
    pub fn rule<F>(&mut self, rule: R, pos: Position<I>, f: F) -> Result<Position<I>, Position<I>>
    where
        F: FnOnce(&mut ParserState<R, I>, Position<I>) -> Result<Position<I>, Position<I>>
    {
        let actual_pos = pos.pos();
        let index = self.queue.len();

        let (pos_attempts_index, neg_attempts_index) = if actual_pos == self.attempt_pos {
            (self.pos_attempts.len(), self.neg_attempts.len())
        } else { // Attempts have not been cleared yet since the attempt_pos is older.
            (0, 0)
        };

        if self.lookahead == Lookahead::None && !self.is_atomic {
            // Pair's position will only be known after running the closure.
            self.queue.push(QueueableToken::Start { pair: 0, pos: actual_pos });
        }

        let result = f(self, pos);

        if result.is_err() || self.lookahead == Lookahead::Negative {
            self.track(rule, actual_pos, pos_attempts_index, neg_attempts_index);
        }

        if self.lookahead == Lookahead::None && !self.is_atomic {
            if let Ok(ref pos) = result {
                // Storing the pair's index in the first token that was added before the closure was
                // run.
                let new_index = self.queue.len();
                match self.queue[index] {
                    QueueableToken::Start { ref mut pair, .. } => *pair = new_index,
                    _ => unreachable!()
                };

                self.queue.push(QueueableToken::End { rule: rule, pos: pos.pos() });
            } else {
                self.queue.truncate(index);
            }
        }

        result
    }

    fn track(&mut self, rule: R, pos: usize, pos_attempts_index: usize, neg_attempts_index: usize) {
        if self.is_atomic {
            return;
        }

        // If nested rules made no progress, there is no use to report them; it's only useful to
        // track the current rule.
        if pos == self.attempt_pos {
            self.pos_attempts.truncate(pos_attempts_index);
            self.neg_attempts.truncate(neg_attempts_index);
        }

        let attempts = if self.lookahead != Lookahead::Negative {
            &mut self.pos_attempts
        } else {
            &mut self.neg_attempts
        };

        if pos > self.attempt_pos {
            attempts.clear();
            self.attempt_pos = pos;
        }

        if pos == self.attempt_pos {
            attempts.push(rule);
        }
    }

    /// Wrapper which removes `Tokens` in case of a sequence's failure.
    ///
    /// Usually used in conjunction with
    /// [`Position::sequence`](../inputs/struct.Position#method.sequence).
    ///
    /// # Examples
    ///
    /// ```
    /// # use std::rc::Rc;
    /// # use pest;
    /// # use pest::inputs::StringInput;
    /// # #[allow(non_camel_case_types)]
    /// # #[derive(Clone, Copy, Debug, Eq, Hash, Ord, PartialEq, PartialOrd)]
    /// enum Rule {
    ///     a
    /// }
    ///
    /// let input = Rc::new(StringInput::new("a".to_owned()));
    /// let pairs: Vec<_> = pest::state(input, |state, pos| {
    ///     state.sequence(move |state| {
    ///         pos.sequence(|p| {
    ///             state.rule(Rule::a, p, |_, p| Ok(p)).and_then(|p| {
    ///                 p.match_string("b")
    ///             })
    ///         })
    ///     }).or_else(|p| {
    ///         Ok(p)
    ///     })
    /// }).unwrap().collect();
    ///
    /// assert_eq!(pairs.len(), 0);
    /// ```
    #[inline]
    pub fn sequence<F>(&mut self, f: F) -> Result<Position<I>, Position<I>>
    where
        F: FnOnce(&mut ParserState<R, I>) -> Result<Position<I>, Position<I>>
    {
        let index = self.queue.len();

        let result = f(self);

        if result.is_err() {
            self.queue.truncate(index);
        }

        result
    }

    /// Wrapper which stops `Token`s from being generated.
    ///
    /// Usually used in conjunction with
    /// [`Position::lookahead`](../inputs/struct.Position#method.lookahead).
    ///
    /// # Examples
    ///
    /// ```
    /// # use std::rc::Rc;
    /// # use pest;
    /// # use pest::inputs::StringInput;
    /// # #[allow(non_camel_case_types)]
    /// # #[derive(Clone, Copy, Debug, Eq, Hash, Ord, PartialEq, PartialOrd)]
    /// enum Rule {
    ///     a
    /// }
    ///
    /// let input = Rc::new(StringInput::new("a".to_owned()));
    /// let pairs: Vec<_> = pest::state(input, |state, pos| {
    ///     state.lookahead(true, move |state| {
    ///         state.rule(Rule::a, pos, |_, p| Ok(p))
    ///     })
    /// }).unwrap().collect();
    ///
    /// assert_eq!(pairs.len(), 0);
    /// ```
    #[inline]
    pub fn lookahead<F>(&mut self, is_positive: bool, f: F) -> Result<Position<I>, Position<I>>
    where
        F: FnOnce(&mut ParserState<R, I>) -> Result<Position<I>, Position<I>>
    {
        let initial_lookahead = self.lookahead;

        self.lookahead = if is_positive {
            match initial_lookahead {
                Lookahead::None | Lookahead::Positive => Lookahead::Positive,
                Lookahead::Negative => Lookahead::Negative
            }
        } else {
            match initial_lookahead {
                Lookahead::None | Lookahead::Positive => Lookahead::Negative,
                Lookahead::Negative => Lookahead::Positive
            }
        };

        let result = f(self);

        self.lookahead = initial_lookahead;

        result
    }

    /// Wrapper which stops `Token`s from being generated according to `is_atomic`.
    ///
    /// # Examples
    ///
    /// ```
    /// # use std::rc::Rc;
    /// # use pest;
    /// # use pest::inputs::StringInput;
    /// # #[allow(non_camel_case_types)]
    /// # #[derive(Clone, Copy, Debug, Eq, Hash, Ord, PartialEq, PartialOrd)]
    /// enum Rule {
    ///     a
    /// }
    ///
    /// let input = Rc::new(StringInput::new("a".to_owned()));
    /// let pairs: Vec<_> = pest::state(input, |state, pos| {
    ///     state.atomic(true, move |state| {
    ///         state.rule(Rule::a, pos, |_, p| Ok(p))
    ///     })
    /// }).unwrap().collect();
    ///
    /// assert_eq!(pairs.len(), 0);
    /// ```
    #[inline]
    pub fn atomic<F>(&mut self, is_atomic: bool, f: F) -> Result<Position<I>, Position<I>>
    where
        F: FnOnce(&mut ParserState<R, I>) -> Result<Position<I>, Position<I>>
    {
        let should_toggle = self.is_atomic != is_atomic;

        if should_toggle {
            self.is_atomic = is_atomic;
        }

        let result = f(self);

        if should_toggle {
            self.is_atomic = !is_atomic;
        }

        result
    }

    /// Returns whether the `ParserState` is in an atomic state.
    ///
    /// # Examples
    ///
    /// ```
    /// # use std::rc::Rc;
    /// # use pest;
    /// # use pest::inputs::StringInput;
    /// # #[allow(non_camel_case_types)]
    /// # #[derive(Clone, Copy, Debug, Eq, Hash, Ord, PartialEq, PartialOrd)]
    /// enum Rule {
    ///     a
    /// }
    ///
    /// let input = Rc::new(StringInput::new("a".to_owned()));
    /// let pairs: Vec<_> = pest::state::<(), _, _>(input, |state, pos| {
    ///     assert!(!state.is_atomic());
    ///
    ///     state.atomic(true, move |state| {
    ///         assert!(state.is_atomic());
    ///
    ///         Ok(pos)
    ///     })
    /// }).unwrap().collect();
    ///
    /// assert_eq!(pairs.len(), 0);
    /// ```
    #[inline]
    pub fn is_atomic(&self) -> bool {
        self.is_atomic
    }
}
<commit_msg>Made child parsing attempts visible. Fixes #140.<commit_after>// pest. The Elegant Parser
// Copyright (C) 2017  Dragoș Tiselice
//
// 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/.

use std::rc::Rc;

use super::error::Error;
use super::inputs::{Input, Position, Span};
use super::inputs::position;
use super::iterators::{pairs, QueueableToken};
use super::RuleType;

/// An `enum` specifying the current lookahead status of a `ParserState`.
#[derive(Clone, Copy, Debug, Eq, PartialEq)]
pub enum Lookahead {
    Positive,
    Negative,
    None
}

/// A `struct` which contains the complete state of a `Parser`.
#[derive(Debug)]
pub struct ParserState<R: RuleType, I: Input> {
    queue: Vec<QueueableToken<R>>,
    lookahead: Lookahead,
    pos_attempts: Vec<R>,
    neg_attempts: Vec<R>,
    attempt_pos: usize,
    /// Specifies whether the current state is atomic
    pub is_atomic: bool,
    /// Stack of `Span`s
    pub stack: Vec<Span<I>>
}

/// Creates a `ParserState` from an `Input`, supplying it to a closure `f`.
///
/// # Examples
///
/// ```
/// # use std::rc::Rc;
/// # use pest;
/// # use pest::inputs::StringInput;
///
/// let input = Rc::new(StringInput::new("".to_owned()));
/// pest::state::<(), _, _>(input, |_, pos| {
///     Ok(pos)
/// }).unwrap();
/// ```
pub fn state<R: RuleType, I: Input, F>(
    input: Rc<I>,
    f: F
) -> Result<pairs::Pairs<R, I>, Error<R, I>>
where
    F: FnOnce(&mut ParserState<R, I>, Position<I>) -> Result<Position<I>, Position<I>>
{
    let mut state = ParserState {
        queue: vec![],
        lookahead: Lookahead::None,
        is_atomic: false,
        pos_attempts: vec![],
        neg_attempts: vec![],
        attempt_pos: 0,
        stack: vec![]
    };

    if f(&mut state, Position::from_start(input.clone())).is_ok() {
        let len = state.queue.len();
        Ok(pairs::new(Rc::new(state.queue), input, 0, len))
    } else {
        state.pos_attempts.sort();
        state.pos_attempts.dedup();
        state.neg_attempts.sort();
        state.neg_attempts.dedup();

        Err(Error::ParsingError {
            positives: state.pos_attempts,
            negatives: state.neg_attempts,
            // All attempted positions were legal.
            pos: unsafe { position::new(input, state.attempt_pos) }
        })
    }
}

impl<R: RuleType, I: Input> ParserState<R, I> {
    /// Wrapper needed to generate tokens.
    ///
    /// # Examples
    ///
    /// ```
    /// # use std::rc::Rc;
    /// # use pest;
    /// # use pest::inputs::StringInput;
    /// # #[allow(non_camel_case_types)]
    /// # #[derive(Clone, Copy, Debug, Eq, Hash, Ord, PartialEq, PartialOrd)]
    /// enum Rule {
    ///     a
    /// }
    ///
    /// let input = Rc::new(StringInput::new("a".to_owned()));
    /// let pairs: Vec<_> = pest::state(input, |state, pos| {
    ///     state.rule(Rule::a, pos, |_, p| Ok(p))
    /// }).unwrap().collect();
    ///
    /// assert_eq!(pairs.len(), 1);
    /// ```
    #[inline]
    pub fn rule<F>(&mut self, rule: R, pos: Position<I>, f: F) -> Result<Position<I>, Position<I>>
    where
        F: FnOnce(&mut ParserState<R, I>, Position<I>) -> Result<Position<I>, Position<I>>
    {
        let actual_pos = pos.pos();
        let index = self.queue.len();

        let (pos_attempts_index, neg_attempts_index) = if actual_pos == self.attempt_pos {
            (self.pos_attempts.len(), self.neg_attempts.len())
        } else { // Attempts have not been cleared yet since the attempt_pos is older.
            (0, 0)
        };

        if self.lookahead == Lookahead::None && !self.is_atomic {
            // Pair's position will only be known after running the closure.
            self.queue.push(QueueableToken::Start { pair: 0, pos: actual_pos });
        }

        let attempts = self.pos_attempts.len() + self.neg_attempts.len();

        let result = f(self, pos);

        if result.is_err() || self.lookahead == Lookahead::Negative {
            self.track(rule, actual_pos, pos_attempts_index, neg_attempts_index, attempts);
        }

        if self.lookahead == Lookahead::None && !self.is_atomic {
            if let Ok(ref pos) = result {
                // Storing the pair's index in the first token that was added before the closure was
                // run.
                let new_index = self.queue.len();
                match self.queue[index] {
                    QueueableToken::Start { ref mut pair, .. } => *pair = new_index,
                    _ => unreachable!()
                };

                self.queue.push(QueueableToken::End { rule: rule, pos: pos.pos() });
            } else {
                self.queue.truncate(index);
            }
        }

        result
    }

    fn track(
        &mut self,
        rule: R,
        pos: usize,
        pos_attempts_index: usize,
        neg_attempts_index: usize,
        prev_attempts: usize
    ) {
        if self.is_atomic {
            return;
        }

        // If nested rules made no progress, there is no use to report them; it's only useful to
        // track the current rule, the exception being when only one attempt has been made during
        // the children rules.
        let curr_attempts = self.pos_attempts.len() + self.neg_attempts.len();
        if curr_attempts > prev_attempts && curr_attempts - prev_attempts == 1 {
            return;
        }

        if pos == self.attempt_pos {
            self.pos_attempts.truncate(pos_attempts_index);
            self.neg_attempts.truncate(neg_attempts_index);
        }

        let attempts = if self.lookahead != Lookahead::Negative {
            &mut self.pos_attempts
        } else {
            &mut self.neg_attempts
        };

        if pos > self.attempt_pos {
            attempts.clear();
            self.attempt_pos = pos;
        }

        if pos == self.attempt_pos {
            attempts.push(rule);
        }
    }

    /// Wrapper which removes `Tokens` in case of a sequence's failure.
    ///
    /// Usually used in conjunction with
    /// [`Position::sequence`](../inputs/struct.Position#method.sequence).
    ///
    /// # Examples
    ///
    /// ```
    /// # use std::rc::Rc;
    /// # use pest;
    /// # use pest::inputs::StringInput;
    /// # #[allow(non_camel_case_types)]
    /// # #[derive(Clone, Copy, Debug, Eq, Hash, Ord, PartialEq, PartialOrd)]
    /// enum Rule {
    ///     a
    /// }
    ///
    /// let input = Rc::new(StringInput::new("a".to_owned()));
    /// let pairs: Vec<_> = pest::state(input, |state, pos| {
    ///     state.sequence(move |state| {
    ///         pos.sequence(|p| {
    ///             state.rule(Rule::a, p, |_, p| Ok(p)).and_then(|p| {
    ///                 p.match_string("b")
    ///             })
    ///         })
    ///     }).or_else(|p| {
    ///         Ok(p)
    ///     })
    /// }).unwrap().collect();
    ///
    /// assert_eq!(pairs.len(), 0);
    /// ```
    #[inline]
    pub fn sequence<F>(&mut self, f: F) -> Result<Position<I>, Position<I>>
    where
        F: FnOnce(&mut ParserState<R, I>) -> Result<Position<I>, Position<I>>
    {
        let index = self.queue.len();

        let result = f(self);

        if result.is_err() {
            self.queue.truncate(index);
        }

        result
    }

    /// Wrapper which stops `Token`s from being generated.
    ///
    /// Usually used in conjunction with
    /// [`Position::lookahead`](../inputs/struct.Position#method.lookahead).
    ///
    /// # Examples
    ///
    /// ```
    /// # use std::rc::Rc;
    /// # use pest;
    /// # use pest::inputs::StringInput;
    /// # #[allow(non_camel_case_types)]
    /// # #[derive(Clone, Copy, Debug, Eq, Hash, Ord, PartialEq, PartialOrd)]
    /// enum Rule {
    ///     a
    /// }
    ///
    /// let input = Rc::new(StringInput::new("a".to_owned()));
    /// let pairs: Vec<_> = pest::state(input, |state, pos| {
    ///     state.lookahead(true, move |state| {
    ///         state.rule(Rule::a, pos, |_, p| Ok(p))
    ///     })
    /// }).unwrap().collect();
    ///
    /// assert_eq!(pairs.len(), 0);
    /// ```
    #[inline]
    pub fn lookahead<F>(&mut self, is_positive: bool, f: F) -> Result<Position<I>, Position<I>>
    where
        F: FnOnce(&mut ParserState<R, I>) -> Result<Position<I>, Position<I>>
    {
        let initial_lookahead = self.lookahead;

        self.lookahead = if is_positive {
            match initial_lookahead {
                Lookahead::None | Lookahead::Positive => Lookahead::Positive,
                Lookahead::Negative => Lookahead::Negative
            }
        } else {
            match initial_lookahead {
                Lookahead::None | Lookahead::Positive => Lookahead::Negative,
                Lookahead::Negative => Lookahead::Positive
            }
        };

        let result = f(self);

        self.lookahead = initial_lookahead;

        result
    }

    /// Wrapper which stops `Token`s from being generated according to `is_atomic`.
    ///
    /// # Examples
    ///
    /// ```
    /// # use std::rc::Rc;
    /// # use pest;
    /// # use pest::inputs::StringInput;
    /// # #[allow(non_camel_case_types)]
    /// # #[derive(Clone, Copy, Debug, Eq, Hash, Ord, PartialEq, PartialOrd)]
    /// enum Rule {
    ///     a
    /// }
    ///
    /// let input = Rc::new(StringInput::new("a".to_owned()));
    /// let pairs: Vec<_> = pest::state(input, |state, pos| {
    ///     state.atomic(true, move |state| {
    ///         state.rule(Rule::a, pos, |_, p| Ok(p))
    ///     })
    /// }).unwrap().collect();
    ///
    /// assert_eq!(pairs.len(), 0);
    /// ```
    #[inline]
    pub fn atomic<F>(&mut self, is_atomic: bool, f: F) -> Result<Position<I>, Position<I>>
    where
        F: FnOnce(&mut ParserState<R, I>) -> Result<Position<I>, Position<I>>
    {
        let should_toggle = self.is_atomic != is_atomic;

        if should_toggle {
            self.is_atomic = is_atomic;
        }

        let result = f(self);

        if should_toggle {
            self.is_atomic = !is_atomic;
        }

        result
    }

    /// Returns whether the `ParserState` is in an atomic state.
    ///
    /// # Examples
    ///
    /// ```
    /// # use std::rc::Rc;
    /// # use pest;
    /// # use pest::inputs::StringInput;
    /// # #[allow(non_camel_case_types)]
    /// # #[derive(Clone, Copy, Debug, Eq, Hash, Ord, PartialEq, PartialOrd)]
    /// enum Rule {
    ///     a
    /// }
    ///
    /// let input = Rc::new(StringInput::new("a".to_owned()));
    /// let pairs: Vec<_> = pest::state::<(), _, _>(input, |state, pos| {
    ///     assert!(!state.is_atomic());
    ///
    ///     state.atomic(true, move |state| {
    ///         assert!(state.is_atomic());
    ///
    ///         Ok(pos)
    ///     })
    /// }).unwrap().collect();
    ///
    /// assert_eq!(pairs.len(), 0);
    /// ```
    #[inline]
    pub fn is_atomic(&self) -> bool {
        self.is_atomic
    }
}
<|endoftext|>


<commit_before>use serde::de;
use std::{error::Error as StdError, fmt, io, str::Utf8Error, string::FromUtf8Error};

use crate::parse::Position;

/// Deserialization result.
pub type Result<T> = std::result::Result<T, Error>;

#[derive(Clone, Debug, PartialEq)]
pub enum Error {
    IoError(String),
    Message(String),
    Parser(ParseError, Position),
}

#[derive(Clone, Debug, PartialEq)]
pub enum ParseError {
    Base64Error(base64::DecodeError),
    Eof,
    ExpectedArray,
    ExpectedArrayEnd,
    ExpectedAttribute,
    ExpectedAttributeEnd,
    ExpectedBoolean,
    ExpectedComma,
    ExpectedEnum,
    ExpectedChar,
    ExpectedFloat,
    ExpectedInteger,
    ExpectedOption,
    ExpectedOptionEnd,
    ExpectedMap,
    ExpectedMapColon,
    ExpectedMapEnd,
    ExpectedStruct,
    ExpectedStructEnd,
    ExpectedUnit,
    ExpectedStructName,
    ExpectedString,
    ExpectedStringEnd,
    ExpectedIdentifier,

    InvalidEscape(&'static str),

    IntegerOutOfBounds,

    NoSuchExtension(String),

    UnclosedBlockComment,
    UnderscoreAtBeginning,
    UnexpectedByte(char),

    Utf8Error(Utf8Error),
    TrailingCharacters,

    #[doc(hidden)]
    __NonExhaustive,
}

impl fmt::Display for Error {
    fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
        match *self {
            Error::IoError(ref s) => write!(f, "{}", s),
            Error::Message(ref s) => write!(f, "{}", s),
            Error::Parser(_, pos) => write!(f, "{}: {}", pos, self.description()),
        }
    }
}

impl de::Error for Error {
    fn custom<T: fmt::Display>(msg: T) -> Self {
        Error::Message(msg.to_string())
    }
}

impl StdError for Error {
    fn description(&self) -> &str {
        match *self {
            Error::IoError(ref s) => s,
            Error::Message(ref e) => e,
            Error::Parser(ref kind, _) => match *kind {
                ParseError::Base64Error(ref e) => e.description(),
                ParseError::Eof => "Unexpected end of file",
                ParseError::ExpectedArray => "Expected array",
                ParseError::ExpectedArrayEnd => "Expected end of array",
                ParseError::ExpectedAttribute => "Expected an enable attribute",
                ParseError::ExpectedAttributeEnd => {
                    "Expected closing `)` and `]` after the attribute"
                }
                ParseError::ExpectedBoolean => "Expected boolean",
                ParseError::ExpectedComma => "Expected comma",
                ParseError::ExpectedEnum => "Expected enum",
                ParseError::ExpectedChar => "Expected char",
                ParseError::ExpectedFloat => "Expected float",
                ParseError::ExpectedInteger => "Expected integer",
                ParseError::ExpectedOption => "Expected option",
                ParseError::ExpectedOptionEnd => "Expected end of option",
                ParseError::ExpectedMap => "Expected map",
                ParseError::ExpectedMapColon => "Expected colon",
                ParseError::ExpectedMapEnd => "Expected end of map",
                ParseError::ExpectedStruct => "Expected struct",
                ParseError::ExpectedStructEnd => "Expected end of struct",
                ParseError::ExpectedUnit => "Expected unit",
                ParseError::ExpectedStructName => "Expected struct name",
                ParseError::ExpectedString => "Expected string",
                ParseError::ExpectedStringEnd => "Expected string end",
                ParseError::ExpectedIdentifier => "Expected identifier",

                ParseError::InvalidEscape(_) => "Invalid escape sequence",

                ParseError::IntegerOutOfBounds => "Integer is out of bounds",

                ParseError::NoSuchExtension(_) => "No such RON extension",

                ParseError::Utf8Error(ref e) => e.description(),
                ParseError::UnclosedBlockComment => "Unclosed block comment",
                ParseError::UnderscoreAtBeginning => "Found underscore at the beginning",
                ParseError::UnexpectedByte(_) => "Unexpected byte",
                ParseError::TrailingCharacters => "Non-whitespace trailing characters",

                ParseError::__NonExhaustive => unimplemented!(),
            },
        }
    }
}

impl From<Utf8Error> for ParseError {
    fn from(e: Utf8Error) -> Self {
        ParseError::Utf8Error(e)
    }
}

impl From<FromUtf8Error> for ParseError {
    fn from(e: FromUtf8Error) -> Self {
        ParseError::Utf8Error(e.utf8_error())
    }
}

impl From<Utf8Error> for Error {
    fn from(e: Utf8Error) -> Self {
        Error::Parser(ParseError::Utf8Error(e), Position { line: 0, col: 0 })
    }
}

impl From<io::Error> for Error {
    fn from(e: io::Error) -> Self {
        Error::IoError(e.description().to_string())
    }
}
<commit_msg>Remove deprecated uses of Error::description.<commit_after>use serde::de;
use std::{error::Error as StdError, fmt, io, str::Utf8Error, string::FromUtf8Error};

use crate::parse::Position;

/// Deserialization result.
pub type Result<T> = std::result::Result<T, Error>;

#[derive(Clone, Debug, PartialEq)]
pub enum Error {
    IoError(String),
    Message(String),
    Parser(ParseError, Position),
}

#[derive(Clone, Debug, PartialEq)]
pub enum ParseError {
    Base64Error(base64::DecodeError),
    Eof,
    ExpectedArray,
    ExpectedArrayEnd,
    ExpectedAttribute,
    ExpectedAttributeEnd,
    ExpectedBoolean,
    ExpectedComma,
    ExpectedEnum,
    ExpectedChar,
    ExpectedFloat,
    ExpectedInteger,
    ExpectedOption,
    ExpectedOptionEnd,
    ExpectedMap,
    ExpectedMapColon,
    ExpectedMapEnd,
    ExpectedStruct,
    ExpectedStructEnd,
    ExpectedUnit,
    ExpectedStructName,
    ExpectedString,
    ExpectedStringEnd,
    ExpectedIdentifier,

    InvalidEscape(&'static str),

    IntegerOutOfBounds,

    NoSuchExtension(String),

    UnclosedBlockComment,
    UnderscoreAtBeginning,
    UnexpectedByte(char),

    Utf8Error(Utf8Error),
    TrailingCharacters,

    #[doc(hidden)]
    __NonExhaustive,
}

impl fmt::Display for Error {
    fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
        match *self {
            Error::IoError(ref s) => write!(f, "{}", s),
            Error::Message(ref s) => write!(f, "{}", s),
            Error::Parser(_, pos) => write!(f, "{}: {}", pos, self),
        }
    }
}

impl de::Error for Error {
    fn custom<T: fmt::Display>(msg: T) -> Self {
        Error::Message(msg.to_string())
    }
}

impl StdError for Error {}

impl From<Utf8Error> for ParseError {
    fn from(e: Utf8Error) -> Self {
        ParseError::Utf8Error(e)
    }
}

impl From<FromUtf8Error> for ParseError {
    fn from(e: FromUtf8Error) -> Self {
        ParseError::Utf8Error(e.utf8_error())
    }
}

impl From<Utf8Error> for Error {
    fn from(e: Utf8Error) -> Self {
        Error::Parser(ParseError::Utf8Error(e), Position { line: 0, col: 0 })
    }
}

impl From<io::Error> for Error {
    fn from(e: io::Error) -> Self {
        Error::IoError(e.to_string())
    }
}
<|endoftext|>


<commit_before>use toml::Table;
use walkdir::WalkDir;
use std::path::Path;
use std::fs::{create_dir_all, copy, metadata};

pub fn load(home_dir: &str, app_dir: &str, config: &Table) {
    let dotfiles = config
        .get("dotfiles")
        .expect("No [dotfiles] section found")
        .as_table().expect("[templates] is not a valid TOML table");

    for (dotfile, tar_path) in dotfiles {
        let tar_path = tar_path.as_str().expect("error: path not a valid string");
        let tar_path = tar_path.replace("$HOME", home_dir).replace("~", home_dir);
        let src_path = format!("{}/dotfiles/{}", app_dir, dotfile);
        let src_len = src_path.len();

        for entry in WalkDir::new(&src_path) {
            println!("Copying {} to {}", ent_src_path, ent_tar_path);

            let ent_src_path = entry
                .expect("There was an error accessing the dotfile source.");
            let ent_src_path = ent_src_path.path().to_str()
                .expect("Path is not a valid string.");
            let ent_tar_path = format!("{}{}", tar_path, &ent_src_path[src_len..]);

            let src_meta = metadata(&ent_src_path)
                .expect("Could not get file metadata.");
            if src_meta.is_file() {
                create_dir_all(Path::new(&ent_tar_path).parent()
                               .expect("Invalid path structure."))
                    .expect("Could not create target directory.");

                copy(&ent_src_path, &ent_tar_path)
                    .expect(format!("Could not copy {}.", &ent_src_path).as_str());
            }
        }
    }
}
<commit_msg>Fixed progress printing bug.<commit_after>use toml::Table;
use walkdir::WalkDir;
use std::path::Path;
use std::fs::{create_dir_all, copy, metadata};

pub fn load(home_dir: &str, app_dir: &str, config: &Table) {
    let dotfiles = config
        .get("dotfiles")
        .expect("No [dotfiles] section found")
        .as_table().expect("[templates] is not a valid TOML table");

    for (dotfile, tar_path) in dotfiles {
        let tar_path = tar_path.as_str().expect("error: path not a valid string");
        let tar_path = tar_path.replace("$HOME", home_dir).replace("~", home_dir);
        let src_path = format!("{}/dotfiles/{}", app_dir, dotfile);
        let src_len = src_path.len();

        for entry in WalkDir::new(&src_path) {
            let ent_src_path = entry
                .expect("There was an error accessing the dotfile source.");
            let ent_src_path = ent_src_path.path().to_str()
                .expect("Path is not a valid string.");
            let ent_tar_path = format!("{}{}", tar_path, &ent_src_path[src_len..]);

            println!("Copying {} to {}", ent_src_path, ent_tar_path);

            let src_meta = metadata(&ent_src_path)
                .expect("Could not get file metadata.");
            if src_meta.is_file() {
                create_dir_all(Path::new(&ent_tar_path).parent()
                               .expect("Invalid path structure."))
                    .expect("Could not create target directory.");

                copy(&ent_src_path, &ent_tar_path)
                    .expect(format!("Could not copy {}.", &ent_src_path).as_str());
            }
        }
    }
}
<|endoftext|>


<commit_before>pub mod stats;

mod scrape;

use crate::config::Config;
use crate::err::{self, Result};
use notify_rust::Notification;
use serde::de::Visitor;
use serde::{Deserialize, Deserializer};
use snafu::ResultExt;
use stats::ListenerStats;
use std::borrow::Cow;
use std::cmp::{self, Eq, Ord};
use std::fmt;
use std::result;
use std::str::FromStr;
use strum_macros::EnumString;

#[derive(Debug)]
pub struct Feed<'a> {
    pub id: u32,
    pub name: String,
    pub listeners: u32,
    pub location: Location,
    pub county: Cow<'a, str>,
    pub alert: Option<String>,
}

impl<'a> Feed<'a> {
    pub fn scrape_all(config: &Config) -> Result<Vec<Self>> {
        let mut feeds = Self::scrape_source(Source::Top50, config.misc.minimum_listeners)?;

        if let Some(loc) = config.misc.location {
            let loc_feeds =
                Self::scrape_source(Source::Location(loc), config.misc.minimum_listeners)?;

            feeds.extend(loc_feeds);
        }

        feeds.sort_unstable();
        feeds.dedup();

        Ok(feeds)
    }

    fn scrape_source(source: Source, min_listeners: u32) -> Result<Vec<Self>> {
        let resp = ureq::get(source.url().as_ref())
            .timeout_connect(15_000)
            .call();

        if let Some(err) = resp.synthetic_error() {
            return Err(err.into());
        }

        let body = resp.into_string().context(err::HttpIO)?;

        match source {
            Source::Top50 => scrape::scrape_top(&body, min_listeners).context(err::ParseTopFeeds),
            Source::Location(location) => scrape::scrape_location(&body, min_listeners, location)
                .context(err::ParseLocationFeeds { location }),
        }
    }
}

impl<'a> PartialEq for Feed<'a> {
    fn eq(&self, other: &Self) -> bool {
        self.id == other.id
    }
}

impl<'a> Eq for Feed<'a> {}

impl<'a> PartialOrd for Feed<'a> {
    fn partial_cmp(&self, other: &Self) -> Option<cmp::Ordering> {
        Some(self.cmp(other))
    }
}

impl<'a> Ord for Feed<'a> {
    fn cmp(&self, other: &Self) -> cmp::Ordering {
        self.id.cmp(&other.id)
    }
}

pub enum Source {
    Top50,
    Location(Location),
}

impl Source {
    pub fn url(&self) -> Cow<str> {
        match self {
            Self::Top50 => "https://www.broadcastify.com/listen/top".into(),
            Self::Location(loc) => {
                format!("https://www.broadcastify.com/listen/stid/{}", loc.id()).into()
            }
        }
    }
}

#[derive(Debug)]
pub struct FeedNotif<'a> {
    pub feed: Feed<'a>,
    pub jump: f32,
}

impl<'a> FeedNotif<'a> {
    pub fn new(feed: Feed<'a>, stats: &ListenerStats) -> Self {
        Self {
            feed,
            jump: stats.jump,
        }
    }

    pub fn show_notif(&self, index: u32, max_index: u32) -> Result<()> {
        let title = format!(
            concat!(env!("CARGO_PKG_NAME"), " update {} of {}"),
            index, max_index
        );

        let alert = match &self.feed.alert {
            Some(alert) => Cow::Owned(format!("\nalert: {}", alert)),
            None => Cow::Borrowed(""),
        };

        let body = format!(
            "{abbrev} | {name}\n{listeners} (^{jump}){alert}",
            abbrev = self.feed.location.abbrev(),
            name = self.feed.name,
            listeners = self.feed.listeners,
            jump = self.jump as i32,
            alert = alert,
        );

        Notification::new()
            .summary(&title)
            .body(&body)
            .show()
            .context(err::CreateNotif)
            .map(|_| ())
    }

    pub fn sort_all(notifs: &mut [Self], config: &Config) {
        use crate::config::{SortOrder, SortType};

        notifs.sort_unstable_by(|x, y| {
            let (x, y) = match config.sorting.order {
                SortOrder::Ascending => (x, y),
                SortOrder::Descending => (y, x),
            };

            match config.sorting.value {
                SortType::Listeners => x.feed.listeners.cmp(&y.feed.listeners),
                SortType::Jump => {
                    let x_jump = x.jump as i32;
                    let y_jump = y.jump as i32;

                    x_jump.cmp(&y_jump)
                }
            }
        });
    }

    pub fn show_all(notifs: &[Self]) -> Result<()> {
        let num_notifs = notifs.len() as u32;

        for (i, notif) in notifs.iter().enumerate() {
            notif.show_notif(1 + i as u32, num_notifs)?;
        }

        Ok(())
    }
}

/// List of all states / provinces / territories on Broadcastify that have a significant feed presence or airport feeds.
/// Every location can be mapped to its state ID as it appears on Broadcastify.
///
/// Airport feeds are used as a factor for being on this list because there's a chance of a major event occuring on them, at least more so than just amateur radio stations.
#[derive(Copy, Clone, Debug, EnumString, Eq, FromPrimitive, Hash, PartialEq)]
#[strum(serialize_all = "kebab_case")]
#[repr(u32)]
pub enum Location {
    // United States
    UsAlabama = 1,
    UsAlaska,
    UsArizona = 4,
    UsArkansas,
    UsCalifornia,
    UsColorado = 8,
    UsConnecticut,
    UsDelaware,
    UsDistrictOfColumbia,
    UsFlorida,
    UsGeorgia,
    UsHawaii = 15,
    UsIdaho,
    UsIllinois,
    UsIndiana,
    UsIowa,
    UsKansas,
    UsKentucky,
    UsLouisiana,
    UsMaine,
    UsMaryland,
    UsMassachusetts,
    UsMichigan,
    UsMinnesota,
    UsMississippi,
    UsMissouri,
    UsMontana,
    UsNebraska,
    UsNevada,
    UsNewHampshire,
    UsNewJersey,
    UsNewMexico,
    UsNewYork,
    UsNorthCarolina,
    UsNorthDakota,
    UsOhio,
    UsOklahoma,
    UsOregon,
    UsPennsylvania,
    UsRhodeIsland = 44,
    UsSouthCarolina,
    UsSouthDakota,
    UsTennessee,
    UsTexas,
    UsUtah,
    UsVermont,
    UsVirginia,
    UsWashington = 53,
    UsWestVirginia,
    UsWisconsin,
    UsWyoming,
    // Canada
    CaAlberta = 101,
    CaBritishColumbia,
    CaManitoba,
    CaNewBrunswick,
    CaNewfoundland,
    CaNorthwestTerritories,
    CaNovaScotia,
    CaNunavut,
    CaOntario,
    CaPrinceEdwardIsland,
    CaQuebec,
    CaSaskatchewan,
    CaYukon,
    // Austrailia
    AuWesternAustrailia = 151,
    AuVictoria,
    AuSouthAustrailia,
    AuNorthernTerritory,
    AuQueensland,
    AuTasmania,
    AuNewSouthWales,
    AuAustralianCapitalTerritory,
    // Netherlands
    NlCountrywide = 223,
    NlDrenthe = 688,
    NlFlevoland,
    NlFriesland,
    NlGelderland,
    NlGroningen,
    NlLimburg,
    NlNoordBrabant,
    NlNoordHolland,
    NlOverijssel,
    NlUtrecht,
    NlZeeland,
    NlZuidHolland,
    // Malaysia
    MyCountrywide = 231,
    // Luxembourg
    LuCountrywide = 252,
    // Brazil
    BrCountrywide = 345,
    // Chile
    ClValparaiso = 714,
    ClBiobio = 717,
    ClAraucania,
    ClSantiago = 723,
}

impl Location {
    const LISTED_COUNTRIES: [&'static str; 8] = [
        "Austrailia",
        "Brazil",
        "Canada",
        "Chile",
        "Luxembourg",
        "Malaysia",
        "Netherlands",
        "United States",
    ];

    #[inline(always)]
    pub fn id(self) -> u32 {
        self as u32
    }

    pub fn abbrev(self) -> &'static str {
        match self {
            // United States
            Self::UsAlabama => "US-AL",
            Self::UsAlaska => "US-AK",
            Self::UsArizona => "US-AZ",
            Self::UsArkansas => "US-AR",
            Self::UsCalifornia => "US-CA",
            Self::UsColorado => "US-CO",
            Self::UsConnecticut => "US-CT",
            Self::UsDelaware => "US-DE",
            Self::UsDistrictOfColumbia => "US-DC",
            Self::UsFlorida => "US-FL",
            Self::UsGeorgia => "US-GA",
            Self::UsHawaii => "US-HI",
            Self::UsIdaho => "US-ID",
            Self::UsIllinois => "US-IL",
            Self::UsIndiana => "US-IN",
            Self::UsIowa => "US-IA",
            Self::UsKansas => "US-KS",
            Self::UsKentucky => "US-KY",
            Self::UsLouisiana => "US-LA",
            Self::UsMaine => "US-ME",
            Self::UsMaryland => "US-MD",
            Self::UsMassachusetts => "US-MA",
            Self::UsMichigan => "US-MI",
            Self::UsMinnesota => "US-MN",
            Self::UsMississippi => "US-MS",
            Self::UsMissouri => "US-MO",
            Self::UsMontana => "US-MT",
            Self::UsNebraska => "US-NE",
            Self::UsNevada => "US-NV",
            Self::UsNewHampshire => "US-NH",
            Self::UsNewJersey => "US-NJ",
            Self::UsNewMexico => "US-NM",
            Self::UsNewYork => "US-NY",
            Self::UsNorthCarolina => "US-NC",
            Self::UsNorthDakota => "US-ND",
            Self::UsOhio => "US-OH",
            Self::UsOklahoma => "US-OK",
            Self::UsOregon => "US-OR",
            Self::UsPennsylvania => "US-PA",
            Self::UsRhodeIsland => "US-RI",
            Self::UsSouthCarolina => "US-SC",
            Self::UsSouthDakota => "US-SD",
            Self::UsTennessee => "US-TN",
            Self::UsTexas => "US-TX",
            Self::UsUtah => "US-UT",
            Self::UsVermont => "US-VT",
            Self::UsVirginia => "US-VA",
            Self::UsWashington => "US-WA",
            Self::UsWestVirginia => "US-WV",
            Self::UsWisconsin => "US-WI",
            Self::UsWyoming => "US-WY",
            // Canada
            Self::CaAlberta => "CA-AB",
            Self::CaBritishColumbia => "CA-BC",
            Self::CaManitoba => "CA-MB",
            Self::CaNewBrunswick => "CA-NB",
            Self::CaNewfoundland => "CA-NL",
            Self::CaNorthwestTerritories => "CA-NT",
            Self::CaNovaScotia => "CA-NS",
            Self::CaNunavut => "CA-NU",
            Self::CaOntario => "CA-ON",
            Self::CaPrinceEdwardIsland => "CA-PE",
            Self::CaQuebec => "CA-QC",
            Self::CaSaskatchewan => "CA-SK",
            Self::CaYukon => "CA-YT",
            // Austrailia
            Self::AuWesternAustrailia => "AU-WA",
            Self::AuVictoria => "AU-VIC",
            Self::AuSouthAustrailia => "AU-SA",
            Self::AuNorthernTerritory => "AU-NT",
            Self::AuQueensland => "AU-QLD",
            Self::AuTasmania => "AU-TAS",
            Self::AuNewSouthWales => "AU-NSW",
            Self::AuAustralianCapitalTerritory => "AU-ACT",
            // Netherlands
            Self::NlCountrywide => "NL-ALL",
            Self::NlDrenthe => "NL-DR",
            Self::NlFlevoland => "NL-FL",
            Self::NlFriesland => "NL-FR",
            Self::NlGelderland => "NL-GE",
            Self::NlGroningen => "NL-GR",
            Self::NlLimburg => "NL-LI",
            Self::NlNoordBrabant => "NL-NB",
            Self::NlNoordHolland => "NL-NH",
            Self::NlOverijssel => "NL-OV",
            Self::NlUtrecht => "NL-UT",
            Self::NlZeeland => "NL-ZE",
            Self::NlZuidHolland => "NL-ZH",
            // Malaysia
            Self::MyCountrywide => "MY-ALL",
            // Luxembourg
            Self::LuCountrywide => "LU-ALL",
            // Brazil
            Self::BrCountrywide => "BR-ALL",
            // Chile
            Self::ClValparaiso => "CL-VL",
            Self::ClBiobio => "CL-BI",
            Self::ClAraucania => "CL-AR",
            Self::ClSantiago => "CL-RM",
        }
    }
}

impl<'de> Deserialize<'de> for Location {
    fn deserialize<D>(deserializer: D) -> result::Result<Self, D::Error>
    where
        D: Deserializer<'de>,
    {
        struct LocationVisitor;

        impl<'de> Visitor<'de> for LocationVisitor {
            type Value = Location;

            fn expecting(&self, formatter: &mut fmt::Formatter) -> fmt::Result {
                let country_list = Location::LISTED_COUNTRIES.join("\n\t");
                formatter.write_str(&format!("a state / province / territory located in one of the following countries:\n\t{}\n", country_list))
            }

            fn visit_str<E>(self, value: &str) -> result::Result<Self::Value, E>
            where
                E: serde::de::Error,
            {
                use serde::de::{self, Unexpected};

                Location::from_str(value)
                    .map_err(|_| de::Error::invalid_value(Unexpected::Str(value), &self))
            }
        }

        deserializer.deserialize_str(LocationVisitor)
    }
}
<commit_msg>cache ureq requests<commit_after>pub mod stats;

mod scrape;

use crate::config::Config;
use crate::err::{self, Result};
use notify_rust::Notification;
use once_cell::sync::Lazy;
use serde::de::Visitor;
use serde::{Deserialize, Deserializer};
use snafu::ResultExt;
use stats::ListenerStats;
use std::borrow::Cow;
use std::cmp::{self, Eq, Ord};
use std::fmt;
use std::result;
use std::str::FromStr;
use strum_macros::EnumString;

#[derive(Debug)]
pub struct Feed<'a> {
    pub id: u32,
    pub name: String,
    pub listeners: u32,
    pub location: Location,
    pub county: Cow<'a, str>,
    pub alert: Option<String>,
}

impl<'a> Feed<'a> {
    pub fn scrape_all(config: &Config) -> Result<Vec<Self>> {
        let mut feeds = Self::scrape_source(Source::Top50, config.misc.minimum_listeners)?;

        if let Some(loc) = config.misc.location {
            let loc_feeds =
                Self::scrape_source(Source::Location(loc), config.misc.minimum_listeners)?;

            feeds.extend(loc_feeds);
        }

        feeds.sort_unstable();
        feeds.dedup();

        Ok(feeds)
    }

    fn scrape_source(source: Source, min_listeners: u32) -> Result<Vec<Self>> {
        static REQ_AGENT: Lazy<ureq::Agent> = Lazy::new(|| {
            let mut agent = ureq::agent();
            agent.set("Connection", "keep-alive");
            agent
        });

        let resp = REQ_AGENT
            .get(source.url().as_ref())
            .timeout_connect(15_000)
            .call();

        if let Some(err) = resp.synthetic_error() {
            return Err(err.into());
        }

        let body = resp.into_string().context(err::HttpIO)?;

        match source {
            Source::Top50 => scrape::scrape_top(&body, min_listeners).context(err::ParseTopFeeds),
            Source::Location(location) => scrape::scrape_location(&body, min_listeners, location)
                .context(err::ParseLocationFeeds { location }),
        }
    }
}

impl<'a> PartialEq for Feed<'a> {
    fn eq(&self, other: &Self) -> bool {
        self.id == other.id
    }
}

impl<'a> Eq for Feed<'a> {}

impl<'a> PartialOrd for Feed<'a> {
    fn partial_cmp(&self, other: &Self) -> Option<cmp::Ordering> {
        Some(self.cmp(other))
    }
}

impl<'a> Ord for Feed<'a> {
    fn cmp(&self, other: &Self) -> cmp::Ordering {
        self.id.cmp(&other.id)
    }
}

pub enum Source {
    Top50,
    Location(Location),
}

impl Source {
    pub fn url(&self) -> Cow<str> {
        match self {
            Self::Top50 => "https://www.broadcastify.com/listen/top".into(),
            Self::Location(loc) => {
                format!("https://www.broadcastify.com/listen/stid/{}", loc.id()).into()
            }
        }
    }
}

#[derive(Debug)]
pub struct FeedNotif<'a> {
    pub feed: Feed<'a>,
    pub jump: f32,
}

impl<'a> FeedNotif<'a> {
    pub fn new(feed: Feed<'a>, stats: &ListenerStats) -> Self {
        Self {
            feed,
            jump: stats.jump,
        }
    }

    pub fn show_notif(&self, index: u32, max_index: u32) -> Result<()> {
        let title = format!(
            concat!(env!("CARGO_PKG_NAME"), " update {} of {}"),
            index, max_index
        );

        let alert = match &self.feed.alert {
            Some(alert) => Cow::Owned(format!("\nalert: {}", alert)),
            None => Cow::Borrowed(""),
        };

        let body = format!(
            "{abbrev} | {name}\n{listeners} (^{jump}){alert}",
            abbrev = self.feed.location.abbrev(),
            name = self.feed.name,
            listeners = self.feed.listeners,
            jump = self.jump as i32,
            alert = alert,
        );

        Notification::new()
            .summary(&title)
            .body(&body)
            .show()
            .context(err::CreateNotif)
            .map(|_| ())
    }

    pub fn sort_all(notifs: &mut [Self], config: &Config) {
        use crate::config::{SortOrder, SortType};

        notifs.sort_unstable_by(|x, y| {
            let (x, y) = match config.sorting.order {
                SortOrder::Ascending => (x, y),
                SortOrder::Descending => (y, x),
            };

            match config.sorting.value {
                SortType::Listeners => x.feed.listeners.cmp(&y.feed.listeners),
                SortType::Jump => {
                    let x_jump = x.jump as i32;
                    let y_jump = y.jump as i32;

                    x_jump.cmp(&y_jump)
                }
            }
        });
    }

    pub fn show_all(notifs: &[Self]) -> Result<()> {
        let num_notifs = notifs.len() as u32;

        for (i, notif) in notifs.iter().enumerate() {
            notif.show_notif(1 + i as u32, num_notifs)?;
        }

        Ok(())
    }
}

/// List of all states / provinces / territories on Broadcastify that have a significant feed presence or airport feeds.
/// Every location can be mapped to its state ID as it appears on Broadcastify.
///
/// Airport feeds are used as a factor for being on this list because there's a chance of a major event occuring on them, at least more so than just amateur radio stations.
#[derive(Copy, Clone, Debug, EnumString, Eq, FromPrimitive, Hash, PartialEq)]
#[strum(serialize_all = "kebab_case")]
#[repr(u32)]
pub enum Location {
    // United States
    UsAlabama = 1,
    UsAlaska,
    UsArizona = 4,
    UsArkansas,
    UsCalifornia,
    UsColorado = 8,
    UsConnecticut,
    UsDelaware,
    UsDistrictOfColumbia,
    UsFlorida,
    UsGeorgia,
    UsHawaii = 15,
    UsIdaho,
    UsIllinois,
    UsIndiana,
    UsIowa,
    UsKansas,
    UsKentucky,
    UsLouisiana,
    UsMaine,
    UsMaryland,
    UsMassachusetts,
    UsMichigan,
    UsMinnesota,
    UsMississippi,
    UsMissouri,
    UsMontana,
    UsNebraska,
    UsNevada,
    UsNewHampshire,
    UsNewJersey,
    UsNewMexico,
    UsNewYork,
    UsNorthCarolina,
    UsNorthDakota,
    UsOhio,
    UsOklahoma,
    UsOregon,
    UsPennsylvania,
    UsRhodeIsland = 44,
    UsSouthCarolina,
    UsSouthDakota,
    UsTennessee,
    UsTexas,
    UsUtah,
    UsVermont,
    UsVirginia,
    UsWashington = 53,
    UsWestVirginia,
    UsWisconsin,
    UsWyoming,
    // Canada
    CaAlberta = 101,
    CaBritishColumbia,
    CaManitoba,
    CaNewBrunswick,
    CaNewfoundland,
    CaNorthwestTerritories,
    CaNovaScotia,
    CaNunavut,
    CaOntario,
    CaPrinceEdwardIsland,
    CaQuebec,
    CaSaskatchewan,
    CaYukon,
    // Austrailia
    AuWesternAustrailia = 151,
    AuVictoria,
    AuSouthAustrailia,
    AuNorthernTerritory,
    AuQueensland,
    AuTasmania,
    AuNewSouthWales,
    AuAustralianCapitalTerritory,
    // Netherlands
    NlCountrywide = 223,
    NlDrenthe = 688,
    NlFlevoland,
    NlFriesland,
    NlGelderland,
    NlGroningen,
    NlLimburg,
    NlNoordBrabant,
    NlNoordHolland,
    NlOverijssel,
    NlUtrecht,
    NlZeeland,
    NlZuidHolland,
    // Malaysia
    MyCountrywide = 231,
    // Luxembourg
    LuCountrywide = 252,
    // Brazil
    BrCountrywide = 345,
    // Chile
    ClValparaiso = 714,
    ClBiobio = 717,
    ClAraucania,
    ClSantiago = 723,
}

impl Location {
    const LISTED_COUNTRIES: [&'static str; 8] = [
        "Austrailia",
        "Brazil",
        "Canada",
        "Chile",
        "Luxembourg",
        "Malaysia",
        "Netherlands",
        "United States",
    ];

    #[inline(always)]
    pub fn id(self) -> u32 {
        self as u32
    }

    pub fn abbrev(self) -> &'static str {
        match self {
            // United States
            Self::UsAlabama => "US-AL",
            Self::UsAlaska => "US-AK",
            Self::UsArizona => "US-AZ",
            Self::UsArkansas => "US-AR",
            Self::UsCalifornia => "US-CA",
            Self::UsColorado => "US-CO",
            Self::UsConnecticut => "US-CT",
            Self::UsDelaware => "US-DE",
            Self::UsDistrictOfColumbia => "US-DC",
            Self::UsFlorida => "US-FL",
            Self::UsGeorgia => "US-GA",
            Self::UsHawaii => "US-HI",
            Self::UsIdaho => "US-ID",
            Self::UsIllinois => "US-IL",
            Self::UsIndiana => "US-IN",
            Self::UsIowa => "US-IA",
            Self::UsKansas => "US-KS",
            Self::UsKentucky => "US-KY",
            Self::UsLouisiana => "US-LA",
            Self::UsMaine => "US-ME",
            Self::UsMaryland => "US-MD",
            Self::UsMassachusetts => "US-MA",
            Self::UsMichigan => "US-MI",
            Self::UsMinnesota => "US-MN",
            Self::UsMississippi => "US-MS",
            Self::UsMissouri => "US-MO",
            Self::UsMontana => "US-MT",
            Self::UsNebraska => "US-NE",
            Self::UsNevada => "US-NV",
            Self::UsNewHampshire => "US-NH",
            Self::UsNewJersey => "US-NJ",
            Self::UsNewMexico => "US-NM",
            Self::UsNewYork => "US-NY",
            Self::UsNorthCarolina => "US-NC",
            Self::UsNorthDakota => "US-ND",
            Self::UsOhio => "US-OH",
            Self::UsOklahoma => "US-OK",
            Self::UsOregon => "US-OR",
            Self::UsPennsylvania => "US-PA",
            Self::UsRhodeIsland => "US-RI",
            Self::UsSouthCarolina => "US-SC",
            Self::UsSouthDakota => "US-SD",
            Self::UsTennessee => "US-TN",
            Self::UsTexas => "US-TX",
            Self::UsUtah => "US-UT",
            Self::UsVermont => "US-VT",
            Self::UsVirginia => "US-VA",
            Self::UsWashington => "US-WA",
            Self::UsWestVirginia => "US-WV",
            Self::UsWisconsin => "US-WI",
            Self::UsWyoming => "US-WY",
            // Canada
            Self::CaAlberta => "CA-AB",
            Self::CaBritishColumbia => "CA-BC",
            Self::CaManitoba => "CA-MB",
            Self::CaNewBrunswick => "CA-NB",
            Self::CaNewfoundland => "CA-NL",
            Self::CaNorthwestTerritories => "CA-NT",
            Self::CaNovaScotia => "CA-NS",
            Self::CaNunavut => "CA-NU",
            Self::CaOntario => "CA-ON",
            Self::CaPrinceEdwardIsland => "CA-PE",
            Self::CaQuebec => "CA-QC",
            Self::CaSaskatchewan => "CA-SK",
            Self::CaYukon => "CA-YT",
            // Austrailia
            Self::AuWesternAustrailia => "AU-WA",
            Self::AuVictoria => "AU-VIC",
            Self::AuSouthAustrailia => "AU-SA",
            Self::AuNorthernTerritory => "AU-NT",
            Self::AuQueensland => "AU-QLD",
            Self::AuTasmania => "AU-TAS",
            Self::AuNewSouthWales => "AU-NSW",
            Self::AuAustralianCapitalTerritory => "AU-ACT",
            // Netherlands
            Self::NlCountrywide => "NL-ALL",
            Self::NlDrenthe => "NL-DR",
            Self::NlFlevoland => "NL-FL",
            Self::NlFriesland => "NL-FR",
            Self::NlGelderland => "NL-GE",
            Self::NlGroningen => "NL-GR",
            Self::NlLimburg => "NL-LI",
            Self::NlNoordBrabant => "NL-NB",
            Self::NlNoordHolland => "NL-NH",
            Self::NlOverijssel => "NL-OV",
            Self::NlUtrecht => "NL-UT",
            Self::NlZeeland => "NL-ZE",
            Self::NlZuidHolland => "NL-ZH",
            // Malaysia
            Self::MyCountrywide => "MY-ALL",
            // Luxembourg
            Self::LuCountrywide => "LU-ALL",
            // Brazil
            Self::BrCountrywide => "BR-ALL",
            // Chile
            Self::ClValparaiso => "CL-VL",
            Self::ClBiobio => "CL-BI",
            Self::ClAraucania => "CL-AR",
            Self::ClSantiago => "CL-RM",
        }
    }
}

impl<'de> Deserialize<'de> for Location {
    fn deserialize<D>(deserializer: D) -> result::Result<Self, D::Error>
    where
        D: Deserializer<'de>,
    {
        struct LocationVisitor;

        impl<'de> Visitor<'de> for LocationVisitor {
            type Value = Location;

            fn expecting(&self, formatter: &mut fmt::Formatter) -> fmt::Result {
                let country_list = Location::LISTED_COUNTRIES.join("\n\t");
                formatter.write_str(&format!("a state / province / territory located in one of the following countries:\n\t{}\n", country_list))
            }

            fn visit_str<E>(self, value: &str) -> result::Result<Self::Value, E>
            where
                E: serde::de::Error,
            {
                use serde::de::{self, Unexpected};

                Location::from_str(value)
                    .map_err(|_| de::Error::invalid_value(Unexpected::Str(value), &self))
            }
        }

        deserializer.deserialize_str(LocationVisitor)
    }
}
<|endoftext|>


<commit_before>use file::File;
use std::io;

use ansi_term::{Paint, Colour, Plain, Style, Red, Green, Yellow, Blue, Cyan, Fixed};

static Grey: Colour = Fixed(244);

pub enum FileType {
    Normal, Directory, Executable, Immediate, Compiled, Symlink, Special,
    Image, Video, Music, Lossless, Compressed, Document, Temp, Crypto,
}

static IMAGE_TYPES: &'static [&'static str] = &[
    "png", "jpeg", "jpg", "gif", "bmp", "tiff", "tif",
    "ppm", "pgm", "pbm", "pnm", "webp", "raw", "arw",
    "svg", "stl", "eps", "dvi", "ps", "cbr",
    "cbz", "xpm", "ico" ];

static VIDEO_TYPES: &'static [&'static str] = &[
    "avi", "flv", "m2v", "mkv", "mov", "mp4", "mpeg",
    "mpg", "ogm", "ogv", "vob", "wmv" ];

static MUSIC_TYPES: &'static [&'static str] = &[
    "aac", "m4a", "mp3", "ogg" ];

static MUSIC_LOSSLESS: &'static [&'static str] = &[
    "alac", "ape", "flac", "wav" ];

static COMPRESSED_TYPES: &'static [&'static str] = &[
    "zip", "tar", "Z", "gz", "bz2", "a", "ar", "7z",
    "iso", "dmg", "tc", "rar", "par" ];

static DOCUMENT_TYPES: &'static [&'static str] = &[
    "djvu", "doc", "docx", "eml", "eps", "odp", "ods",
    "odt", "pdf", "ppt", "pptx", "xls", "xlsx" ];

static TEMP_TYPES: &'static [&'static str] = &[
    "tmp", "swp", "swo", "swn", "bak" ];

static CRYPTO_TYPES: &'static [&'static str] = &[
    "asc", "gpg", "sig", "signature", "pgp" ];

static COMPILED_TYPES: &'static [&'static str] = &[
    "class", "elc", "hi", "o", "pyc" ];

static BUILD_TYPES: &'static [&'static str] = &[
    "Makefile", "Cargo.toml", "SConstruct", "CMakeLists.txt",
    "build.gradle", "Rakefile", "Gruntfile.js",
    "Gruntfile.coffee" ];

impl FileType {
    pub fn style(&self) -> Style {
        match *self {
            Normal => Plain,
            Directory => Blue.bold(),
            Symlink => Cyan.normal(),
            Special => Yellow.normal(),
            Executable => Green.bold(),
            Image => Fixed(133).normal(),
            Video => Fixed(135).normal(),
            Music => Fixed(92).normal(),
            Lossless => Fixed(93).normal(),
            Crypto => Fixed(109).normal(),
            Document => Fixed(105).normal(),
            Compressed => Red.normal(),
            Temp => Grey.normal(),
            Immediate => Yellow.bold().underline(),
            Compiled => Fixed(137).normal(),
        }
    }
}

pub trait HasType {
    fn get_type(&self) -> FileType;
}

impl<'a> HasType for File<'a> {
    fn get_type(&self) -> FileType {
        let name = self.name.as_slice();
        if self.stat.kind == io::TypeDirectory {
            return Directory;
        }
        else if self.stat.kind == io::TypeSymlink {
            return Symlink;
        }
        else if self.stat.kind == io::TypeBlockSpecial || self.stat.kind == io::TypeNamedPipe || self.stat.kind == io::TypeUnknown {
            return Special;
        }
        else if self.stat.perm.contains(io::UserExecute) {
            return Executable;
        }
        else if name.starts_with("README") || BUILD_TYPES.iter().any(|&s| s == name) {
            return Immediate;
        }
        else if self.ext.is_some() {
            let e = self.ext.clone().unwrap();
            let ext = e.as_slice();
            if IMAGE_TYPES.iter().any(|&s| s == ext) {
                return Image;
            }
            else if VIDEO_TYPES.iter().any(|&s| s == ext) {
                return Video;
            }
            else if MUSIC_TYPES.iter().any(|&s| s == ext) {
                return Music;
            }
            else if MUSIC_LOSSLESS.iter().any(|&s| s == ext) {
                return Lossless;
            }
            else if CRYPTO_TYPES.iter().any(|&s| s == ext) {
                return Crypto;
            }
            else if DOCUMENT_TYPES.iter().any(|&s| s == ext) {
                return Document;
            }
            else if COMPRESSED_TYPES.iter().any(|&s| s == ext) {
                return Compressed;
            }
            else if self.is_tmpfile() || TEMP_TYPES.iter().any(|&s| s == ext) {
                return Temp;
            }

            let source_files = self.get_source_files();
            if source_files.len() == 0 {
                return Normal;
            }
            else if source_files.iter().any(|path| self.dir.contains(path)) {
                return Temp;
            }
            else {
                if COMPILED_TYPES.iter().any(|&s| s == ext) {
                    return Compiled;
                }
                else {
                    return Normal;
                }
            }
        }
        return Normal;  // no filetype
    }
}
<commit_msg>Match file extensions case-insensitively<commit_after>use file::File;
use std::io;
use std::ascii::StrAsciiExt;

use ansi_term::{Paint, Colour, Plain, Style, Red, Green, Yellow, Blue, Cyan, Fixed};

static Grey: Colour = Fixed(244);

pub enum FileType {
    Normal, Directory, Executable, Immediate, Compiled, Symlink, Special,
    Image, Video, Music, Lossless, Compressed, Document, Temp, Crypto,
}

static IMAGE_TYPES: &'static [&'static str] = &[
    "png", "jpeg", "jpg", "gif", "bmp", "tiff", "tif",
    "ppm", "pgm", "pbm", "pnm", "webp", "raw", "arw",
    "svg", "stl", "eps", "dvi", "ps", "cbr",
    "cbz", "xpm", "ico" ];

static VIDEO_TYPES: &'static [&'static str] = &[
    "avi", "flv", "m2v", "mkv", "mov", "mp4", "mpeg",
    "mpg", "ogm", "ogv", "vob", "wmv" ];

static MUSIC_TYPES: &'static [&'static str] = &[
    "aac", "m4a", "mp3", "ogg" ];

static MUSIC_LOSSLESS: &'static [&'static str] = &[
    "alac", "ape", "flac", "wav" ];

static COMPRESSED_TYPES: &'static [&'static str] = &[
    "zip", "tar", "Z", "gz", "bz2", "a", "ar", "7z",
    "iso", "dmg", "tc", "rar", "par" ];

static DOCUMENT_TYPES: &'static [&'static str] = &[
    "djvu", "doc", "docx", "eml", "eps", "odp", "ods",
    "odt", "pdf", "ppt", "pptx", "xls", "xlsx" ];

static TEMP_TYPES: &'static [&'static str] = &[
    "tmp", "swp", "swo", "swn", "bak" ];

static CRYPTO_TYPES: &'static [&'static str] = &[
    "asc", "gpg", "sig", "signature", "pgp" ];

static COMPILED_TYPES: &'static [&'static str] = &[
    "class", "elc", "hi", "o", "pyc" ];

static BUILD_TYPES: &'static [&'static str] = &[
    "Makefile", "Cargo.toml", "SConstruct", "CMakeLists.txt",
    "build.gradle", "Rakefile", "Gruntfile.js",
    "Gruntfile.coffee" ];

impl FileType {
    pub fn style(&self) -> Style {
        match *self {
            Normal => Plain,
            Directory => Blue.bold(),
            Symlink => Cyan.normal(),
            Special => Yellow.normal(),
            Executable => Green.bold(),
            Image => Fixed(133).normal(),
            Video => Fixed(135).normal(),
            Music => Fixed(92).normal(),
            Lossless => Fixed(93).normal(),
            Crypto => Fixed(109).normal(),
            Document => Fixed(105).normal(),
            Compressed => Red.normal(),
            Temp => Grey.normal(),
            Immediate => Yellow.bold().underline(),
            Compiled => Fixed(137).normal(),
        }
    }
}

pub trait HasType {
    fn get_type(&self) -> FileType;
}

impl<'a> HasType for File<'a> {
    fn get_type(&self) -> FileType {
        let name = self.name.as_slice();
        if self.stat.kind == io::TypeDirectory {
            return Directory;
        }
        else if self.stat.kind == io::TypeSymlink {
            return Symlink;
        }
        else if self.stat.kind == io::TypeBlockSpecial || self.stat.kind == io::TypeNamedPipe || self.stat.kind == io::TypeUnknown {
            return Special;
        }
        else if self.stat.perm.contains(io::UserExecute) {
            return Executable;
        }
        else if name.starts_with("README") || BUILD_TYPES.iter().any(|&s| s == name) {
            return Immediate;
        }
        else if self.ext.is_some() {
            let e = self.ext.clone().unwrap().as_slice().to_ascii_lower();
            let ext = e.as_slice();
            if IMAGE_TYPES.iter().any(|&s| s == ext) {
                return Image;
            }
            else if VIDEO_TYPES.iter().any(|&s| s == ext) {
                return Video;
            }
            else if MUSIC_TYPES.iter().any(|&s| s == ext) {
                return Music;
            }
            else if MUSIC_LOSSLESS.iter().any(|&s| s == ext) {
                return Lossless;
            }
            else if CRYPTO_TYPES.iter().any(|&s| s == ext) {
                return Crypto;
            }
            else if DOCUMENT_TYPES.iter().any(|&s| s == ext) {
                return Document;
            }
            else if COMPRESSED_TYPES.iter().any(|&s| s == ext) {
                return Compressed;
            }
            else if self.is_tmpfile() || TEMP_TYPES.iter().any(|&s| s == ext) {
                return Temp;
            }

            let source_files = self.get_source_files();
            if source_files.len() == 0 {
                return Normal;
            }
            else if source_files.iter().any(|path| self.dir.contains(path)) {
                return Temp;
            }
            else {
                if COMPILED_TYPES.iter().any(|&s| s == ext) {
                    return Compiled;
                }
                else {
                    return Normal;
                }
            }
        }
        return Normal;  // no filetype
    }
}
<|endoftext|>


<commit_before>#![crate_id="gdal#0.1"]
#![crate_type="lib"]

extern crate sync;
extern crate libc;
#[cfg(test)] extern crate test;

use std::str::raw;
use libc::c_char;

pub mod raster;
pub mod proj;
pub mod geom;
pub mod warp;

#[link(name="gdal")]
extern {
    fn GDALVersionInfo(key: *c_char) -> *c_char;
}

pub fn version_info(key: &str) -> String {
    let info = key.with_c_str(|c_key| {
        unsafe {
            let rv = GDALVersionInfo(c_key);
            return raw::from_c_str(rv);
        };
    });
    return info;
}

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

    #[test]
    fn test_version_info() {
        let release_date = version_info("RELEASE_DATE");
        let release_name = version_info("RELEASE_NAME");
        let version_text = version_info("--version");

        let expected_text: String = format!(
            "GDAL {}, released {}/{}/{}",
            release_name,
            release_date.as_slice().slice(0, 4),
            release_date.as_slice().slice(4, 6),
            release_date.as_slice().slice(6, 8),
        );

        assert_eq!(version_text.into_owned(), expected_text);
    }
}
<commit_msg>fix warning<commit_after>#![crate_id="gdal#0.1"]
#![crate_type="lib"]

extern crate sync;
extern crate libc;
#[cfg(test)] extern crate test;

use std::str::raw;
use libc::c_char;

pub mod raster;
pub mod proj;
pub mod geom;
pub mod warp;

#[link(name="gdal")]
extern {
    fn GDALVersionInfo(key: *c_char) -> *c_char;
}

pub fn version_info(key: &str) -> String {
    let info = key.with_c_str(|c_key| {
        unsafe {
            let rv = GDALVersionInfo(c_key);
            return raw::from_c_str(rv);
        };
    });
    return info;
}

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

    #[test]
    fn test_version_info() {
        let release_date = version_info("RELEASE_DATE");
        let release_name = version_info("RELEASE_NAME");
        let version_text = version_info("--version");

        let expected_text: String = format!(
            "GDAL {}, released {}/{}/{}",
            release_name,
            release_date.as_slice().slice(0, 4),
            release_date.as_slice().slice(4, 6),
            release_date.as_slice().slice(6, 8),
        );

        assert_eq!(version_text.into_string(), expected_text);
    }
}
<|endoftext|>