<commit_before>//
// zhttpto.rs
//
// Starting code for PS1
// Running on Rust 0.9
//
// Note that this code has serious security risks!  You should not run it
// on any system with access to sensitive files.
//
// University of Virginia - cs4414 Spring 2014
// Weilin Xu and David Evans
// Version 0.3

#[feature(globs)];
use std::io::*;
use std::io::net::ip::{SocketAddr};
use std::{str};

static IP: &'static str = "127.0.0.1";
static PORT:        int = 4414;
static mut count:   int = 0;

fn main() {
    let addr = from_str::<SocketAddr>(format!("{:s}:{:d}", IP, PORT)).unwrap();
    let mut acceptor = net::tcp::TcpListener::bind(addr).listen();

    println(format!("Listening on [{:s}] ...", addr.to_str()));

    for stream in acceptor.incoming() {
        // Spawn a task to handle the connection
        do spawn {
            let mut stream = stream;

            match stream {
                Some(ref mut s) => {
                             match s.peer_name() {
                                Some(pn) => {
					println(format!("Received connection from: [{:s}]", pn.to_str()));
					unsafe {
						count = count + 1;
						println!("Total number of requests received: {:d}", count)
					}
				},
                                None => ()
                             }
                           },
                None => ()
            }

            let mut buf = [0, ..500];
            stream.read(buf);
            let request_str = str::from_utf8(buf);
            println(format!("Received request :\n{:s}", request_str));
            let mut components : ~[~str] = ~[];
            for tempString in request_str.split(' ') {
            	components.push(tempString.to_owned());
            }
            if !str::eq(&components[1], &~"/") {
            	if components[1].len() > 4 && str::eq(&components[1].slice_from(components[1].len()-5).to_owned(), &~".html") {
        	let f1name = components[1].slice_from(1);
        	let path1 = Path::new(f1name.clone());
        	let mut msg_file1 = File::open(&path1);
                let msg_bytes1: ~[u8] = msg_file1.read_to_end();
		stream.write(msg_bytes1);
		}
		else {
            let response: ~str =
                 ~"HTTP/1.1 403 Forbidden\r\nContent-Type: text/html; charset=UTF-8\r\n\r\n
                 <doctype !html><title>403 Forbidden</title>

403: Forbidden

You do not have permission to access that file";
		stream.write(response.as_bytes());
		}
            }
            else {
            unsafe {
            let response: ~str =
                 ("HTTP/1.1 200 OK\r\nContent-Type: text/html; charset=UTF-8\r\n\r\n
                 <doctype !html><title>Hello, Rust!</title>

Greetings, Krusty!

" + format!("Total Number of Requests: {:d}
", count) +
                 "\r\n");
            stream.write(response.as_bytes());
            }
            println!("Connection terminates.");
            }
        }
    }
}
<commit_msg>Server now checks for ../ in the file path<commit_after>//
// zhttpto.rs
//
// Starting code for PS1
// Running on Rust 0.9
//
// Note that this code has serious security risks!  You should not run it
// on any system with access to sensitive files.
//
// University of Virginia - cs4414 Spring 2014
// Weilin Xu and David Evans
// Version 0.3

#[feature(globs)];
use std::io::*;
use std::io::net::ip::{SocketAddr};
use std::{str};

static IP: &'static str = "127.0.0.1";
static PORT:        int = 4414;
static mut count:   int = 0;

fn main() {
    let addr = from_str::<SocketAddr>(format!("{:s}:{:d}", IP, PORT)).unwrap();
    let mut acceptor = net::tcp::TcpListener::bind(addr).listen();

    println(format!("Listening on [{:s}] ...", addr.to_str()));

    for stream in acceptor.incoming() {
        // Spawn a task to handle the connection
        do spawn {
            let mut stream = stream;

            match stream {
                Some(ref mut s) => {
                             match s.peer_name() {
                                Some(pn) => {
					println(format!("Received connection from: [{:s}]", pn.to_str()));
					unsafe {
						count = count + 1;
						println!("Total number of requests received: {:d}", count)
					}
				},
                                None => ()
                             }
                           },
                None => ()
            }

            let mut buf = [0, ..500];
            stream.read(buf);
            let request_str = str::from_utf8(buf);
            println(format!("Received request :\n{:s}", request_str));
            let mut components : ~[~str] = ~[];
            for tempString in request_str.split(' ') {
            	components.push(tempString.to_owned());
            }
            if !str::eq(&components[1], &~"/") {
		let mut dotdot = false;
		let dot : u8 = ".".as_bytes()[0];
		let slash : u8 = "/".as_bytes()[0];
		for i in range (0, components[1].len()-3) {
			if(components[1][i]==dot && components[1][i+1]==dot && components[1][i+2]==slash) {
				dotdot = true;
			}
		}
            	if components[1].len() > 4 && !dotdot && str::eq(&components[1].slice_from(components[1].len()-5).to_owned(), &~".html") {
        	let f1name = components[1].slice_from(1);
        	let path1 = Path::new(f1name.clone());
        	let mut msg_file1 = File::open(&path1);
                let msg_bytes1: ~[u8] = msg_file1.read_to_end();
		stream.write(msg_bytes1);
		}
		else {
            let response: ~str =
                 ~"HTTP/1.1 403 Forbidden\r\nContent-Type: text/html; charset=UTF-8\r\n\r\n
                 <doctype !html><title>403 Forbidden</title>

403: Forbidden

You do not have permission to access that file";
		stream.write(response.as_bytes());
		}
            }
            else {
            unsafe {
            let response: ~str =
                 ("HTTP/1.1 200 OK\r\nContent-Type: text/html; charset=UTF-8\r\n\r\n
                 <doctype !html><title>Hello, Rust!</title>

Greetings, Krusty!

" + format!("Total Number of Requests: {:d}
", count) +
                 "\r\n");
            stream.write(response.as_bytes());
            }
            println!("Connection terminates.");
            }
        }
    }
}
<|endoftext|>


<commit_before>mod component;
mod ui;

use crate::config::Config;
use crate::err::{self, Result};
use crate::file::SaveFile;
use crate::series::{SavedSeries, Series};
use anime::remote::RemoteService;
use chrono::{DateTime, Duration, Utc};
use clap::ArgMatches;
use component::command_prompt::{Command, CommandPrompt};
use component::log::{LogItem, StatusLog};
use snafu::ResultExt;
use std::mem;
use std::process;
use termion::event::Key;
use ui::{Event, Events, UI};

pub fn run(args: &ArgMatches) -> Result<()> {
    let mut ui = UI::init()?;

    let mut cstate = {
        let config = Config::load_or_create()?;
        let remote = init_remote(args, &mut ui.status_log);

        CommonState { config, remote }
    };

    let mut ui_state = init_ui_state(&cstate, args)?;
    let events = Events::new(Duration::seconds(1));

    loop {
        ui.draw(&ui_state, cstate.remote.as_ref())?;

        match events.next()? {
            Event::Input(key) => match key {
                // Exit
                Key::Char('q') if !ui_state.status_bar_state.in_input_dialog() => {
                    // Prevent ruining the user's terminal
                    ui.clear().ok();
                    break Ok(());
                }
                key => match ui_state.process_key(&mut cstate, &mut ui.status_log, key) {
                    Ok(_) => (),
                    Err(err) => {
                        ui.status_log.push(LogItem::failed("Processing key", err));
                    }
                },
            },
            Event::Tick => match ui_state.process_tick(&cstate, &mut ui.status_log) {
                Ok(_) => (),
                Err(err) => ui.status_log.push(LogItem::failed("Processing tick", err)),
            },
        }
    }
}

fn init_remote(args: &ArgMatches, log: &mut StatusLog) -> Box<dyn RemoteService> {
    use anime::remote::anilist;
    use anime::remote::offline::Offline;

    match crate::init_remote(args, true) {
        Ok(remote) => remote,
        Err(err::Error::NeedAniListToken) => {
            log.push(format!(
                "No access token found. Go to {} and set your token with the 'token' command",
                anilist::auth_url(crate::ANILIST_CLIENT_ID)
            ));

            Box::new(Offline::new())
        }
        Err(err) => {
            log.push(LogItem::failed("Logging in", err));
            log.push(format!(
                "If you need a new token, go to {} and set it with the 'token' command",
                anilist::auth_url(crate::ANILIST_CLIENT_ID)
            ));
            log.push("Continuing in offline mode");

            Box::new(Offline::new())
        }
    }
}

/// Items that are not tied to the UI and are commonly used together.
struct CommonState {
    config: Config,
    remote: Box<dyn RemoteService>,
}

fn init_ui_state<'a>(cstate: &CommonState, args: &ArgMatches) -> Result<UIState<'a>> {
    let mut saved_series = SavedSeries::load_or_default()?;
    let series = init_series_list(&cstate, args, &mut saved_series)?;

    let selected_series = match saved_series.last_watched_id {
        Some(id) => series
            .iter()
            .position(|series| series.info.id == id)
            .unwrap_or(0),
        None => 0,
    };

    let series = series.into_iter().map(SeriesState::new).collect();

    Ok(UIState {
        series,
        selected_series,
        saved_series,
        status_bar_state: StatusBarState::default(),
        last_used_command: None,
    })
}

fn init_series_list(
    cstate: &CommonState,
    args: &ArgMatches,
    saved_series: &mut SavedSeries,
) -> Result<Vec<Series>> {
    let mut series = saved_series.load_all_series_and_validate()?;

    // If the user specified a series, we'll need to check to see if we
    // already have it or fetch & save it otherwise.
    let desired_series = match args.value_of("series") {
        Some(desired_series) => desired_series,
        None => return Ok(series),
    };

    if saved_series.contains(desired_series) {
        return Ok(series);
    }

    let new_series = saved_series.insert_and_save_from_args_and_remote(
        args,
        desired_series,
        &cstate.config,
        cstate.remote.as_ref(),
    )?;

    series.push(new_series);
    Ok(series)
}

/// Current state of the UI.
pub struct UIState<'a> {
    series: Vec<SeriesState>,
    selected_series: usize,
    saved_series: SavedSeries,
    status_bar_state: StatusBarState<'a>,
    last_used_command: Option<Command>,
}

macro_rules! cur_series {
    ($struct:ident) => {
        match $struct.cur_series() {
            Some(value) => value,
            None => return Ok(()),
        }
    };
}

macro_rules! cur_series_mut {
    ($struct:ident) => {
        match $struct.cur_series_mut() {
            Some(value) => value,
            None => return Ok(()),
        }
    };
}

impl<'a> UIState<'a> {
    fn cur_series(&self) -> Option<&SeriesState> {
        self.series.get(self.selected_series)
    }

    fn cur_series_mut(&mut self) -> Option<&mut SeriesState> {
        self.series.get_mut(self.selected_series)
    }

    fn process_key(
        &mut self,
        state: &mut CommonState,
        log: &mut StatusLog,
        key: Key,
    ) -> Result<()> {
        if !self.is_idle() {
            return Ok(());
        }

        if self.status_bar_state.in_input_dialog() {
            return self.process_input_dialog_key(state, log, key);
        }

        match key {
            // Play next episode
            Key::Char(ch) if ch == state.config.tui.keys.play_next_episode => {
                let last_watched_changed = {
                    let id = cur_series!(self).inner.info.id;
                    self.saved_series.set_last_watched(id)
                };

                if last_watched_changed {
                    log.capture_status("Marking series as the last watched one", || {
                        self.saved_series.save()
                    });
                }

                log.capture_status("Playing next episode", || {
                    cur_series_mut!(self).play_next_episode_async(&state)
                });
            }
            // Command prompt
            Key::Char(':') => {
                self.status_bar_state.set_to_command_prompt();
            }
            // Run last used command
            Key::Char(ch) if ch == state.config.tui.keys.run_last_command => {
                let cmd = match &self.last_used_command {
                    Some(cmd) => cmd.clone(),
                    None => return Ok(()),
                };

                self.process_command(cmd, state, log)?;
            }
            // Select series
            Key::Up | Key::Down => {
                self.selected_series = match key {
                    Key::Up => self.selected_series.saturating_sub(1),
                    Key::Down if self.selected_series < self.series.len().saturating_sub(1) => {
                        self.selected_series + 1
                    }
                    _ => self.selected_series,
                };
            }
            _ => (),
        }

        Ok(())
    }

    fn process_input_dialog_key(
        &mut self,
        state: &mut CommonState,
        log: &mut StatusLog,
        key: Key,
    ) -> Result<()> {
        match &mut self.status_bar_state {
            StatusBarState::Log => Ok(()),
            StatusBarState::CommandPrompt(prompt) => {
                use component::command_prompt::PromptResult;

                match prompt.process_key(key) {
                    Ok(PromptResult::Command(command)) => {
                        self.status_bar_state.reset();
                        self.last_used_command = Some(command.clone());
                        self.process_command(command, state, log)
                    }
                    Ok(PromptResult::Done) => {
                        self.status_bar_state.reset();
                        Ok(())
                    }
                    Ok(PromptResult::NotDone) => Ok(()),
                    // We need to set the status bar state back before propagating errors,
                    // otherwise we'll be stuck in the prompt
                    Err(err) => {
                        self.status_bar_state.reset();
                        Err(err)
                    }
                }
            }
        }
    }

    fn process_command(
        &mut self,
        command: Command,
        cstate: &mut CommonState,
        log: &mut StatusLog,
    ) -> Result<()> {
        let series = &mut cur_series_mut!(self).inner;
        let remote = cstate.remote.as_ref();

        match command {
            Command::SyncFromRemote => {
                log.capture_status("Syncing entry from remote", || {
                    series.force_sync_changes_from_remote(remote)
                });

                Ok(())
            }
            Command::SyncToRemote => {
                log.capture_status("Syncing entry to remote", || {
                    series.force_sync_changes_to_remote(remote)
                });

                Ok(())
            }
            Command::Status(status) => {
                series.entry.set_status(status, &cstate.config);

                log.capture_status(format!("Setting series status to \"{}\"", status), || {
                    series.sync_changes_to_remote(remote)
                });

                Ok(())
            }
            Command::Progress(direction) => {
                use component::command_prompt::ProgressDirection;

                match direction {
                    ProgressDirection::Forwards => {
                        log.capture_status("Forcing forward watch progress", || {
                            series.episode_completed(remote, &cstate.config)
                        });
                    }
                    ProgressDirection::Backwards => {
                        log.capture_status("Forcing backwards watch progress", || {
                            series.episode_regressed(remote, &cstate.config)
                        });
                    }
                }

                Ok(())
            }
            Command::Score(raw_score) => {
                let score = match cstate.remote.parse_score(&raw_score) {
                    Some(score) if score == 0 => None,
                    Some(score) => Some(score),
                    None => {
                        log.push(LogItem::failed("Parsing score", None));
                        return Ok(());
                    }
                };

                series.entry.set_score(score);

                log.capture_status("Setting score", || {
                    series.sync_changes_to_remote(cstate.remote.as_ref())
                });

                Ok(())
            }
            Command::PlayerArgs(args) => {
                log.capture_status("Saving player args for series", || {
                    series.player_args = args;
                    series.save()
                });

                Ok(())
            }
            Command::LoginToken(token) => {
                use anime::remote::anilist::AniList;
                use anime::remote::AccessToken;

                log.capture_status("Setting user access token", || {
                    let token = AccessToken::encode(token);
                    token.save()?;
                    cstate.remote = Box::new(AniList::login(token)?);
                    Ok(())
                });

                Ok(())
            }
        }
    }

    fn process_tick(&mut self, state: &CommonState, log: &mut StatusLog) -> Result<()> {
        cur_series_mut!(self).process_tick(state, log)
    }

    fn is_idle(&self) -> bool {
        match self.cur_series() {
            Some(cur_series) => cur_series.watch_state == WatchState::Idle,
            None => true,
        }
    }
}

enum StatusBarState<'a> {
    Log,
    CommandPrompt(CommandPrompt<'a>),
}

impl<'a> StatusBarState<'a> {
    fn set_to_command_prompt(&mut self) {
        *self = StatusBarState::CommandPrompt(CommandPrompt::new());
    }

    fn reset(&mut self) {
        *self = StatusBarState::default();
    }

    fn in_input_dialog(&self) -> bool {
        match self {
            StatusBarState::Log => false,
            StatusBarState::CommandPrompt(_) => true,
        }
    }
}

impl<'a> Default for StatusBarState<'a> {
    fn default() -> StatusBarState<'a> {
        StatusBarState::Log
    }
}

#[derive(Debug)]
struct SeriesState {
    inner: Series,
    watch_state: WatchState,
}

impl SeriesState {
    fn new(inner: Series) -> SeriesState {
        SeriesState {
            inner,
            watch_state: WatchState::Idle,
        }
    }

    fn play_next_episode_async(&mut self, state: &CommonState) -> Result<()> {
        let remote = state.remote.as_ref();
        let config = &state.config;

        self.inner.begin_watching(remote, config)?;
        let next_ep = self.inner.entry.watched_eps() + 1;

        let child = self
            .inner
            .play_episode_cmd(next_ep, &state.config)?
            .spawn()
            .context(err::FailedToPlayEpisode { episode: next_ep })?;

        let progress_time = {
            let secs_must_watch =
                (self.inner.info.episode_length as f32 * config.episode.pcnt_must_watch) * 60.0;
            let time_must_watch = Duration::seconds(secs_must_watch as i64);

            Utc::now() + time_must_watch
        };

        self.watch_state = WatchState::Watching(progress_time, child);

        Ok(())
    }

    fn process_tick(&mut self, state: &CommonState, log: &mut StatusLog) -> Result<()> {
        match &mut self.watch_state {
            WatchState::Idle => (),
            WatchState::Watching(_, child) => {
                let status = match child.try_wait().context(err::IO)? {
                    Some(status) => status,
                    None => return Ok(()),
                };

                // The watch state should be set to idle immediately to avoid a potential infinite loop.
                let progress_time = match mem::replace(&mut self.watch_state, WatchState::Idle) {
                    WatchState::Watching(progress_time, _) => progress_time,
                    WatchState::Idle => unreachable!(),
                };

                if !status.success() {
                    log.push("Player did not exit properly");
                    return Ok(());
                }

                if Utc::now() >= progress_time {
                    log.capture_status("Marking episode as completed", || {
                        self.inner
                            .episode_completed(state.remote.as_ref(), &state.config)
                    });
                } else {
                    log.push("Not marking episode as completed");
                }
            }
        }

        Ok(())
    }
}

type ProgressTime = DateTime<Utc>;

#[derive(Debug)]
enum WatchState {
    Idle,
    Watching(ProgressTime, process::Child),
}

impl PartialEq for WatchState {
    fn eq(&self, other: &Self) -> bool {
        match (self, other) {
            (WatchState::Idle, WatchState::Idle) => true,
            (WatchState::Watching(_, _), WatchState::Watching(_, _)) => true,
            _ => false,
        }
    }
}
<commit_msg>anup: always show offline mode message when init_remote() fails<commit_after>mod component;
mod ui;

use crate::config::Config;
use crate::err::{self, Result};
use crate::file::SaveFile;
use crate::series::{SavedSeries, Series};
use anime::remote::RemoteService;
use chrono::{DateTime, Duration, Utc};
use clap::ArgMatches;
use component::command_prompt::{Command, CommandPrompt};
use component::log::{LogItem, StatusLog};
use snafu::ResultExt;
use std::mem;
use std::process;
use termion::event::Key;
use ui::{Event, Events, UI};

pub fn run(args: &ArgMatches) -> Result<()> {
    let mut ui = UI::init()?;

    let mut cstate = {
        let config = Config::load_or_create()?;
        let remote = init_remote(args, &mut ui.status_log);

        CommonState { config, remote }
    };

    let mut ui_state = init_ui_state(&cstate, args)?;
    let events = Events::new(Duration::seconds(1));

    loop {
        ui.draw(&ui_state, cstate.remote.as_ref())?;

        match events.next()? {
            Event::Input(key) => match key {
                // Exit
                Key::Char('q') if !ui_state.status_bar_state.in_input_dialog() => {
                    // Prevent ruining the user's terminal
                    ui.clear().ok();
                    break Ok(());
                }
                key => match ui_state.process_key(&mut cstate, &mut ui.status_log, key) {
                    Ok(_) => (),
                    Err(err) => {
                        ui.status_log.push(LogItem::failed("Processing key", err));
                    }
                },
            },
            Event::Tick => match ui_state.process_tick(&cstate, &mut ui.status_log) {
                Ok(_) => (),
                Err(err) => ui.status_log.push(LogItem::failed("Processing tick", err)),
            },
        }
    }
}

fn init_remote(args: &ArgMatches, log: &mut StatusLog) -> Box<dyn RemoteService> {
    use anime::remote::anilist;
    use anime::remote::offline::Offline;

    match crate::init_remote(args, true) {
        Ok(remote) => remote,
        Err(err) => {
            match err {
                err::Error::NeedAniListToken => {
                    log.push(format!(
                        "No access token found. Go to {} \
                         and set your token with the 'token' command",
                        anilist::auth_url(crate::ANILIST_CLIENT_ID)
                    ));
                }
                _ => {
                    log.push(LogItem::failed("Logging in", err));
                    log.push(format!(
                        "If you need a new token, go to {} \
                         and set it with the 'token' command",
                        anilist::auth_url(crate::ANILIST_CLIENT_ID)
                    ));
                }
            }

            log.push("Continuing in offline mode");

            Box::new(Offline::new())
        }
    }
}

/// Items that are not tied to the UI and are commonly used together.
struct CommonState {
    config: Config,
    remote: Box<dyn RemoteService>,
}

fn init_ui_state<'a>(cstate: &CommonState, args: &ArgMatches) -> Result<UIState<'a>> {
    let mut saved_series = SavedSeries::load_or_default()?;
    let series = init_series_list(&cstate, args, &mut saved_series)?;

    let selected_series = match saved_series.last_watched_id {
        Some(id) => series
            .iter()
            .position(|series| series.info.id == id)
            .unwrap_or(0),
        None => 0,
    };

    let series = series.into_iter().map(SeriesState::new).collect();

    Ok(UIState {
        series,
        selected_series,
        saved_series,
        status_bar_state: StatusBarState::default(),
        last_used_command: None,
    })
}

fn init_series_list(
    cstate: &CommonState,
    args: &ArgMatches,
    saved_series: &mut SavedSeries,
) -> Result<Vec<Series>> {
    let mut series = saved_series.load_all_series_and_validate()?;

    // If the user specified a series, we'll need to check to see if we
    // already have it or fetch & save it otherwise.
    let desired_series = match args.value_of("series") {
        Some(desired_series) => desired_series,
        None => return Ok(series),
    };

    if saved_series.contains(desired_series) {
        return Ok(series);
    }

    let new_series = saved_series.insert_and_save_from_args_and_remote(
        args,
        desired_series,
        &cstate.config,
        cstate.remote.as_ref(),
    )?;

    series.push(new_series);
    Ok(series)
}

/// Current state of the UI.
pub struct UIState<'a> {
    series: Vec<SeriesState>,
    selected_series: usize,
    saved_series: SavedSeries,
    status_bar_state: StatusBarState<'a>,
    last_used_command: Option<Command>,
}

macro_rules! cur_series {
    ($struct:ident) => {
        match $struct.cur_series() {
            Some(value) => value,
            None => return Ok(()),
        }
    };
}

macro_rules! cur_series_mut {
    ($struct:ident) => {
        match $struct.cur_series_mut() {
            Some(value) => value,
            None => return Ok(()),
        }
    };
}

impl<'a> UIState<'a> {
    fn cur_series(&self) -> Option<&SeriesState> {
        self.series.get(self.selected_series)
    }

    fn cur_series_mut(&mut self) -> Option<&mut SeriesState> {
        self.series.get_mut(self.selected_series)
    }

    fn process_key(
        &mut self,
        state: &mut CommonState,
        log: &mut StatusLog,
        key: Key,
    ) -> Result<()> {
        if !self.is_idle() {
            return Ok(());
        }

        if self.status_bar_state.in_input_dialog() {
            return self.process_input_dialog_key(state, log, key);
        }

        match key {
            // Play next episode
            Key::Char(ch) if ch == state.config.tui.keys.play_next_episode => {
                let last_watched_changed = {
                    let id = cur_series!(self).inner.info.id;
                    self.saved_series.set_last_watched(id)
                };

                if last_watched_changed {
                    log.capture_status("Marking series as the last watched one", || {
                        self.saved_series.save()
                    });
                }

                log.capture_status("Playing next episode", || {
                    cur_series_mut!(self).play_next_episode_async(&state)
                });
            }
            // Command prompt
            Key::Char(':') => {
                self.status_bar_state.set_to_command_prompt();
            }
            // Run last used command
            Key::Char(ch) if ch == state.config.tui.keys.run_last_command => {
                let cmd = match &self.last_used_command {
                    Some(cmd) => cmd.clone(),
                    None => return Ok(()),
                };

                self.process_command(cmd, state, log)?;
            }
            // Select series
            Key::Up | Key::Down => {
                self.selected_series = match key {
                    Key::Up => self.selected_series.saturating_sub(1),
                    Key::Down if self.selected_series < self.series.len().saturating_sub(1) => {
                        self.selected_series + 1
                    }
                    _ => self.selected_series,
                };
            }
            _ => (),
        }

        Ok(())
    }

    fn process_input_dialog_key(
        &mut self,
        state: &mut CommonState,
        log: &mut StatusLog,
        key: Key,
    ) -> Result<()> {
        match &mut self.status_bar_state {
            StatusBarState::Log => Ok(()),
            StatusBarState::CommandPrompt(prompt) => {
                use component::command_prompt::PromptResult;

                match prompt.process_key(key) {
                    Ok(PromptResult::Command(command)) => {
                        self.status_bar_state.reset();
                        self.last_used_command = Some(command.clone());
                        self.process_command(command, state, log)
                    }
                    Ok(PromptResult::Done) => {
                        self.status_bar_state.reset();
                        Ok(())
                    }
                    Ok(PromptResult::NotDone) => Ok(()),
                    // We need to set the status bar state back before propagating errors,
                    // otherwise we'll be stuck in the prompt
                    Err(err) => {
                        self.status_bar_state.reset();
                        Err(err)
                    }
                }
            }
        }
    }

    fn process_command(
        &mut self,
        command: Command,
        cstate: &mut CommonState,
        log: &mut StatusLog,
    ) -> Result<()> {
        let series = &mut cur_series_mut!(self).inner;
        let remote = cstate.remote.as_ref();

        match command {
            Command::SyncFromRemote => {
                log.capture_status("Syncing entry from remote", || {
                    series.force_sync_changes_from_remote(remote)
                });

                Ok(())
            }
            Command::SyncToRemote => {
                log.capture_status("Syncing entry to remote", || {
                    series.force_sync_changes_to_remote(remote)
                });

                Ok(())
            }
            Command::Status(status) => {
                series.entry.set_status(status, &cstate.config);

                log.capture_status(format!("Setting series status to \"{}\"", status), || {
                    series.sync_changes_to_remote(remote)
                });

                Ok(())
            }
            Command::Progress(direction) => {
                use component::command_prompt::ProgressDirection;

                match direction {
                    ProgressDirection::Forwards => {
                        log.capture_status("Forcing forward watch progress", || {
                            series.episode_completed(remote, &cstate.config)
                        });
                    }
                    ProgressDirection::Backwards => {
                        log.capture_status("Forcing backwards watch progress", || {
                            series.episode_regressed(remote, &cstate.config)
                        });
                    }
                }

                Ok(())
            }
            Command::Score(raw_score) => {
                let score = match cstate.remote.parse_score(&raw_score) {
                    Some(score) if score == 0 => None,
                    Some(score) => Some(score),
                    None => {
                        log.push(LogItem::failed("Parsing score", None));
                        return Ok(());
                    }
                };

                series.entry.set_score(score);

                log.capture_status("Setting score", || {
                    series.sync_changes_to_remote(cstate.remote.as_ref())
                });

                Ok(())
            }
            Command::PlayerArgs(args) => {
                log.capture_status("Saving player args for series", || {
                    series.player_args = args;
                    series.save()
                });

                Ok(())
            }
            Command::LoginToken(token) => {
                use anime::remote::anilist::AniList;
                use anime::remote::AccessToken;

                log.capture_status("Setting user access token", || {
                    let token = AccessToken::encode(token);
                    token.save()?;
                    cstate.remote = Box::new(AniList::login(token)?);
                    Ok(())
                });

                Ok(())
            }
        }
    }

    fn process_tick(&mut self, state: &CommonState, log: &mut StatusLog) -> Result<()> {
        cur_series_mut!(self).process_tick(state, log)
    }

    fn is_idle(&self) -> bool {
        match self.cur_series() {
            Some(cur_series) => cur_series.watch_state == WatchState::Idle,
            None => true,
        }
    }
}

enum StatusBarState<'a> {
    Log,
    CommandPrompt(CommandPrompt<'a>),
}

impl<'a> StatusBarState<'a> {
    fn set_to_command_prompt(&mut self) {
        *self = StatusBarState::CommandPrompt(CommandPrompt::new());
    }

    fn reset(&mut self) {
        *self = StatusBarState::default();
    }

    fn in_input_dialog(&self) -> bool {
        match self {
            StatusBarState::Log => false,
            StatusBarState::CommandPrompt(_) => true,
        }
    }
}

impl<'a> Default for StatusBarState<'a> {
    fn default() -> StatusBarState<'a> {
        StatusBarState::Log
    }
}

#[derive(Debug)]
struct SeriesState {
    inner: Series,
    watch_state: WatchState,
}

impl SeriesState {
    fn new(inner: Series) -> SeriesState {
        SeriesState {
            inner,
            watch_state: WatchState::Idle,
        }
    }

    fn play_next_episode_async(&mut self, state: &CommonState) -> Result<()> {
        let remote = state.remote.as_ref();
        let config = &state.config;

        self.inner.begin_watching(remote, config)?;
        let next_ep = self.inner.entry.watched_eps() + 1;

        let child = self
            .inner
            .play_episode_cmd(next_ep, &state.config)?
            .spawn()
            .context(err::FailedToPlayEpisode { episode: next_ep })?;

        let progress_time = {
            let secs_must_watch =
                (self.inner.info.episode_length as f32 * config.episode.pcnt_must_watch) * 60.0;
            let time_must_watch = Duration::seconds(secs_must_watch as i64);

            Utc::now() + time_must_watch
        };

        self.watch_state = WatchState::Watching(progress_time, child);

        Ok(())
    }

    fn process_tick(&mut self, state: &CommonState, log: &mut StatusLog) -> Result<()> {
        match &mut self.watch_state {
            WatchState::Idle => (),
            WatchState::Watching(_, child) => {
                let status = match child.try_wait().context(err::IO)? {
                    Some(status) => status,
                    None => return Ok(()),
                };

                // The watch state should be set to idle immediately to avoid a potential infinite loop.
                let progress_time = match mem::replace(&mut self.watch_state, WatchState::Idle) {
                    WatchState::Watching(progress_time, _) => progress_time,
                    WatchState::Idle => unreachable!(),
                };

                if !status.success() {
                    log.push("Player did not exit properly");
                    return Ok(());
                }

                if Utc::now() >= progress_time {
                    log.capture_status("Marking episode as completed", || {
                        self.inner
                            .episode_completed(state.remote.as_ref(), &state.config)
                    });
                } else {
                    log.push("Not marking episode as completed");
                }
            }
        }

        Ok(())
    }
}

type ProgressTime = DateTime<Utc>;

#[derive(Debug)]
enum WatchState {
    Idle,
    Watching(ProgressTime, process::Child),
}

impl PartialEq for WatchState {
    fn eq(&self, other: &Self) -> bool {
        match (self, other) {
            (WatchState::Idle, WatchState::Idle) => true,
            (WatchState::Watching(_, _), WatchState::Watching(_, _)) => true,
            _ => false,
        }
    }
}
<|endoftext|>


<commit_before>
fn main () {
  // dummy
  println!("cargo:rustc-flags=-l sodium:static -L ../../libsodium-0.7.0/src/libsodium/.libs/");
}<commit_msg>Fixed libsodium-sys build script<commit_after>
fn main () {
  // dummy
  println!("cargo:rustc-flags=-l sodium");
}<|endoftext|>


<commit_before>// Zinc, the bare metal stack for rust.
// Copyright 2014 Vladimir "farcaller" Pouzanov <farcaller@gmail.com>
//
// Licensed under the Apache License, Version 2.0 (the "License");
// you may not use this file except in compliance with the License.
// You may obtain a copy of the License at
//
//     http://www.apache.org/licenses/LICENSE-2.0
//
// Unless required by applicable law or agreed to in writing, software
// distributed under the License is distributed on an "AS IS" BASIS,
// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
// See the License for the specific language governing permissions and
// limitations under the License.

use std::collections::hashmap::HashMap;
use std::gc::{Gc, GC};
use syntax::ast::TokenTree;
use syntax::codemap::{Span, mk_sp};
use syntax::ext::base::ExtCtxt;
use syntax::parse::{token, ParseSess, lexer};

use node;

pub struct Parser<'a> {
  pub sess: &'a ParseSess,
  reader: Box<lexer::Reader:>,
  token: token::Token,
  span: Span,

  last_token: Option<Box<token::Token>>,
  last_span: Span,
}

impl<'a> Parser<'a> {
  pub fn new<'a>(cx: &'a ExtCtxt, tts: &[TokenTree]) -> Parser<'a> {
    let sess = cx.parse_sess();
    let ttsvec = tts.iter().map(|x| (*x).clone()).collect();
    let mut reader = box lexer::new_tt_reader(
        &sess.span_diagnostic, None, ttsvec) as Box<lexer::Reader>;

    let tok0 = reader.next_token();
    let token = tok0.tok;
    let span = tok0.sp;

    Parser {
      sess: sess,
      reader: reader,

      token: token,
      span: span,

      last_token: None,
      last_span: span,
    }
  }

  /// Parse the platform tree from passed in tokens.
  pub fn parse_platformtree(&mut self) -> Option<node::PlatformTree> {
    let mut nodes: HashMap<String, Gc<node::Node>> = HashMap::new();
    let mut nodes_by_path: HashMap<String, Gc<node::Node>> = HashMap::new();
    let mut failed = false;
    loop {
      if self.token == token::EOF {
        break
      }

      let node = match self.parse_node() {
        Some(node) => box(GC) node,
        None => {
          failed = true;
          self.bump();
          continue
        }
      };

      let path = node.path.clone();
      if nodes_by_path.contains_key(&path) {
        failed = true;
        self.sess.span_diagnostic.span_err(node.path_span,
            format!("duplicate node definition `{}`", path).as_slice());
        let old_node: &Gc<node::Node> = nodes_by_path.get(&path);
        self.sess.span_diagnostic.span_err(old_node.path_span,
            "previously defined here");
      } else {
        nodes_by_path.insert(node.path.clone(), node);
      }

      match node.name {
        Some(ref name) => { nodes.insert(name.clone(), node); },
        None => {
          failed = true;
          self.sess.span_diagnostic.span_err(node.name_span,
              "missing name for root node");
        }
      }
    }

    if failed {
      None
    } else {
      let mut map = HashMap::new();
      if self.collect_node_names(&mut map, &nodes) {
        Some(node::PlatformTree::new(nodes, map))
      } else {
        None
      }
    }
  }

  fn collect_node_names(&self, map: &mut HashMap<String, Gc<node::Node>>,
      nodes: &HashMap<String, Gc<node::Node>>) -> bool {
    for (_, n) in nodes.iter() {
      if !self.collect_node_names(map, &n.subnodes) {
        return false;
      }

      match n.name {
        Some(ref name) => {
          if map.contains_key(name) {
            self.sess.span_diagnostic.span_err(n.name_span, format!(
                "duplicate `{}` definition", name).as_slice());

            self.sess.span_diagnostic.span_warn(
                map.get(name).name_span,
                "previously defined here");
            return false;
          } else {
            map.insert(name.clone(), *n);
          }
        },
        None => (),
      }
    }
    true
  }

  fn parse_node(&mut self) -> Option<node::Node> {
    let name_span: Option<Span>;
    let node_name: Option<String>;

    //  we're here
    // /
    // v
    // NAME @ PATH { ... }
    //      ^-- peeking here
    if self.reader.peek().tok == token::AT {
      name_span = Some(self.span);
      node_name = match self.expect_ident() {
        Some(name) => Some(name),
        None => return None,
      };
      if !self.expect(&token::AT) {
        return None;
      }
    } else {
      node_name = None;
      name_span = None;
    }

    let node_span: Span;
    let node_path_span: Span;
    let attributes: HashMap<String, node::Attribute>;
    let subnodes: HashMap<String, Gc<node::Node>>;

    // NAME is resolved, if it was there anyway.
    //    we're here
    //          |
    //          v
    //   NAME @ PATH { ... }
    node_path_span = self.span;
    if node_name == None {
      node_span = self.span;
    } else {
      node_span = mk_sp(name_span.unwrap().lo, self.span.hi);
    }

    let node_path = match self.token {
      token::IDENT(_, _) => {
        token::to_str(&self.bump())
      },
      token::LIT_INT_UNSUFFIXED(u) => {
        self.bump();
        u.to_str()
      }
      ref other => {
        self.error(format!("expected node path but found `{}`",
            token::to_str(other)));
        return None;
      }
    };

    //    we're here
    //             |
    //             v
    // NAME @ PATH { ... }
    // it's either a body, or a semicolon (no body)
    match self.bump() {
      token::LBRACE => {
        let (a, s) = match self.parse_node_body() {
          Some((attrs, subnodes)) => (attrs, subnodes),
          // TODO(farcaller): eat everything up to '}' and continue if failed
          // we can still parse further nodes.
          None => return None,
        };
        attributes = a;
        subnodes = s;

        if !self.expect(&token::RBRACE) {
          return None;
        }
      },
      token::SEMI => {
        attributes = HashMap::new();
        subnodes = HashMap::new();
      },
      ref other => {
        self.error(format!("expected `{{` or `;` but found `{}`",
            token::to_str(other)));
        return None;
      }
    }

    let mut node = node::Node::new(node_name, node_span, node_path, node_path_span);
    node.attributes = attributes;
    node.subnodes = subnodes;
    Some(node)
  }

  fn parse_node_body(&mut self)
      -> Option<(
          HashMap<String, node::Attribute>,
          HashMap<String, Gc<node::Node>>)> {
    let mut attrs = HashMap::new();
    let mut subnodes = HashMap::new();

    loop {
      // break early if at brace
      if self.token == token::RBRACE {
        break;
      }

      // we're here
      // |
      // v
      // ATTR = VAL ;
      // NAME @ PATH
      // PATH { ... }
      // PATH ;
      //      ^-- peeking here
      if self.reader.peek().tok == token::EQ {
        // we're here
        // |
        // v
        // ATTR = VAL ;
        let name_span = self.span;
        let some_name = match self.expect_ident() {
          Some(name) => name,
          None => return None,
        };

        if attrs.contains_key(&some_name) {
          self.error(format!("key `{}` is already defined", some_name));
          return None;
        }

        self.bump(); // bump token::EQ

        // we're here
        //        |
        //        v
        // ATTR = VAL ;
        let attr_value_span = self.span;
        let attr_value = match self.parse_attribute_value() {
          Some(value) => value,
          None => return None,
        };

        //   we're here
        //            |
        //            v
        // ATTR = VAL ;
        if !self.expect(&token::SEMI) {
          return None;
        }

        attrs.insert(some_name, node::Attribute::new(
            attr_value, name_span, attr_value_span));
      } else {
        // this should be a subnode
        let oldsp = self.span;
        let oldtok = self.token.clone();
        let node = match self.parse_node() {
          Some(node) => node,
          None => {
            self.span = oldsp;
            self.error(format!("expected `=` or node but found `{}`",
                token::to_str(&oldtok)));
            return None;
          },
        };

        let path = node.path.clone();
        if subnodes.contains_key(&path) {
          self.span = node.path_span;
          self.error(format!("duplicate node definition `{}`",
              path));
          let old_node: &Gc<node::Node> = subnodes.get(&path);
          self.span = old_node.path_span.clone();
          self.error("previously defined here".to_str());
          return None;
        } else {
          subnodes.insert(path, box(GC) node);
        }
      }
    }

    Some((attrs, subnodes))
  }

  fn parse_attribute_value(&mut self) -> Option<node::AttributeValue> {
    match self.token {
      token::LIT_STR(string_val) => {
        self.bump();
        let string = token::get_ident(string_val).get().to_str();
        Some(node::StrValue(string))
      },
      token::LIT_INT_UNSUFFIXED(intval) => {
        self.bump();
        Some(node::UIntValue(intval as uint))
      },
      token::BINOP(token::AND) => {
        self.bump();
        let name = match self.expect_ident() {
          Some(name) => name,
          None => return None,
        };
        Some(node::RefValue(name))
      },
      ref other => {
        self.error(format!("expected string but found `{}`",
            token::to_str(other)));
        None
      }
    }
  }

  fn error(&self, m: String) {
    self.sess.span_diagnostic.span_err(self.span, m.as_slice());
  }

  /// Bumps a token.
  ///
  /// This moves current token to last token, pops a new token from backlog or
  /// reader and returns the last token (i.e. the 'current' token at the time of
  /// method call).
  fn bump(&mut self) -> token::Token {
    let tok = self.token.clone();
    self.last_span = self.span;
    self.last_token = Some(box tok.clone());

    let next = self.reader.next_token();

    self.span = next.sp;
    self.token = next.tok;

    tok
  }

  /// Expects that the current token is t. Bumps on success.
  fn expect(&mut self, t: &token::Token) -> bool {
    if self.token == *t {
      self.bump();
      true
    } else {
      let token_str = token::to_str(t);
      let this_token_str = token::to_str(&self.token);
      self.error(format!("expected `{}` but found `{}`", token_str,
          this_token_str));
      false
    }
  }

  /// Expects that the current token is IDENT, returns its string value. Bumps
  /// on success.
  fn expect_ident(&mut self) -> Option<String> {
    let tok_str = token::to_str(&self.token);
    match self.token {
      token::IDENT(_, _) => {
        self.bump();
        Some(tok_str)
      },
      _ => {
        self.error(format!("expected identifier but found `{}`", tok_str));
        None
      },
    }
  }
}
<commit_msg>Better spelling for error messages.<commit_after>// Zinc, the bare metal stack for rust.
// Copyright 2014 Vladimir "farcaller" Pouzanov <farcaller@gmail.com>
//
// Licensed under the Apache License, Version 2.0 (the "License");
// you may not use this file except in compliance with the License.
// You may obtain a copy of the License at
//
//     http://www.apache.org/licenses/LICENSE-2.0
//
// Unless required by applicable law or agreed to in writing, software
// distributed under the License is distributed on an "AS IS" BASIS,
// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
// See the License for the specific language governing permissions and
// limitations under the License.

use std::collections::hashmap::HashMap;
use std::gc::{Gc, GC};
use syntax::ast::TokenTree;
use syntax::codemap::{Span, mk_sp};
use syntax::ext::base::ExtCtxt;
use syntax::parse::{token, ParseSess, lexer};

use node;

pub struct Parser<'a> {
  pub sess: &'a ParseSess,
  reader: Box<lexer::Reader:>,
  token: token::Token,
  span: Span,

  last_token: Option<Box<token::Token>>,
  last_span: Span,
}

impl<'a> Parser<'a> {
  pub fn new<'a>(cx: &'a ExtCtxt, tts: &[TokenTree]) -> Parser<'a> {
    let sess = cx.parse_sess();
    let ttsvec = tts.iter().map(|x| (*x).clone()).collect();
    let mut reader = box lexer::new_tt_reader(
        &sess.span_diagnostic, None, ttsvec) as Box<lexer::Reader>;

    let tok0 = reader.next_token();
    let token = tok0.tok;
    let span = tok0.sp;

    Parser {
      sess: sess,
      reader: reader,

      token: token,
      span: span,

      last_token: None,
      last_span: span,
    }
  }

  /// Parse the platform tree from passed in tokens.
  pub fn parse_platformtree(&mut self) -> Option<node::PlatformTree> {
    let mut nodes: HashMap<String, Gc<node::Node>> = HashMap::new();
    let mut nodes_by_path: HashMap<String, Gc<node::Node>> = HashMap::new();
    let mut failed = false;
    loop {
      if self.token == token::EOF {
        break
      }

      let node = match self.parse_node() {
        Some(node) => box(GC) node,
        None => {
          failed = true;
          self.bump();
          continue
        }
      };

      let path = node.path.clone();
      if nodes_by_path.contains_key(&path) {
        failed = true;
        self.sess.span_diagnostic.span_err(node.path_span,
            format!("duplicate node definition `{}`", path).as_slice());
        let old_node: &Gc<node::Node> = nodes_by_path.get(&path);
        self.sess.span_diagnostic.span_err(old_node.path_span,
            "previously defined here");
      } else {
        nodes_by_path.insert(node.path.clone(), node);
      }

      match node.name {
        Some(ref name) => { nodes.insert(name.clone(), node); },
        None => {
          failed = true;
          self.sess.span_diagnostic.span_err(node.name_span,
              "root node must have a name");
        }
      }
    }

    if failed {
      None
    } else {
      let mut map = HashMap::new();
      if self.collect_node_names(&mut map, &nodes) {
        Some(node::PlatformTree::new(nodes, map))
      } else {
        None
      }
    }
  }

  fn collect_node_names(&self, map: &mut HashMap<String, Gc<node::Node>>,
      nodes: &HashMap<String, Gc<node::Node>>) -> bool {
    for (_, n) in nodes.iter() {
      if !self.collect_node_names(map, &n.subnodes) {
        return false;
      }

      match n.name {
        Some(ref name) => {
          if map.contains_key(name) {
            self.sess.span_diagnostic.span_err(n.name_span, format!(
                "duplicate `{}` definition", name).as_slice());

            self.sess.span_diagnostic.span_warn(
                map.get(name).name_span,
                "previously defined here");
            return false;
          } else {
            map.insert(name.clone(), *n);
          }
        },
        None => (),
      }
    }
    true
  }

  fn parse_node(&mut self) -> Option<node::Node> {
    let name_span: Option<Span>;
    let node_name: Option<String>;

    //  we're here
    // /
    // v
    // NAME @ PATH { ... }
    //      ^-- peeking here
    if self.reader.peek().tok == token::AT {
      name_span = Some(self.span);
      node_name = match self.expect_ident() {
        Some(name) => Some(name),
        None => return None,
      };
      if !self.expect(&token::AT) {
        return None;
      }
    } else {
      node_name = None;
      name_span = None;
    }

    let node_span: Span;
    let node_path_span: Span;
    let attributes: HashMap<String, node::Attribute>;
    let subnodes: HashMap<String, Gc<node::Node>>;

    // NAME is resolved, if it was there anyway.
    //    we're here
    //          |
    //          v
    //   NAME @ PATH { ... }
    node_path_span = self.span;
    if node_name == None {
      node_span = self.span;
    } else {
      node_span = mk_sp(name_span.unwrap().lo, self.span.hi);
    }

    let node_path = match self.token {
      token::IDENT(_, _) => {
        token::to_str(&self.bump())
      },
      token::LIT_INT_UNSUFFIXED(u) => {
        self.bump();
        u.to_str()
      }
      ref other => {
        self.error(format!("expected node path but found `{}`",
            token::to_str(other)));
        return None;
      }
    };

    //    we're here
    //             |
    //             v
    // NAME @ PATH { ... }
    // it's either a body, or a semicolon (no body)
    match self.bump() {
      token::LBRACE => {
        let (a, s) = match self.parse_node_body() {
          Some((attrs, subnodes)) => (attrs, subnodes),
          // TODO(farcaller): eat everything up to '}' and continue if failed
          // we can still parse further nodes.
          None => return None,
        };
        attributes = a;
        subnodes = s;

        if !self.expect(&token::RBRACE) {
          return None;
        }
      },
      token::SEMI => {
        attributes = HashMap::new();
        subnodes = HashMap::new();
      },
      ref other => {
        self.error(format!("expected `{{` or `;` but found `{}`",
            token::to_str(other)));
        return None;
      }
    }

    let mut node = node::Node::new(node_name, node_span, node_path, node_path_span);
    node.attributes = attributes;
    node.subnodes = subnodes;
    Some(node)
  }

  fn parse_node_body(&mut self)
      -> Option<(
          HashMap<String, node::Attribute>,
          HashMap<String, Gc<node::Node>>)> {
    let mut attrs = HashMap::new();
    let mut subnodes = HashMap::new();

    loop {
      // break early if at brace
      if self.token == token::RBRACE {
        break;
      }

      // we're here
      // |
      // v
      // ATTR = VAL ;
      // NAME @ PATH
      // PATH { ... }
      // PATH ;
      //      ^-- peeking here
      if self.reader.peek().tok == token::EQ {
        // we're here
        // |
        // v
        // ATTR = VAL ;
        let name_span = self.span;
        let some_name = match self.expect_ident() {
          Some(name) => name,
          None => return None,
        };

        if attrs.contains_key(&some_name) {
          self.error(format!("key `{}` is already defined", some_name));
          return None;
        }

        self.bump(); // bump token::EQ

        // we're here
        //        |
        //        v
        // ATTR = VAL ;
        let attr_value_span = self.span;
        let attr_value = match self.parse_attribute_value() {
          Some(value) => value,
          None => return None,
        };

        //   we're here
        //            |
        //            v
        // ATTR = VAL ;
        if !self.expect(&token::SEMI) {
          return None;
        }

        attrs.insert(some_name, node::Attribute::new(
            attr_value, name_span, attr_value_span));
      } else {
        // this should be a subnode
        let oldsp = self.span;
        let oldtok = self.token.clone();
        let node = match self.parse_node() {
          Some(node) => node,
          None => {
            self.span = oldsp;
            self.error(format!("expected `=` or node but found `{}`",
                token::to_str(&oldtok)));
            return None;
          },
        };

        let path = node.path.clone();
        if subnodes.contains_key(&path) {
          self.span = node.path_span;
          self.error(format!("duplicate node definition `{}`",
              path));
          let old_node: &Gc<node::Node> = subnodes.get(&path);
          self.span = old_node.path_span.clone();
          self.error("previously defined here".to_str());
          return None;
        } else {
          subnodes.insert(path, box(GC) node);
        }
      }
    }

    Some((attrs, subnodes))
  }

  fn parse_attribute_value(&mut self) -> Option<node::AttributeValue> {
    match self.token {
      token::LIT_STR(string_val) => {
        self.bump();
        let string = token::get_ident(string_val).get().to_str();
        Some(node::StrValue(string))
      },
      token::LIT_INT_UNSUFFIXED(intval) => {
        self.bump();
        Some(node::UIntValue(intval as uint))
      },
      token::BINOP(token::AND) => {
        self.bump();
        let name = match self.expect_ident() {
          Some(name) => name,
          None => return None,
        };
        Some(node::RefValue(name))
      },
      ref other => {
        self.error(format!("expected string but found `{}`",
            token::to_str(other)));
        None
      }
    }
  }

  fn error(&self, m: String) {
    self.sess.span_diagnostic.span_err(self.span, m.as_slice());
  }

  /// Bumps a token.
  ///
  /// This moves current token to last token, pops a new token from backlog or
  /// reader and returns the last token (i.e. the 'current' token at the time of
  /// method call).
  fn bump(&mut self) -> token::Token {
    let tok = self.token.clone();
    self.last_span = self.span;
    self.last_token = Some(box tok.clone());

    let next = self.reader.next_token();

    self.span = next.sp;
    self.token = next.tok;

    tok
  }

  /// Expects that the current token is t. Bumps on success.
  fn expect(&mut self, t: &token::Token) -> bool {
    if self.token == *t {
      self.bump();
      true
    } else {
      let token_str = token::to_str(t);
      let this_token_str = token::to_str(&self.token);
      self.error(format!("expected `{}` but found `{}`", token_str,
          this_token_str));
      false
    }
  }

  /// Expects that the current token is IDENT, returns its string value. Bumps
  /// on success.
  fn expect_ident(&mut self) -> Option<String> {
    let tok_str = token::to_str(&self.token);
    match self.token {
      token::IDENT(_, _) => {
        self.bump();
        Some(tok_str)
      },
      _ => {
        self.error(format!("expected identifier but found `{}`", tok_str));
        None
      },
    }
  }
}
<|endoftext|>


<commit_before>//! # descriptor
//!
//! Loads the tasks descriptor.

//! It will first load the default descriptor which is defined in cargo-make internally and
//! afterwards tries to find the external descriptor and load it as well.

//! If an extenal descriptor exists, it will be loaded and extend the default descriptor.
//!

#[cfg(test)]
#[path = "./descriptor_test.rs"]
mod descriptor_test;

use crate::command;
use crate::types::{Config, ConfigSection, EnvValue, Extend, ExternalConfig, ModifyConfig, Task};
use crate::version;
use envmnt;
use indexmap::IndexMap;
use std::env;
use std::fs::{canonicalize, File};
use std::io::Read;
use std::path::{Path, PathBuf};
use toml;

fn merge_env(
    base: &mut IndexMap<String, EnvValue>,
    extended: &mut IndexMap<String, EnvValue>,
) -> IndexMap<String, EnvValue> {
    let mut merged = IndexMap::<String, EnvValue>::new();

    for (key, value) in base.iter() {
        let key_str = key.to_string();
        let value_clone = value.clone();
        merged.insert(key_str, value_clone);
    }

    for (key, value) in extended.iter() {
        let key_str = key.to_string();
        let value_clone = value.clone();

        if merged.contains_key(&key_str) {
            let base_value = merged.remove(&key_str).unwrap();

            match (base_value, value_clone.clone()) {
                (
                    EnvValue::Profile(ref base_profile_env),
                    EnvValue::Profile(ref extended_profile_env),
                ) => {
                    let mut base_profile_env_mut = base_profile_env.clone();
                    let mut extended_profile_env_mut = extended_profile_env.clone();

                    let merged_sub_env =
                        merge_env(&mut base_profile_env_mut, &mut extended_profile_env_mut);

                    merged.insert(key_str, EnvValue::Profile(merged_sub_env));
                }
                _ => {
                    merged.insert(key_str, value_clone);
                    ()
                }
            };
        } else {
            merged.insert(key_str, value_clone);
        }
    }

    merged
}

fn merge_tasks(
    base: &mut IndexMap<String, Task>,
    extended: &mut IndexMap<String, Task>,
) -> IndexMap<String, Task> {
    let mut merged = IndexMap::<String, Task>::new();

    for (key, value) in base.iter() {
        let key_str = key.to_string();
        merged.insert(key_str, value.clone());
    }

    for (key, value) in extended.iter() {
        let key_str = key.to_string();
        let mut task = value.clone();

        task = match base.get(key) {
            Some(ref value) => {
                let mut merged_task = Task::new();

                merged_task.extend(value);
                merged_task.extend(&task);

                merged_task
            }
            _ => task,
        };

        merged.insert(key_str, task);
    }

    merged
}

fn run_load_script(external_config: &ExternalConfig) -> bool {
    match external_config.config {
        Some(ref config) => {
            let load_script = config.get_load_script();

            match load_script {
                Some(ref script) => {
                    debug!("Load script found.");

                    command::run_script(script, None, &vec![], true);

                    true
                }
                None => {
                    debug!("No load script defined.");
                    false
                }
            }
        }
        None => {
            debug!("No load script defined.");
            false
        }
    }
}

fn merge_external_configs(config: ExternalConfig, parent_config: ExternalConfig) -> ExternalConfig {
    // merge env
    let mut parent_env = match parent_config.env {
        Some(env) => env,
        None => IndexMap::new(),
    };
    let mut extended_env = match config.env {
        Some(env) => env,
        None => IndexMap::new(),
    };
    let all_env = merge_env(&mut parent_env, &mut extended_env);

    // merge tasks
    let mut parent_tasks = match parent_config.tasks {
        Some(tasks) => tasks,
        None => IndexMap::new(),
    };
    let mut extended_tasks = match config.tasks {
        Some(tasks) => tasks,
        None => IndexMap::new(),
    };
    let all_tasks = merge_tasks(&mut parent_tasks, &mut extended_tasks);

    let mut config_section = ConfigSection::new();
    if parent_config.config.is_some() {
        config_section.extend(&mut parent_config.config.unwrap());
    }
    if config.config.is_some() {
        config_section.extend(&mut config.config.unwrap());
    }

    ExternalConfig {
        extend: None,
        config: Some(config_section),
        env: Some(all_env),
        tasks: Some(all_tasks),
    }
}

fn load_descriptor_extended_makefiles(
    parent_path: &str,
    extend_struct: &Extend,
) -> Result<ExternalConfig, String> {
    match extend_struct {
        Extend::Path(base_file) => load_external_descriptor(parent_path, &base_file, true, false),
        Extend::Options(extend_options) => {
            let force = !extend_options.optional.unwrap_or(false);
            load_external_descriptor(parent_path, &extend_options.path, force, false)
        }
        Extend::List(extend_list) => {
            let mut ordered_list_config = ExternalConfig::new();

            for entry in extend_list.iter() {
                let extend_options = entry.clone();
                let entry_config = load_descriptor_extended_makefiles(
                    parent_path,
                    &Extend::Options(extend_options),
                )?;

                // merge configs
                ordered_list_config = merge_external_configs(entry_config, ordered_list_config);
            }

            Ok(ordered_list_config)
        }
    }
}

fn load_external_descriptor(
    base_path: &str,
    file_name: &str,
    force: bool,
    set_env: bool,
) -> Result<ExternalConfig, String> {
    debug!(
        "Loading tasks from file: {} base directory: {}",
        &file_name, &base_path
    );

    let file_path = Path::new(base_path).join(file_name);

    if file_path.exists() && file_path.is_file() {
        if set_env {
            let absolute_file_path = match canonicalize(&file_path) {
                Ok(result_path) => result_path,
                _ => file_path.clone(),
            };

            envmnt::set("CARGO_MAKE_MAKEFILE_PATH", &absolute_file_path);
        }

        debug!("Opening file: {:#?}", &file_path);
        let mut file = match File::open(&file_path) {
            Ok(value) => value,
            Err(error) => panic!(
                "Unable to open file, base path: {} file name: {} error: {}",
                base_path, file_name, error
            ),
        };
        let mut external_descriptor = String::new();
        file.read_to_string(&mut external_descriptor).unwrap();

        // Descriptor version-check
        let value: toml::Value = toml::from_str(&external_descriptor)
            .unwrap_or_else(|error| panic!("Unable to parse external descriptor, {}", error));
        let min_version = value
            .get("config")
            .and_then(|config| config.get("min_version"))
            .and_then(|min_ver| min_ver.as_str());
        if let Some(ref min_version) = min_version {
            if version::is_newer_found(&min_version) {
                return Err(min_version.to_string());
            }
        }

        let file_config: ExternalConfig = match toml::from_str(&external_descriptor) {
            Ok(value) => value,
            Err(error) => panic!("Unable to parse external descriptor, {}", error),
        };
        debug!("Loaded external config: {:#?}", &file_config);

        run_load_script(&file_config);

        match file_config.extend {
            Some(ref extend_struct) => {
                let parent_path_buf = Path::new(base_path).join(file_name).join("..");
                let parent_path = file_path
                    .parent()
                    .unwrap_or(&parent_path_buf)
                    .to_str()
                    .unwrap_or(".");
                debug!("External config parent path: {}", &parent_path);

                let base_file_config =
                    load_descriptor_extended_makefiles(&parent_path, extend_struct)?;

                Ok(merge_external_configs(
                    file_config.clone(),
                    base_file_config,
                ))
            }
            None => Ok(file_config),
        }
    } else if force {
        error!("Descriptor file: {:#?} not found.", &file_path);
        panic!("Descriptor file: {:#?} not found.", &file_path);
    } else {
        info!("External file not found or is not a file, skipping.");

        Ok(ExternalConfig::new())
    }
}

pub(crate) fn load_internal_descriptors(
    stable: bool,
    experimental: bool,
    modify_config: Option<ModifyConfig>,
) -> Config {
    debug!("Loading base tasks.");

    let base_descriptor = if stable {
        include_str!("Makefile.stable.toml")
    } else {
        include_str!("Makefile.base.toml")
    };

    let mut base_config: Config = match toml::from_str(base_descriptor) {
        Ok(value) => value,
        Err(error) => panic!("Unable to parse base descriptor, {}", error),
    };
    debug!("Loaded base config: {:#?}", &base_config);

    if experimental {
        debug!("Loading experimental tasks.");
        let experimental_descriptor = include_str!("Makefile.beta.toml");

        let experimental_config: Config = match toml::from_str(experimental_descriptor) {
            Ok(value) => value,
            Err(error) => panic!("Unable to parse experimental descriptor, {}", error),
        };
        debug!("Loaded experimental config: {:#?}", &experimental_config);

        let mut base_tasks = base_config.tasks;
        let mut experimental_tasks = experimental_config.tasks;
        let all_tasks = merge_tasks(&mut base_tasks, &mut experimental_tasks);

        base_config.tasks = all_tasks;
    }

    // reset
    envmnt::set("CARGO_MAKE_CORE_TASK_NAMESPACE", "");
    envmnt::set("CARGO_MAKE_CORE_TASK_NAMESPACE_PREFIX", "");

    match modify_config {
        Some(props) => {
            base_config.apply(&props);

            match props.namespace {
                Some(ref namespace) => {
                    let prefix = props.get_namespace_prefix();

                    envmnt::set("CARGO_MAKE_CORE_TASK_NAMESPACE", &namespace);
                    envmnt::set("CARGO_MAKE_CORE_TASK_NAMESPACE_PREFIX", &prefix);
                }
                None => (),
            };
        }
        None => (),
    };

    base_config
}

/// Loads the tasks descriptor.

/// It will first load the default descriptor which is defined in cargo-make internally and
/// afterwards tries to find the external descriptor and load it as well.

/// If an extenal descriptor exists, it will be loaded and extend the default descriptor.
/// If one of the descriptor requires a newer version of cargo-make, returns an error with the
/// minimum version required by the descriptor.
fn load_descriptors(
    file_name: &str,
    force: bool,
    env_map: Option<Vec<String>>,
    stable: bool,
    experimental: bool,
    modify_core_tasks: Option<ModifyConfig>,
) -> Result<Config, String> {
    let default_config = load_internal_descriptors(stable, experimental, modify_core_tasks);

    let mut external_config: ExternalConfig =
        load_external_descriptor(".", file_name, force, true)?;

    external_config = match env::var("CARGO_MAKE_WORKSPACE_MAKEFILE") {
        Ok(workspace_makefile) => {
            let mut pathbuf = PathBuf::from(workspace_makefile);
            match pathbuf.clone().file_name() {
                Some(workspace_file_name) => match workspace_file_name.to_str() {
                    Some(workspace_file_name_str) => {
                        pathbuf.pop();

                        match pathbuf.to_str() {
                            Some(directory) => {
                                let workspace_config = load_external_descriptor(
                                    directory,
                                    workspace_file_name_str,
                                    false,
                                    false,
                                )?;
                                merge_external_configs(external_config, workspace_config)
                            }
                            _ => external_config,
                        }
                    }
                    _ => external_config,
                },
                _ => external_config,
            }
        }
        _ => external_config,
    };

    let mut external_tasks = match external_config.tasks {
        Some(tasks) => tasks,
        None => IndexMap::new(),
    };
    let mut default_tasks = default_config.tasks;

    let mut external_env = match external_config.env {
        Some(env) => env,
        None => IndexMap::new(),
    };
    let mut default_env = default_config.env;

    // merge configs
    let mut all_env = merge_env(&mut default_env, &mut external_env);
    all_env = match env_map {
        Some(values) => {
            let mut cli_env = IndexMap::new();

            for env_pair in &values {
                let env_part: Vec<&str> = env_pair.split('=').collect();
                debug!("Checking env pair: {}", &env_pair);

                if env_part.len() == 2 {
                    cli_env.insert(
                        env_part[0].to_string(),
                        EnvValue::Value(env_part[1].to_string()),
                    );
                }
            }

            merge_env(&mut all_env, &mut cli_env)
        }
        None => all_env,
    };

    let all_tasks = merge_tasks(&mut default_tasks, &mut external_tasks);

    let mut config_section = default_config.config.clone();
    config_section.extend(&mut external_config.config.unwrap_or(ConfigSection::new()));

    let config = Config {
        config: config_section,
        env: all_env,
        tasks: all_tasks,
    };

    debug!("Loaded merged config: {:#?}", &config);

    Ok(config)
}

/// Loads the tasks descriptor.

/// It will first load the default descriptor which is defined in cargo-make internally and
/// afterwards tries to find the external descriptor and load it as well.

/// If an extenal descriptor exists, it will be loaded and extend the default descriptor.

/// If one of the descriptor requires a newer version of cargo-make, returns an error with the
/// minimum version required by the descriptor.
pub(crate) fn load(
    file_name: &str,
    force: bool,
    env_map: Option<Vec<String>>,
    experimental: bool,
) -> Result<Config, String> {
    let mut config =
        load_descriptors(&file_name, force, env_map.clone(), true, experimental, None)?;

    if config.config.skip_core_tasks.unwrap_or(false) {
        config = load_descriptors(&file_name, force, env_map.clone(), false, false, None)?;
    } else {
        let modify_core_tasks = config.config.modify_core_tasks.clone();

        match modify_core_tasks {
            Some(modify_config) => {
                if modify_config.is_modifications_defined() {
                    config = load_descriptors(
                        &file_name,
                        force,
                        env_map.clone(),
                        true,
                        experimental,
                        Some(modify_config),
                    )?;
                }
            }
            None => (),
        };
    }

    Ok(config)
}
<commit_msg>Move makefile minversion check to own func<commit_after>//! # descriptor
//!
//! Loads the tasks descriptor.

//! It will first load the default descriptor which is defined in cargo-make internally and
//! afterwards tries to find the external descriptor and load it as well.

//! If an extenal descriptor exists, it will be loaded and extend the default descriptor.
//!

#[cfg(test)]
#[path = "./descriptor_test.rs"]
mod descriptor_test;

use crate::command;
use crate::types::{Config, ConfigSection, EnvValue, Extend, ExternalConfig, ModifyConfig, Task};
use crate::version;
use envmnt;
use indexmap::IndexMap;
use std::env;
use std::fs::{canonicalize, File};
use std::io::Read;
use std::path::{Path, PathBuf};
use toml;

fn merge_env(
    base: &mut IndexMap<String, EnvValue>,
    extended: &mut IndexMap<String, EnvValue>,
) -> IndexMap<String, EnvValue> {
    let mut merged = IndexMap::<String, EnvValue>::new();

    for (key, value) in base.iter() {
        let key_str = key.to_string();
        let value_clone = value.clone();
        merged.insert(key_str, value_clone);
    }

    for (key, value) in extended.iter() {
        let key_str = key.to_string();
        let value_clone = value.clone();

        if merged.contains_key(&key_str) {
            let base_value = merged.remove(&key_str).unwrap();

            match (base_value, value_clone.clone()) {
                (
                    EnvValue::Profile(ref base_profile_env),
                    EnvValue::Profile(ref extended_profile_env),
                ) => {
                    let mut base_profile_env_mut = base_profile_env.clone();
                    let mut extended_profile_env_mut = extended_profile_env.clone();

                    let merged_sub_env =
                        merge_env(&mut base_profile_env_mut, &mut extended_profile_env_mut);

                    merged.insert(key_str, EnvValue::Profile(merged_sub_env));
                }
                _ => {
                    merged.insert(key_str, value_clone);
                    ()
                }
            };
        } else {
            merged.insert(key_str, value_clone);
        }
    }

    merged
}

fn merge_tasks(
    base: &mut IndexMap<String, Task>,
    extended: &mut IndexMap<String, Task>,
) -> IndexMap<String, Task> {
    let mut merged = IndexMap::<String, Task>::new();

    for (key, value) in base.iter() {
        let key_str = key.to_string();
        merged.insert(key_str, value.clone());
    }

    for (key, value) in extended.iter() {
        let key_str = key.to_string();
        let mut task = value.clone();

        task = match base.get(key) {
            Some(ref value) => {
                let mut merged_task = Task::new();

                merged_task.extend(value);
                merged_task.extend(&task);

                merged_task
            }
            _ => task,
        };

        merged.insert(key_str, task);
    }

    merged
}

fn run_load_script(external_config: &ExternalConfig) -> bool {
    match external_config.config {
        Some(ref config) => {
            let load_script = config.get_load_script();

            match load_script {
                Some(ref script) => {
                    debug!("Load script found.");

                    command::run_script(script, None, &vec![], true);

                    true
                }
                None => {
                    debug!("No load script defined.");
                    false
                }
            }
        }
        None => {
            debug!("No load script defined.");
            false
        }
    }
}

fn merge_external_configs(config: ExternalConfig, parent_config: ExternalConfig) -> ExternalConfig {
    // merge env
    let mut parent_env = match parent_config.env {
        Some(env) => env,
        None => IndexMap::new(),
    };
    let mut extended_env = match config.env {
        Some(env) => env,
        None => IndexMap::new(),
    };
    let all_env = merge_env(&mut parent_env, &mut extended_env);

    // merge tasks
    let mut parent_tasks = match parent_config.tasks {
        Some(tasks) => tasks,
        None => IndexMap::new(),
    };
    let mut extended_tasks = match config.tasks {
        Some(tasks) => tasks,
        None => IndexMap::new(),
    };
    let all_tasks = merge_tasks(&mut parent_tasks, &mut extended_tasks);

    let mut config_section = ConfigSection::new();
    if parent_config.config.is_some() {
        config_section.extend(&mut parent_config.config.unwrap());
    }
    if config.config.is_some() {
        config_section.extend(&mut config.config.unwrap());
    }

    ExternalConfig {
        extend: None,
        config: Some(config_section),
        env: Some(all_env),
        tasks: Some(all_tasks),
    }
}

fn load_descriptor_extended_makefiles(
    parent_path: &str,
    extend_struct: &Extend,
) -> Result<ExternalConfig, String> {
    match extend_struct {
        Extend::Path(base_file) => load_external_descriptor(parent_path, &base_file, true, false),
        Extend::Options(extend_options) => {
            let force = !extend_options.optional.unwrap_or(false);
            load_external_descriptor(parent_path, &extend_options.path, force, false)
        }
        Extend::List(extend_list) => {
            let mut ordered_list_config = ExternalConfig::new();

            for entry in extend_list.iter() {
                let extend_options = entry.clone();
                let entry_config = load_descriptor_extended_makefiles(
                    parent_path,
                    &Extend::Options(extend_options),
                )?;

                // merge configs
                ordered_list_config = merge_external_configs(entry_config, ordered_list_config);
            }

            Ok(ordered_list_config)
        }
    }
}

/// Ensure the Makefile's min_version, if present, is older than cargo-make's
/// currently running version.
fn check_makefile_minversion(external_descriptor: &str) -> Result<(), String> {
    let value: toml::Value = toml::from_str(&external_descriptor)
        .unwrap_or_else(|error| panic!("Unable to parse external descriptor, {}", error));
    let min_version = value
        .get("config")
        .and_then(|config| config.get("min_version"))
        .and_then(|min_ver| min_ver.as_str());
    if let Some(ref min_version) = min_version {
        if version::is_newer_found(&min_version) {
            return Err(min_version.to_string());
        }
    }
    Ok(())
}

fn load_external_descriptor(
    base_path: &str,
    file_name: &str,
    force: bool,
    set_env: bool,
) -> Result<ExternalConfig, String> {
    debug!(
        "Loading tasks from file: {} base directory: {}",
        &file_name, &base_path
    );

    let file_path = Path::new(base_path).join(file_name);

    if file_path.exists() && file_path.is_file() {
        if set_env {
            let absolute_file_path = match canonicalize(&file_path) {
                Ok(result_path) => result_path,
                _ => file_path.clone(),
            };

            envmnt::set("CARGO_MAKE_MAKEFILE_PATH", &absolute_file_path);
        }

        debug!("Opening file: {:#?}", &file_path);
        let mut file = match File::open(&file_path) {
            Ok(value) => value,
            Err(error) => panic!(
                "Unable to open file, base path: {} file name: {} error: {}",
                base_path, file_name, error
            ),
        };
        let mut external_descriptor = String::new();
        file.read_to_string(&mut external_descriptor).unwrap();

        check_makefile_minversion(&external_descriptor)?;

        let file_config: ExternalConfig = match toml::from_str(&external_descriptor) {
            Ok(value) => value,
            Err(error) => panic!("Unable to parse external descriptor, {}", error),
        };
        debug!("Loaded external config: {:#?}", &file_config);

        run_load_script(&file_config);

        match file_config.extend {
            Some(ref extend_struct) => {
                let parent_path_buf = Path::new(base_path).join(file_name).join("..");
                let parent_path = file_path
                    .parent()
                    .unwrap_or(&parent_path_buf)
                    .to_str()
                    .unwrap_or(".");
                debug!("External config parent path: {}", &parent_path);

                let base_file_config =
                    load_descriptor_extended_makefiles(&parent_path, extend_struct)?;

                Ok(merge_external_configs(
                    file_config.clone(),
                    base_file_config,
                ))
            }
            None => Ok(file_config),
        }
    } else if force {
        error!("Descriptor file: {:#?} not found.", &file_path);
        panic!("Descriptor file: {:#?} not found.", &file_path);
    } else {
        info!("External file not found or is not a file, skipping.");

        Ok(ExternalConfig::new())
    }
}

pub(crate) fn load_internal_descriptors(
    stable: bool,
    experimental: bool,
    modify_config: Option<ModifyConfig>,
) -> Config {
    debug!("Loading base tasks.");

    let base_descriptor = if stable {
        include_str!("Makefile.stable.toml")
    } else {
        include_str!("Makefile.base.toml")
    };

    let mut base_config: Config = match toml::from_str(base_descriptor) {
        Ok(value) => value,
        Err(error) => panic!("Unable to parse base descriptor, {}", error),
    };
    debug!("Loaded base config: {:#?}", &base_config);

    if experimental {
        debug!("Loading experimental tasks.");
        let experimental_descriptor = include_str!("Makefile.beta.toml");

        let experimental_config: Config = match toml::from_str(experimental_descriptor) {
            Ok(value) => value,
            Err(error) => panic!("Unable to parse experimental descriptor, {}", error),
        };
        debug!("Loaded experimental config: {:#?}", &experimental_config);

        let mut base_tasks = base_config.tasks;
        let mut experimental_tasks = experimental_config.tasks;
        let all_tasks = merge_tasks(&mut base_tasks, &mut experimental_tasks);

        base_config.tasks = all_tasks;
    }

    // reset
    envmnt::set("CARGO_MAKE_CORE_TASK_NAMESPACE", "");
    envmnt::set("CARGO_MAKE_CORE_TASK_NAMESPACE_PREFIX", "");

    match modify_config {
        Some(props) => {
            base_config.apply(&props);

            match props.namespace {
                Some(ref namespace) => {
                    let prefix = props.get_namespace_prefix();

                    envmnt::set("CARGO_MAKE_CORE_TASK_NAMESPACE", &namespace);
                    envmnt::set("CARGO_MAKE_CORE_TASK_NAMESPACE_PREFIX", &prefix);
                }
                None => (),
            };
        }
        None => (),
    };

    base_config
}

/// Loads the tasks descriptor.

/// It will first load the default descriptor which is defined in cargo-make internally and
/// afterwards tries to find the external descriptor and load it as well.

/// If an extenal descriptor exists, it will be loaded and extend the default descriptor.
/// If one of the descriptor requires a newer version of cargo-make, returns an error with the
/// minimum version required by the descriptor.
fn load_descriptors(
    file_name: &str,
    force: bool,
    env_map: Option<Vec<String>>,
    stable: bool,
    experimental: bool,
    modify_core_tasks: Option<ModifyConfig>,
) -> Result<Config, String> {
    let default_config = load_internal_descriptors(stable, experimental, modify_core_tasks);

    let mut external_config: ExternalConfig =
        load_external_descriptor(".", file_name, force, true)?;

    external_config = match env::var("CARGO_MAKE_WORKSPACE_MAKEFILE") {
        Ok(workspace_makefile) => {
            let mut pathbuf = PathBuf::from(workspace_makefile);
            match pathbuf.clone().file_name() {
                Some(workspace_file_name) => match workspace_file_name.to_str() {
                    Some(workspace_file_name_str) => {
                        pathbuf.pop();

                        match pathbuf.to_str() {
                            Some(directory) => {
                                let workspace_config = load_external_descriptor(
                                    directory,
                                    workspace_file_name_str,
                                    false,
                                    false,
                                )?;
                                merge_external_configs(external_config, workspace_config)
                            }
                            _ => external_config,
                        }
                    }
                    _ => external_config,
                },
                _ => external_config,
            }
        }
        _ => external_config,
    };

    let mut external_tasks = match external_config.tasks {
        Some(tasks) => tasks,
        None => IndexMap::new(),
    };
    let mut default_tasks = default_config.tasks;

    let mut external_env = match external_config.env {
        Some(env) => env,
        None => IndexMap::new(),
    };
    let mut default_env = default_config.env;

    // merge configs
    let mut all_env = merge_env(&mut default_env, &mut external_env);
    all_env = match env_map {
        Some(values) => {
            let mut cli_env = IndexMap::new();

            for env_pair in &values {
                let env_part: Vec<&str> = env_pair.split('=').collect();
                debug!("Checking env pair: {}", &env_pair);

                if env_part.len() == 2 {
                    cli_env.insert(
                        env_part[0].to_string(),
                        EnvValue::Value(env_part[1].to_string()),
                    );
                }
            }

            merge_env(&mut all_env, &mut cli_env)
        }
        None => all_env,
    };

    let all_tasks = merge_tasks(&mut default_tasks, &mut external_tasks);

    let mut config_section = default_config.config.clone();
    config_section.extend(&mut external_config.config.unwrap_or(ConfigSection::new()));

    let config = Config {
        config: config_section,
        env: all_env,
        tasks: all_tasks,
    };

    debug!("Loaded merged config: {:#?}", &config);

    Ok(config)
}

/// Loads the tasks descriptor.

/// It will first load the default descriptor which is defined in cargo-make internally and
/// afterwards tries to find the external descriptor and load it as well.

/// If an extenal descriptor exists, it will be loaded and extend the default descriptor.

/// If one of the descriptor requires a newer version of cargo-make, returns an error with the
/// minimum version required by the descriptor.
pub(crate) fn load(
    file_name: &str,
    force: bool,
    env_map: Option<Vec<String>>,
    experimental: bool,
) -> Result<Config, String> {
    let mut config =
        load_descriptors(&file_name, force, env_map.clone(), true, experimental, None)?;

    if config.config.skip_core_tasks.unwrap_or(false) {
        config = load_descriptors(&file_name, force, env_map.clone(), false, false, None)?;
    } else {
        let modify_core_tasks = config.config.modify_core_tasks.clone();

        match modify_core_tasks {
            Some(modify_config) => {
                if modify_config.is_modifications_defined() {
                    config = load_descriptors(
                        &file_name,
                        force,
                        env_map.clone(),
                        true,
                        experimental,
                        Some(modify_config),
                    )?;
                }
            }
            None => (),
        };
    }

    Ok(config)
}
<|endoftext|>