<commit_before>use futures::channel::mpsc as async_mpsc;

use gettextrs::gettext;
use gstreamer as gst;
use gtk;
use gtk::prelude::*;
use log::warn;

use std::{
    borrow::ToOwned,
    path::Path,
    rc::Rc,
    string::ToString,
    sync::{Arc, RwLock},
};

use media::{MediaEvent, SplitterPipeline};
use metadata::{get_default_chapter_title, Duration, Format, MediaInfo, Stream, TocVisitor};

use super::{PlaybackPipeline, UIController, UIEventSender, UIFocusContext};

use super::output_base_controller::{
    MediaProcessor, OutputBaseController, OutputControllerImpl, OutputMediaFileInfo,
    ProcessingState, ProcessingType, MEDIA_EVENT_CHANNEL_CAPACITY,
};

pub type SplitController = OutputBaseController<SplitControllerImpl>;

impl SplitController {
    pub fn new(builder: &gtk::Builder, ui_event: UIEventSender) -> Self {
        OutputBaseController::<SplitControllerImpl>::new_base(
            SplitControllerImpl::new(builder),
            builder,
            ui_event,
        )
    }
}

macro_rules! update_list_with_format(
    ($self_:expr, $format:expr, $row:ident, $label:ident) => {
        match SplitterPipeline::check_requirements($format) {
            Ok(_) => if !$self_.is_usable {
                $self_.split_list.select_row(Some(&$self_.$row));
                $self_.is_usable = true;
            },
            Err(err) => {
                warn!("{}", err);
                $self_.$label.set_label(&err);
                $self_.$row.set_sensitive(false);
            }
        }
    };
);

pub struct SplitControllerImpl {
    is_usable: bool,

    src_info: Option<Arc<RwLock<MediaInfo>>>,
    selected_audio: Option<Stream>,

    split_file_info: Option<OutputMediaFileInfo>,
    media_event_sender: Option<async_mpsc::Sender<MediaEvent>>,
    splitter_pipeline: Option<SplitterPipeline>,
    toc_visitor: Option<TocVisitor>,
    idx: usize,
    last_progress: f64,
    current_chapter: Option<gst::TocEntry>,
    current_path: Option<Rc<Path>>,

    split_list: gtk::ListBox,
    split_to_flac_row: gtk::ListBoxRow,
    flac_warning_lbl: gtk::Label,
    split_to_wave_row: gtk::ListBoxRow,
    wave_warning_lbl: gtk::Label,
    split_to_opus_row: gtk::ListBoxRow,
    opus_warning_lbl: gtk::Label,
    split_to_vorbis_row: gtk::ListBoxRow,
    vorbis_warning_lbl: gtk::Label,
    split_to_mp3_row: gtk::ListBoxRow,
    mp3_warning_lbl: gtk::Label,

    split_btn: gtk::Button,
}

impl OutputControllerImpl for SplitControllerImpl {
    const FOCUS_CONTEXT: UIFocusContext = UIFocusContext::SplitPage;
    const BTN_NAME: &'static str = "split-btn";
    const LIST_NAME: &'static str = "split-list-box";
    const PROGRESS_BAR_NAME: &'static str = "split-progress";
}

impl UIController for SplitControllerImpl {
    fn new_media(&mut self, pipeline: &PlaybackPipeline) {
        let info_arc = Arc::clone(&pipeline.info);
        self.src_info = Some(info_arc);
    }

    fn cleanup(&mut self) {
        self.src_info = None;
        self.selected_audio = None;

        self.reset();
    }

    fn streams_changed(&mut self, info: &MediaInfo) {
        self.selected_audio = info.streams.selected_audio().map(ToOwned::to_owned);
        if self.is_usable {
            self.split_btn.set_sensitive(self.selected_audio.is_some());
        }
    }
}

impl SplitControllerImpl {
    pub fn new(builder: &gtk::Builder) -> Self {
        let mut ctrl = SplitControllerImpl {
            is_usable: false,

            src_info: None,
            selected_audio: None,

            split_file_info: None,
            media_event_sender: None,
            splitter_pipeline: None,
            toc_visitor: None,
            idx: 0,
            last_progress: 0f64,
            current_chapter: None,
            current_path: None,

            split_list: builder.get_object(Self::LIST_NAME).unwrap(),
            split_to_flac_row: builder.get_object("flac_split-row").unwrap(),
            flac_warning_lbl: builder.get_object("flac_warning-lbl").unwrap(),
            split_to_wave_row: builder.get_object("wave_split-row").unwrap(),
            wave_warning_lbl: builder.get_object("wave_warning-lbl").unwrap(),
            split_to_opus_row: builder.get_object("opus_split-row").unwrap(),
            opus_warning_lbl: builder.get_object("opus_warning-lbl").unwrap(),
            split_to_vorbis_row: builder.get_object("vorbis_split-row").unwrap(),
            vorbis_warning_lbl: builder.get_object("vorbis_warning-lbl").unwrap(),
            split_to_mp3_row: builder.get_object("mp3_split-row").unwrap(),
            mp3_warning_lbl: builder.get_object("mp3_warning-lbl").unwrap(),

            split_btn: builder.get_object(Self::BTN_NAME).unwrap(),
        };

        update_list_with_format!(ctrl, Format::Flac, split_to_flac_row, flac_warning_lbl);
        update_list_with_format!(ctrl, Format::Wave, split_to_wave_row, wave_warning_lbl);
        update_list_with_format!(ctrl, Format::Opus, split_to_opus_row, opus_warning_lbl);
        update_list_with_format!(
            ctrl,
            Format::Vorbis,
            split_to_vorbis_row,
            vorbis_warning_lbl
        );
        update_list_with_format!(ctrl, Format::MP3, split_to_mp3_row, mp3_warning_lbl);

        ctrl.split_list.set_sensitive(ctrl.is_usable);
        ctrl.split_btn.set_sensitive(ctrl.is_usable);

        ctrl
    }

    fn reset(&mut self) {
        self.split_file_info = None;
        self.media_event_sender = None;
        self.splitter_pipeline = None;
        self.toc_visitor = None;
        self.idx = 0;
        self.last_progress = 0f64;
        self.current_chapter = None;
        self.current_path = None;
    }

    fn get_split_path(&self, chapter: &gst::TocEntry) -> Rc<Path> {
        let mut split_name = String::new();

        let src_info = self.src_info.as_ref().unwrap().read().unwrap();

        // TODO: make format customisable
        let artist = src_info
            .get_media_artist_sortname()
            .or_else(|| src_info.get_media_artist());
        if let Some(artist) = artist {
            split_name += &format!("{} - ", artist);
        }

        let album_title = src_info
            .get_media_title_sortname()
            .or_else(|| src_info.get_media_title());
        if let Some(album_title) = album_title {
            split_name += &format!("{} - ", album_title);
        }

        if self.toc_visitor.is_some() {
            split_name += &format!("{:02}", self.idx);
        }

        let track_title = chapter
            .get_tags()
            .and_then(|tags| {
                tags.get::<gst::tags::Title>()
                    .and_then(|tag| tag.get().map(ToString::to_string))
            })
            .unwrap_or_else(get_default_chapter_title);

        if !split_name.is_empty() {
            split_name += ". ";
        }

        split_name += &track_title;

        let lang = self.selected_audio.as_ref().and_then(|stream| {
            stream
                .tags
                .get_index::<gst::tags::LanguageName>(0)
                .or_else(|| stream.tags.get_index::<gst::tags::LanguageCode>(0))
                .and_then(|value| value.get().map(ToString::to_string))
        });
        if let Some(lang) = lang {
            split_name += &format!(" ({})", lang);
        }

        let split_file_info = self.split_file_info.as_ref().expect(concat!(
            "SplitControllerImpl: split_file_info not defined in `get_split_path()`, ",
            "did you call `init()`?"
        ));

        split_name += &format!(".{}", split_file_info.extension);

        split_file_info.path.with_file_name(split_name).into()
    }
}

impl MediaProcessor for SplitControllerImpl {
    fn init(&mut self) -> ProcessingType {
        self.reset();

        let format = if self.split_to_flac_row.is_selected() {
            Format::Flac
        } else if self.split_to_wave_row.is_selected() {
            Format::Wave
        } else if self.split_to_opus_row.is_selected() {
            Format::Opus
        } else if self.split_to_vorbis_row.is_selected() {
            Format::Vorbis
        } else if self.split_to_mp3_row.is_selected() {
            Format::MP3
        } else {
            unreachable!("`SplitController`: unknown split type");
        };

        // Split button is not sensitive when no audio
        // stream is selected (see `streams_changed`)
        debug_assert!(self.selected_audio.is_some());

        self.toc_visitor = self
            .src_info
            .as_ref()
            .unwrap()
            .read()
            .unwrap()
            .toc
            .as_ref()
            .map(|toc| TocVisitor::new(toc));
        self.idx = 0;

        self.split_file_info = Some({
            let src_info = self.src_info.as_ref().unwrap().read().unwrap();
            OutputMediaFileInfo::new(format, &src_info)
        });

        let (sender, receiver) = async_mpsc::channel(MEDIA_EVENT_CHANNEL_CAPACITY);
        self.media_event_sender = Some(sender);

        ProcessingType::Async(receiver)
    }

    fn next(&mut self) -> Result<ProcessingState, String> {
        let chapter = match self.toc_visitor.as_mut() {
            Some(toc_visitor) => toc_visitor.next_chapter().map(|chapter| {
                self.idx += 1;
                chapter
            }),
            None => {
                // No chapter defined => build a fake chapter corresponding to the whole file
                if self.idx == 0 {
                    self.idx += 1;

                    let src_info = self.src_info.as_ref().unwrap().read().unwrap();
                    let mut toc_entry =
                        gst::TocEntry::new(gst::TocEntryType::Chapter, &"".to_owned());
                    toc_entry
                        .get_mut()
                        .unwrap()
                        .set_start_stop_times(0, src_info.duration.as_i64());

                    let mut tag_list = gst::TagList::new();
                    tag_list.get_mut().unwrap().add::<gst::tags::Title>(
                        &src_info.path.file_stem().unwrap().to_str().unwrap(),
                        gst::TagMergeMode::Replace,
                    );
                    toc_entry.get_mut().unwrap().set_tags(tag_list);

                    Some(toc_entry)
                } else {
                    None
                }
            }
        };

        if chapter.is_none() {
            self.split_file_info = None;

            return Ok(ProcessingState::AllComplete(gettext(
                "Media split succesfully",
            )));
        }

        let chapter = chapter.as_ref().unwrap();

        self.current_chapter = Some(
            self.src_info
                .as_ref()
                .unwrap()
                .read()
                .unwrap()
                .get_chapter_with_track_tags(chapter, self.idx),
        );

        let split_path = self.get_split_path(chapter);
        self.current_path = Some(Rc::clone(&split_path));

        Ok(ProcessingState::WouldOutputTo(split_path))
    }

    fn process(&mut self, output_path: &Path) -> Result<ProcessingState, String> {
        let res = {
            let src_info = self.src_info.as_ref().unwrap().read().unwrap();
            let split_file_info = self.split_file_info.as_ref().expect(
                "SplitControllerImpl: split_file_info not defined in `next()`, did you call `init()`?"
            );
            SplitterPipeline::try_new(
                &src_info.path,
                output_path,
                &self.selected_audio.as_ref().unwrap().id,
                split_file_info.format,
                self.current_chapter.take().expect(concat!(
                    "SplitControllerImpl: no current_chapter ",
                    "in `process_current_chapter()`",
                )),
                self.media_event_sender
                    .as_ref()
                    .expect(concat!(
                        "SplitControllerImpl: no media_event_sender in `process_current_chapter()` ",
                        "did you call `init()`?",
                    ))
                    .clone(),
            )
        };

        self.splitter_pipeline = Some(res.map_err(|err| {
            self.reset();
            gettext("Failed to prepare for split. {}").replacen("{}", &err, 1)
        })?);

        Ok(ProcessingState::PendingAsyncMediaEvent)
    }

    fn cancel(&mut self) {
        if let Some(pipeline) = self.splitter_pipeline.as_mut() {
            pipeline.cancel();

            if let Some(current_path) = self.current_path.take() {
                if std::fs::remove_file(&current_path).is_err() {
                    if let Some(printable_path) = current_path.to_str() {
                        warn!("Failed to remove canceled split file {}", printable_path);
                    }
                }
            }
        }

        self.reset();
    }

    fn handle_media_event(&mut self, event: MediaEvent) -> Result<ProcessingState, String> {
        match event {
            MediaEvent::Eos => {
                self.current_chapter = None;
                self.current_path = None;
                self.splitter_pipeline = None;
                Ok(ProcessingState::DoneWithCurrent)
            }
            MediaEvent::FailedToExport(err) => {
                self.reset();
                Err(gettext("Failed to split media. {}").replacen("{}", &err, 1))
            }
            other => unimplemented!("SplitController: can't handle media event {:?}", other),
        }
    }

    fn report_progress(&mut self) -> f64 {
        let duration = self.src_info.as_ref().unwrap().read().unwrap().duration;
        if duration > Duration::default() {
            // With some formats, we can't retrieve a proper ts between 2 files
            // so, just report known last progress in this case
            if let Some(ts) = self
                .splitter_pipeline
                .as_ref()
                .and_then(SplitterPipeline::get_current_ts)
            {
                self.last_progress = ts.as_f64() / duration.as_f64()
            }

            self.last_progress
        } else {
            0f64
        }
    }
}
<commit_msg>Split: better fix for the missing tags pblm<commit_after>use futures::channel::mpsc as async_mpsc;

use gettextrs::gettext;
use gstreamer as gst;
use gtk;
use gtk::prelude::*;
use log::warn;

use std::{
    borrow::ToOwned,
    path::Path,
    rc::Rc,
    string::ToString,
    sync::{Arc, RwLock},
};

use media::{MediaEvent, SplitterPipeline};
use metadata::{get_default_chapter_title, Duration, Format, MediaInfo, Stream, TocVisitor};

use super::{PlaybackPipeline, UIController, UIEventSender, UIFocusContext};

use super::output_base_controller::{
    MediaProcessor, OutputBaseController, OutputControllerImpl, OutputMediaFileInfo,
    ProcessingState, ProcessingType, MEDIA_EVENT_CHANNEL_CAPACITY,
};

pub type SplitController = OutputBaseController<SplitControllerImpl>;

impl SplitController {
    pub fn new(builder: &gtk::Builder, ui_event: UIEventSender) -> Self {
        OutputBaseController::<SplitControllerImpl>::new_base(
            SplitControllerImpl::new(builder),
            builder,
            ui_event,
        )
    }
}

macro_rules! update_list_with_format(
    ($self_:expr, $format:expr, $row:ident, $label:ident) => {
        match SplitterPipeline::check_requirements($format) {
            Ok(_) => if !$self_.is_usable {
                $self_.split_list.select_row(Some(&$self_.$row));
                $self_.is_usable = true;
            },
            Err(err) => {
                warn!("{}", err);
                $self_.$label.set_label(&err);
                $self_.$row.set_sensitive(false);
            }
        }
    };
);

pub struct SplitControllerImpl {
    is_usable: bool,

    src_info: Option<Arc<RwLock<MediaInfo>>>,
    selected_audio: Option<Stream>,

    split_file_info: Option<OutputMediaFileInfo>,
    media_event_sender: Option<async_mpsc::Sender<MediaEvent>>,
    splitter_pipeline: Option<SplitterPipeline>,
    toc_visitor: Option<TocVisitor>,
    idx: usize,
    last_progress: f64,
    current_chapter: Option<gst::TocEntry>,
    current_path: Option<Rc<Path>>,

    split_list: gtk::ListBox,
    split_to_flac_row: gtk::ListBoxRow,
    flac_warning_lbl: gtk::Label,
    split_to_wave_row: gtk::ListBoxRow,
    wave_warning_lbl: gtk::Label,
    split_to_opus_row: gtk::ListBoxRow,
    opus_warning_lbl: gtk::Label,
    split_to_vorbis_row: gtk::ListBoxRow,
    vorbis_warning_lbl: gtk::Label,
    split_to_mp3_row: gtk::ListBoxRow,
    mp3_warning_lbl: gtk::Label,

    split_btn: gtk::Button,
}

impl OutputControllerImpl for SplitControllerImpl {
    const FOCUS_CONTEXT: UIFocusContext = UIFocusContext::SplitPage;
    const BTN_NAME: &'static str = "split-btn";
    const LIST_NAME: &'static str = "split-list-box";
    const PROGRESS_BAR_NAME: &'static str = "split-progress";
}

impl UIController for SplitControllerImpl {
    fn new_media(&mut self, pipeline: &PlaybackPipeline) {
        let info_arc = Arc::clone(&pipeline.info);
        self.src_info = Some(info_arc);
    }

    fn cleanup(&mut self) {
        self.src_info = None;
        self.selected_audio = None;

        self.reset();
    }

    fn streams_changed(&mut self, info: &MediaInfo) {
        self.selected_audio = info.streams.selected_audio().map(ToOwned::to_owned);
        if self.is_usable {
            self.split_btn.set_sensitive(self.selected_audio.is_some());
        }
    }
}

impl SplitControllerImpl {
    pub fn new(builder: &gtk::Builder) -> Self {
        let mut ctrl = SplitControllerImpl {
            is_usable: false,

            src_info: None,
            selected_audio: None,

            split_file_info: None,
            media_event_sender: None,
            splitter_pipeline: None,
            toc_visitor: None,
            idx: 0,
            last_progress: 0f64,
            current_chapter: None,
            current_path: None,

            split_list: builder.get_object(Self::LIST_NAME).unwrap(),
            split_to_flac_row: builder.get_object("flac_split-row").unwrap(),
            flac_warning_lbl: builder.get_object("flac_warning-lbl").unwrap(),
            split_to_wave_row: builder.get_object("wave_split-row").unwrap(),
            wave_warning_lbl: builder.get_object("wave_warning-lbl").unwrap(),
            split_to_opus_row: builder.get_object("opus_split-row").unwrap(),
            opus_warning_lbl: builder.get_object("opus_warning-lbl").unwrap(),
            split_to_vorbis_row: builder.get_object("vorbis_split-row").unwrap(),
            vorbis_warning_lbl: builder.get_object("vorbis_warning-lbl").unwrap(),
            split_to_mp3_row: builder.get_object("mp3_split-row").unwrap(),
            mp3_warning_lbl: builder.get_object("mp3_warning-lbl").unwrap(),

            split_btn: builder.get_object(Self::BTN_NAME).unwrap(),
        };

        update_list_with_format!(ctrl, Format::Flac, split_to_flac_row, flac_warning_lbl);
        update_list_with_format!(ctrl, Format::Wave, split_to_wave_row, wave_warning_lbl);
        update_list_with_format!(ctrl, Format::Opus, split_to_opus_row, opus_warning_lbl);
        update_list_with_format!(
            ctrl,
            Format::Vorbis,
            split_to_vorbis_row,
            vorbis_warning_lbl
        );
        update_list_with_format!(ctrl, Format::MP3, split_to_mp3_row, mp3_warning_lbl);

        ctrl.split_list.set_sensitive(ctrl.is_usable);
        ctrl.split_btn.set_sensitive(ctrl.is_usable);

        ctrl
    }

    fn reset(&mut self) {
        self.split_file_info = None;
        self.media_event_sender = None;
        self.splitter_pipeline = None;
        self.toc_visitor = None;
        self.idx = 0;
        self.last_progress = 0f64;
        self.current_chapter = None;
        self.current_path = None;
    }

    fn get_split_path(&self, chapter: &gst::TocEntry) -> Rc<Path> {
        let mut split_name = String::new();

        let src_info = self.src_info.as_ref().unwrap().read().unwrap();

        // TODO: make format customisable
        let artist = src_info
            .get_media_artist_sortname()
            .or_else(|| src_info.get_media_artist());
        if let Some(artist) = artist {
            split_name += &format!("{} - ", artist);
        }

        let album_title = src_info
            .get_media_title_sortname()
            .or_else(|| src_info.get_media_title());
        if let Some(album_title) = album_title {
            split_name += &format!("{} - ", album_title);
        }

        if self.toc_visitor.is_some() {
            split_name += &format!("{:02}. ", self.idx);
        }

        let track_title = chapter
            .get_tags()
            .and_then(|tags| {
                tags.get::<gst::tags::Title>()
                    .and_then(|tag| tag.get().map(ToString::to_string))
            })
            .unwrap_or_else(get_default_chapter_title);

        split_name += &track_title;

        let lang = self.selected_audio.as_ref().and_then(|stream| {
            stream
                .tags
                .get_index::<gst::tags::LanguageName>(0)
                .or_else(|| stream.tags.get_index::<gst::tags::LanguageCode>(0))
                .and_then(|value| value.get().map(ToString::to_string))
        });
        if let Some(lang) = lang {
            split_name += &format!(" ({})", lang);
        }

        let split_file_info = self.split_file_info.as_ref().expect(concat!(
            "SplitControllerImpl: split_file_info not defined in `get_split_path()`, ",
            "did you call `init()`?"
        ));

        split_name += &format!(".{}", split_file_info.extension);

        split_file_info.path.with_file_name(split_name).into()
    }
}

impl MediaProcessor for SplitControllerImpl {
    fn init(&mut self) -> ProcessingType {
        self.reset();

        let format = if self.split_to_flac_row.is_selected() {
            Format::Flac
        } else if self.split_to_wave_row.is_selected() {
            Format::Wave
        } else if self.split_to_opus_row.is_selected() {
            Format::Opus
        } else if self.split_to_vorbis_row.is_selected() {
            Format::Vorbis
        } else if self.split_to_mp3_row.is_selected() {
            Format::MP3
        } else {
            unreachable!("`SplitController`: unknown split type");
        };

        // Split button is not sensitive when no audio
        // stream is selected (see `streams_changed`)
        debug_assert!(self.selected_audio.is_some());

        self.toc_visitor = self
            .src_info
            .as_ref()
            .unwrap()
            .read()
            .unwrap()
            .toc
            .as_ref()
            .map(|toc| TocVisitor::new(toc));
        self.idx = 0;

        self.split_file_info = Some({
            let src_info = self.src_info.as_ref().unwrap().read().unwrap();
            OutputMediaFileInfo::new(format, &src_info)
        });

        let (sender, receiver) = async_mpsc::channel(MEDIA_EVENT_CHANNEL_CAPACITY);
        self.media_event_sender = Some(sender);

        ProcessingType::Async(receiver)
    }

    fn next(&mut self) -> Result<ProcessingState, String> {
        let chapter = match self.toc_visitor.as_mut() {
            Some(toc_visitor) => toc_visitor.next_chapter().map(|chapter| {
                self.idx += 1;
                chapter
            }),
            None => {
                // No chapter defined => build a fake chapter corresponding to the whole file
                if self.idx == 0 {
                    self.idx += 1;

                    let src_info = self.src_info.as_ref().unwrap().read().unwrap();
                    let mut toc_entry =
                        gst::TocEntry::new(gst::TocEntryType::Chapter, &"".to_owned());
                    toc_entry
                        .get_mut()
                        .unwrap()
                        .set_start_stop_times(0, src_info.duration.as_i64());

                    let mut tag_list = gst::TagList::new();
                    tag_list.get_mut().unwrap().add::<gst::tags::Title>(
                        &src_info.path.file_stem().unwrap().to_str().unwrap(),
                        gst::TagMergeMode::Replace,
                    );
                    toc_entry.get_mut().unwrap().set_tags(tag_list);

                    Some(toc_entry)
                } else {
                    None
                }
            }
        };

        if chapter.is_none() {
            self.split_file_info = None;

            return Ok(ProcessingState::AllComplete(gettext(
                "Media split succesfully",
            )));
        }

        let chapter = chapter.as_ref().unwrap();

        self.current_chapter = Some(
            self.src_info
                .as_ref()
                .unwrap()
                .read()
                .unwrap()
                .get_chapter_with_track_tags(chapter, self.idx),
        );

        let split_path = self.get_split_path(chapter);
        self.current_path = Some(Rc::clone(&split_path));

        Ok(ProcessingState::WouldOutputTo(split_path))
    }

    fn process(&mut self, output_path: &Path) -> Result<ProcessingState, String> {
        let res = {
            let src_info = self.src_info.as_ref().unwrap().read().unwrap();
            let split_file_info = self.split_file_info.as_ref().expect(
                "SplitControllerImpl: split_file_info not defined in `next()`, did you call `init()`?"
            );
            SplitterPipeline::try_new(
                &src_info.path,
                output_path,
                &self.selected_audio.as_ref().unwrap().id,
                split_file_info.format,
                self.current_chapter.take().expect(concat!(
                    "SplitControllerImpl: no current_chapter ",
                    "in `process_current_chapter()`",
                )),
                self.media_event_sender
                    .as_ref()
                    .expect(concat!(
                        "SplitControllerImpl: no media_event_sender in `process_current_chapter()` ",
                        "did you call `init()`?",
                    ))
                    .clone(),
            )
        };

        self.splitter_pipeline = Some(res.map_err(|err| {
            self.reset();
            gettext("Failed to prepare for split. {}").replacen("{}", &err, 1)
        })?);

        Ok(ProcessingState::PendingAsyncMediaEvent)
    }

    fn cancel(&mut self) {
        if let Some(pipeline) = self.splitter_pipeline.as_mut() {
            pipeline.cancel();

            if let Some(current_path) = self.current_path.take() {
                if std::fs::remove_file(&current_path).is_err() {
                    if let Some(printable_path) = current_path.to_str() {
                        warn!("Failed to remove canceled split file {}", printable_path);
                    }
                }
            }
        }

        self.reset();
    }

    fn handle_media_event(&mut self, event: MediaEvent) -> Result<ProcessingState, String> {
        match event {
            MediaEvent::Eos => {
                self.current_chapter = None;
                self.current_path = None;
                self.splitter_pipeline = None;
                Ok(ProcessingState::DoneWithCurrent)
            }
            MediaEvent::FailedToExport(err) => {
                self.reset();
                Err(gettext("Failed to split media. {}").replacen("{}", &err, 1))
            }
            other => unimplemented!("SplitController: can't handle media event {:?}", other),
        }
    }

    fn report_progress(&mut self) -> f64 {
        let duration = self.src_info.as_ref().unwrap().read().unwrap().duration;
        if duration > Duration::default() {
            // With some formats, we can't retrieve a proper ts between 2 files
            // so, just report known last progress in this case
            if let Some(ts) = self
                .splitter_pipeline
                .as_ref()
                .and_then(SplitterPipeline::get_current_ts)
            {
                self.last_progress = ts.as_f64() / duration.as_f64()
            }

            self.last_progress
        } else {
            0f64
        }
    }
}
<|endoftext|>


<commit_before>use std::collections::HashMap;
use std::mem;
use slsr_core::geom::{CellId, Geom, Move};
use slsr_core::puzzle::{Edge, Puzzle};

use ::{Error, State, SolverResult};
use ::model::side_map::SideMap;
use ::model::theorem::{Pattern, Theorem, TheoremMatcher};

#[derive(Clone, Debug)]
struct TheoremCount {
    rest_count: usize,
    result: Vec<(Edge, (CellId, CellId))>
}

impl From<TheoremMatcher> for TheoremCount {
    fn from(matcher: TheoremMatcher) -> TheoremCount {
        TheoremCount {
            rest_count: matcher.num_matcher(),
            result: matcher.result_edges().collect()
        }
    }
}

impl TheoremCount {
    fn invalidate(&mut self) {
        self.rest_count = 0;
    }

    fn update(&mut self, side_map: &mut SideMap) {
        match self.rest_count {
            0 => { return }
            1 => {
                self.rest_count = 0;
                for (edge, points) in mem::replace(&mut self.result, vec![]) {
                    let _ = side_map.set_edge(points.0, points.1, edge);
                }
            }
            _ => {
                self.rest_count -= 1;
            }
        }
    }
}

#[derive(Clone, Debug)]
struct IndexByEdge {
    points: (CellId, CellId),
    expect_line: Vec<usize>,
    expect_cross: Vec<usize>
}

impl IndexByEdge {
    fn new(points: (CellId, CellId)) -> IndexByEdge {
        IndexByEdge {
            points: points,
            expect_line: vec![],
            expect_cross: vec![]
        }
    }
}

#[derive(Clone, Debug)]
pub struct TheoremPool {
    matchers: Vec<TheoremCount>,
    index_by_edge: Vec<IndexByEdge>
}

impl TheoremPool {
    pub fn new<'a, T>(theo_defs: T,
                      puzzle: &Puzzle,
                      sum_of_hint: u32,
                      side_map: &mut SideMap)
                      -> SolverResult<TheoremPool>
        where T: IntoIterator<Item=Theorem>
    {
        let mut matchers = try!(create_matcher_list(theo_defs,
                                                    puzzle,
                                                    sum_of_hint,
                                                    side_map));

        loop {
            let rev = side_map.revision();

            try!(apply_all_theorem(&mut matchers, side_map));
            if side_map.revision() != rev {
                continue
            }

            break
        }

        merge_duplicate_matchers(&mut matchers);

        let mut map = HashMap::new();
        for (i, m) in matchers.iter().enumerate() {
            for (edge, points) in m.matcher_edges() {
                let mut e = map.entry(points).or_insert(IndexByEdge::new(points));
                match edge {
                    Edge::Line => e.expect_line.push(i),
                    Edge::Cross => e.expect_cross.push(i)
                }
            }
        }

        let matchers = matchers.into_iter().map(From::from).collect();
        let edges = map.into_iter().map(|(_, v)| v).collect();

        Ok(TheoremPool { matchers: matchers, index_by_edge: edges })
    }

    pub fn apply_all(&mut self, side_map: &mut SideMap) -> SolverResult<()> {
        let cap = self.index_by_edge.len();

        for ibe in mem::replace(&mut self.index_by_edge, Vec::with_capacity(cap)) {
            match side_map.get_edge(ibe.points.0, ibe.points.1) {
                State::Fixed(Edge::Cross) => {
                    for i in ibe.expect_line {
                        self.matchers[i].invalidate();
                    }
                    for i in ibe.expect_cross {
                        self.matchers[i].update(side_map);
                    }
                }
                State::Fixed(Edge::Line) => {
                    for i in ibe.expect_line {
                        self.matchers[i].update(side_map);
                    }
                    for i in ibe.expect_cross {
                        self.matchers[i].invalidate();
                    }
                }
                State::Unknown => {
                    self.index_by_edge.push(ibe)
                }
                State::Conflict => {
                    return Err(Error::invalid_board())
                }
            }
        }

        Ok(())
    }
}

fn create_matcher_list<'a, T>(theo_defs: T,
                              puzzle: &Puzzle,
                              sum_of_hint: u32,
                              side_map: &mut SideMap)
                              -> SolverResult<Vec<TheoremMatcher>>
    where T: IntoIterator<Item=Theorem>
{
    let it = theo_defs
        .into_iter()
        .flat_map(|theo| theo.all_rotations());

    let mut hint_theorem = [vec![], vec![], vec![], vec![], vec![]];
    let mut nonhint_theorem = vec![];

    for theo in it {
        match theo.head() {
            Pattern::Hint(h) => hint_theorem[h.hint() as usize].push(theo),
            _ => nonhint_theorem.push(theo)
        }
    }

    let mut data = vec![];

    for p in puzzle.points() {
        if let Some(x) = puzzle.hint(p) {
            for theo in &hint_theorem[x as usize] {
                let o = match theo.head() {
                    Pattern::Hint(hint) => hint.point(),
                    _ => panic!()
                };
                let matcher = theo.clone().shift(p - o);
                try!(matcher.matches(puzzle, sum_of_hint, side_map))
                    .update(side_map, &mut data);
            }
        }
    }

    for theo in nonhint_theorem {
        let sz = theo.size();
        for r in (1 - sz.0)..(puzzle.row() + sz.0 - 1) {
            for c in (1 - sz.1)..(puzzle.column() + sz.1 - 1) {
                let matcher = theo.clone().shift(Move(r, c));
                try!(matcher.matches(puzzle, sum_of_hint, side_map))
                    .update(side_map, &mut data);
            }
        }
    }

    Ok(data)
}

fn apply_all_theorem(matchers: &mut Vec<TheoremMatcher>,
                     side_map: &mut SideMap)
                     -> SolverResult<()>
{
    let cap = matchers.len();

    for m in mem::replace(matchers, Vec::with_capacity(cap)) {
        try!(m.matches(side_map)).update(side_map, matchers);
    }

    Ok(())
}

fn merge_duplicate_matchers(matchers: &mut Vec<TheoremMatcher>) {
    matchers.sort();

    // Merge elements that have same matchers.
    unsafe {
        let len = matchers.len();
        let p = matchers.as_mut_ptr();

        let mut w = 1;
        for r in 1..len {
            let read = p.offset(r as isize);
            let cmp = p.offset((w - 1) as isize);

            match (*cmp).merge(&*read) {
                Ok(()) => {}
                Err(()) => {
                    let write = cmp.offset(1);
                    mem::swap(&mut *write, &mut *read);
                    w += 1;
                }
            }
        }

        matchers.truncate(w);
    }
}
<commit_msg>solver: Use Rc to improve performance.<commit_after>use std::collections::HashMap;
use std::rc::Rc;
use std::mem;
use slsr_core::geom::{CellId, Geom, Move};
use slsr_core::puzzle::{Edge, Puzzle};

use ::{Error, State, SolverResult};
use ::model::side_map::SideMap;
use ::model::theorem::{Pattern, Theorem, TheoremMatcher};

#[derive(Clone, Debug)]
struct TheoremCount {
    rest_count: usize,
    result: Option<Rc<Vec<(Edge, (CellId, CellId))>>>
}

impl From<TheoremMatcher> for TheoremCount {
    fn from(matcher: TheoremMatcher) -> TheoremCount {
        TheoremCount {
            rest_count: matcher.num_matcher(),
            result: Some(Rc::new(matcher.result_edges().collect()))
        }
    }
}

impl TheoremCount {
    fn invalidate(&mut self) {
        self.rest_count = 0;
    }

    fn update(&mut self, side_map: &mut SideMap) {
        match self.rest_count {
            0 => { return }
            1 => {
                self.rest_count = 0;
                for &(edge, points) in &*self.result.take().unwrap() {
                    let _ = side_map.set_edge(points.0, points.1, edge);
                }
            }
            _ => {
                self.rest_count -= 1;
            }
        }
    }
}

#[derive(Clone, Debug)]
struct IndexByEdge {
    points: (CellId, CellId),
    expect_line: Vec<usize>,
    expect_cross: Vec<usize>
}

impl IndexByEdge {
    fn new(points: (CellId, CellId)) -> IndexByEdge {
        IndexByEdge {
            points: points,
            expect_line: vec![],
            expect_cross: vec![]
        }
    }
}

#[derive(Clone, Debug)]
pub struct TheoremPool {
    matchers: Vec<TheoremCount>,
    index_by_edge: Vec<IndexByEdge>
}

impl TheoremPool {
    pub fn new<'a, T>(theo_defs: T,
                      puzzle: &Puzzle,
                      sum_of_hint: u32,
                      side_map: &mut SideMap)
                      -> SolverResult<TheoremPool>
        where T: IntoIterator<Item=Theorem>
    {
        let mut matchers = try!(create_matcher_list(theo_defs,
                                                    puzzle,
                                                    sum_of_hint,
                                                    side_map));

        loop {
            let rev = side_map.revision();

            try!(apply_all_theorem(&mut matchers, side_map));
            if side_map.revision() != rev {
                continue
            }

            break
        }

        merge_duplicate_matchers(&mut matchers);

        let mut map = HashMap::new();
        for (i, m) in matchers.iter().enumerate() {
            for (edge, points) in m.matcher_edges() {
                let mut e = map.entry(points).or_insert(IndexByEdge::new(points));
                match edge {
                    Edge::Line => e.expect_line.push(i),
                    Edge::Cross => e.expect_cross.push(i)
                }
            }
        }

        let matchers = matchers.into_iter().map(From::from).collect();
        let edges = map.into_iter().map(|(_, v)| v).collect();

        Ok(TheoremPool { matchers: matchers, index_by_edge: edges })
    }

    pub fn apply_all(&mut self, side_map: &mut SideMap) -> SolverResult<()> {
        let cap = self.index_by_edge.len();

        for ibe in mem::replace(&mut self.index_by_edge, Vec::with_capacity(cap)) {
            match side_map.get_edge(ibe.points.0, ibe.points.1) {
                State::Fixed(Edge::Cross) => {
                    for i in ibe.expect_line {
                        self.matchers[i].invalidate();
                    }
                    for i in ibe.expect_cross {
                        self.matchers[i].update(side_map);
                    }
                }
                State::Fixed(Edge::Line) => {
                    for i in ibe.expect_line {
                        self.matchers[i].update(side_map);
                    }
                    for i in ibe.expect_cross {
                        self.matchers[i].invalidate();
                    }
                }
                State::Unknown => {
                    self.index_by_edge.push(ibe)
                }
                State::Conflict => {
                    return Err(Error::invalid_board())
                }
            }
        }

        Ok(())
    }
}

fn create_matcher_list<'a, T>(theo_defs: T,
                              puzzle: &Puzzle,
                              sum_of_hint: u32,
                              side_map: &mut SideMap)
                              -> SolverResult<Vec<TheoremMatcher>>
    where T: IntoIterator<Item=Theorem>
{
    let it = theo_defs
        .into_iter()
        .flat_map(|theo| theo.all_rotations());

    let mut hint_theorem = [vec![], vec![], vec![], vec![], vec![]];
    let mut nonhint_theorem = vec![];

    for theo in it {
        match theo.head() {
            Pattern::Hint(h) => hint_theorem[h.hint() as usize].push(theo),
            _ => nonhint_theorem.push(theo)
        }
    }

    let mut data = vec![];

    for p in puzzle.points() {
        if let Some(x) = puzzle.hint(p) {
            for theo in &hint_theorem[x as usize] {
                let o = match theo.head() {
                    Pattern::Hint(hint) => hint.point(),
                    _ => panic!()
                };
                let matcher = theo.clone().shift(p - o);
                try!(matcher.matches(puzzle, sum_of_hint, side_map))
                    .update(side_map, &mut data);
            }
        }
    }

    for theo in nonhint_theorem {
        let sz = theo.size();
        for r in (1 - sz.0)..(puzzle.row() + sz.0 - 1) {
            for c in (1 - sz.1)..(puzzle.column() + sz.1 - 1) {
                let matcher = theo.clone().shift(Move(r, c));
                try!(matcher.matches(puzzle, sum_of_hint, side_map))
                    .update(side_map, &mut data);
            }
        }
    }

    Ok(data)
}

fn apply_all_theorem(matchers: &mut Vec<TheoremMatcher>,
                     side_map: &mut SideMap)
                     -> SolverResult<()>
{
    let cap = matchers.len();

    for m in mem::replace(matchers, Vec::with_capacity(cap)) {
        try!(m.matches(side_map)).update(side_map, matchers);
    }

    Ok(())
}

fn merge_duplicate_matchers(matchers: &mut Vec<TheoremMatcher>) {
    matchers.sort();

    // Merge elements that have same matchers.
    unsafe {
        let len = matchers.len();
        let p = matchers.as_mut_ptr();

        let mut w = 1;
        for r in 1..len {
            let read = p.offset(r as isize);
            let cmp = p.offset((w - 1) as isize);

            match (*cmp).merge(&*read) {
                Ok(()) => {}
                Err(()) => {
                    let write = cmp.offset(1);
                    mem::swap(&mut *write, &mut *read);
                    w += 1;
                }
            }
        }

        matchers.truncate(w);
    }
}
<|endoftext|>


<commit_before>#![feature(env, path)]

extern crate "pkg-config" as pkg_config;
extern crate gcc;

use std::env;
use std::path::PathBuf;

fn main() {

    let mut lmdb: PathBuf = PathBuf::new(&env::var("CARGO_MANIFEST_DIR").unwrap());
    lmdb.push("mdb");
    lmdb.push("libraries");
    lmdb.push("liblmdb");

    let mut mdb: PathBuf = lmdb.clone();
    let mut midl: PathBuf = lmdb.clone();

    mdb.push("mdb.c");
    midl.push("midl.c");

    if !pkg_config::find_library("liblmdb").is_ok() {
        gcc::compile_library("liblmdb.a",
                             &[(*mdb).to_str().unwrap(),
                               (*midl).to_str().unwrap()]);
    }
}
<commit_msg>std::env is stable<commit_after>#![feature(path)]

extern crate "pkg-config" as pkg_config;
extern crate gcc;

use std::env;
use std::path::PathBuf;

fn main() {

    let mut lmdb: PathBuf = PathBuf::new(&env::var("CARGO_MANIFEST_DIR").unwrap());
    lmdb.push("mdb");
    lmdb.push("libraries");
    lmdb.push("liblmdb");

    let mut mdb: PathBuf = lmdb.clone();
    let mut midl: PathBuf = lmdb.clone();

    mdb.push("mdb.c");
    midl.push("midl.c");

    if !pkg_config::find_library("liblmdb").is_ok() {
        gcc::compile_library("liblmdb.a",
                             &[(*mdb).to_str().unwrap(),
                               (*midl).to_str().unwrap()]);
    }
}
<|endoftext|>


<commit_before>extern crate proc_macro;

use proc_macro::TokenStream;

use quote::quote;
use syn::{parse_macro_input, parse_quote};

#[proc_macro_attribute]
pub fn pam_enum(_: TokenStream, input: TokenStream) -> TokenStream {
    let item = parse_macro_input!(input as syn::Item);

    // Match only enums
    let mut def = match item {
        syn::Item::Enum(ie) => ie,
        _ => {
            // FIXME: slightly clunky
            return TokenStream::from(
                syn::Error::new_spanned(item, "[pam_enum] only works on enums").to_compile_error(),
            );
        }
    };

    // Clone original variants and create prefixed idents as they are used multiple times
    let variants: Vec<_> = def.variants.iter().cloned().collect();
    let idents: Vec<_> = def
        .variants
        .iter()
        .map(|v| prefix_ident(&v.ident, "PAM_"))
        .collect();

    // Attach additional derives to enum definition
    def.attrs.extend(derive_attrs());

    // Build enum variants from uppercased identifiers in pam_sys::
    def.variants = build_variants(&variants, &idents);

    // Build additional impl block for From<u32>
    let impl_block = build_impl_block(&def.ident, &variants, &idents);

    // Assemble the final TokenStream
    let output = quote! {
        #def
        #impl_block
    };

    output.into()
}

fn derive_attrs() -> Vec<syn::Attribute> {
    vec![
        parse_quote! { #[repr(C)] },
        parse_quote! { #[derive(Debug)] },
        parse_quote! { #[derive(Copy)] },
        parse_quote! { #[derive(Clone)] },
        parse_quote! { #[derive(PartialEq)] },
    ]
}

fn build_variants(
    variants: &[syn::Variant],
    idents: &[syn::Ident],
) -> syn::punctuated::Punctuated<syn::Variant, syn::token::Comma> {
    variants
        .iter()
        .zip(idents)
        .map(|(var, id)| {
            let mut var = var.clone();
            // Only insert our discriminant of none was provided
            if var.discriminant.is_none() {
                var.discriminant = Some((parse_quote!(=), parse_quote!(pam_sys::#id as isize)));
            }
            var
        })
        .collect()
}

fn build_impl_block(
    enum_name: &syn::Ident,
    variants: &[syn::Variant],
    idents: &[syn::Ident],
) -> syn::ItemImpl {
    let default = &variants[0].ident;

    let arms: Vec<syn::Arm> = variants
        .iter()
        .zip(idents)
        .map(|(var, id)| {
            let v_id = &var.ident;
            if let Some((_, ref expr)) = var.discriminant {
                // If we have an original expression for the variant, then use it..
                parse_quote!(#expr => #enum_name::#v_id,)
            } else {
                // otherwise, fallback to pam_sys
                // FIXME: This guard should not be necessary
                parse_quote!(x if x as u32 == pam_sys::#id => #enum_name::#v_id,)
            }
        })
        .collect();

    parse_quote! {
        impl std::convert::From<i32> for #enum_name {
            fn from(value: i32) -> Self {
                match value {
                    #(#arms)*
                    _ => #enum_name::#default
                }
            }
        }
    }
}

fn prefix_ident(ident: &syn::Ident, prefix: &str) -> syn::Ident {
    syn::Ident::new(
        &format!("{}{}", prefix, ident.to_string().to_uppercase()),
        ident.span(),
    )
}
<commit_msg>macros: fix constants being i32<commit_after>extern crate proc_macro;

use proc_macro::TokenStream;

use quote::quote;
use syn::{parse_macro_input, parse_quote};

#[proc_macro_attribute]
pub fn pam_enum(_: TokenStream, input: TokenStream) -> TokenStream {
    let item = parse_macro_input!(input as syn::Item);

    // Match only enums
    let mut def = match item {
        syn::Item::Enum(ie) => ie,
        _ => {
            // FIXME: slightly clunky
            return TokenStream::from(
                syn::Error::new_spanned(item, "[pam_enum] only works on enums").to_compile_error(),
            );
        }
    };

    // Clone original variants and create prefixed idents as they are used multiple times
    let variants: Vec<_> = def.variants.iter().cloned().collect();
    let idents: Vec<_> = def
        .variants
        .iter()
        .map(|v| prefix_ident(&v.ident, "PAM_"))
        .collect();

    // Attach additional derives to enum definition
    def.attrs.extend(derive_attrs());

    // Build enum variants from uppercased identifiers in pam_sys::
    def.variants = build_variants(&variants, &idents);

    // Build additional impl block for From<i32>
    let impl_block = build_impl_block(&def.ident, &variants, &idents);

    // Assemble the final TokenStream
    let output = quote! {
        #def
        #impl_block
    };

    output.into()
}

fn derive_attrs() -> Vec<syn::Attribute> {
    vec![
        parse_quote! { #[repr(C)] },
        parse_quote! { #[derive(Debug)] },
        parse_quote! { #[derive(Copy)] },
        parse_quote! { #[derive(Clone)] },
        parse_quote! { #[derive(PartialEq)] },
    ]
}

fn build_variants(
    variants: &[syn::Variant],
    idents: &[syn::Ident],
) -> syn::punctuated::Punctuated<syn::Variant, syn::token::Comma> {
    variants
        .iter()
        .zip(idents)
        .map(|(var, id)| {
            let mut var = var.clone();
            // Only insert our discriminant if none was provided
            if var.discriminant.is_none() {
                var.discriminant = Some((parse_quote!(=), parse_quote!(pam_sys::#id as isize)));
            }
            var
        })
        .collect()
}

fn build_impl_block(
    enum_name: &syn::Ident,
    variants: &[syn::Variant],
    idents: &[syn::Ident],
) -> syn::ItemImpl {
    let default = &variants[0].ident;

    let arms: Vec<syn::Arm> = variants
        .iter()
        .zip(idents)
        .map(|(var, id)| {
            let v_id = &var.ident;
            if let Some((_, ref expr)) = var.discriminant {
                // If we have an original expression for the variant, then use it..
                parse_quote!(#expr => #enum_name::#v_id,)
            } else {
                // otherwise, fallback to pam_sys
                // FIXME: This guard should not be necessary
                parse_quote!(x if x == pam_sys::#id => #enum_name::#v_id,)
            }
        })
        .collect();

    parse_quote! {
        impl std::convert::From<i32> for #enum_name {
            fn from(value: i32) -> Self {
                match value {
                    #(#arms)*
                    _ => #enum_name::#default
                }
            }
        }
    }
}

fn prefix_ident(ident: &syn::Ident, prefix: &str) -> syn::Ident {
    syn::Ident::new(
        &format!("{}{}", prefix, ident.to_string().to_uppercase()),
        ident.span(),
    )
}
<|endoftext|>


<commit_before>use clap::{App, Arg};

fn main() {
    // This method shows the traditional, and slightly more configurable way to set up arguments. This method is
    // more verbose, but allows setting more configuration options, and even supports easier dynamic generation.
    //
    // The example below is functionally identical to the 01a_quick_example.rs and 01c_quick_example.rs
    //
    // *NOTE:* You can actually achieve the best of both worlds by using Arg::from() (instead of Arg::new())
    // and *then* setting any additional properties.
    //
    // Create an application with 5 possible arguments (2 auto generated) and 2 subcommands (1 auto generated)
    //    - A config file
    //        + Uses "-c filename" or "--config filename"
    //    - An output file
    //        + A positional argument (i.e. "$ myapp output_filename")
    //    - A debug flag
    //        + Uses "-d" or "--debug"
    //        + Allows multiple occurrences of such as "-dd" (for vary levels of debugging, as an example)
    //    - A help flag (automatically generated by clap)
    //        + Uses "-h" or "--help" (Only autogenerated if you do NOT specify your own "-h" or "--help")
    //    - A version flag (automatically generated by clap)
    //        + Uses "-V" or "--version" (Only autogenerated if you do NOT specify your own "-V" or "--version")
    //    - A subcommand "test" (subcommands behave like their own apps, with their own arguments
    //        + Used by "$ myapp test" with the following arguments
    //            > A list flag
    //                = Uses "-l" (usage is "$ myapp test -l"
    //            > A help flag (automatically generated by clap
    //                = Uses "-h" or "--help" (full usage "$ myapp test -h" or "$ myapp test --help")
    //            > A version flag (automatically generated by clap
    //                = Uses "-V" or "--version" (full usage "$ myapp test -V" or "$ myapp test --version")
    //    - A subcommand "help" (automatically generated by clap because we specified a subcommand of our own)
    //        + Used by "$ myapp help" (same functionality as "-h" or "--help")
    let matches = App::new("MyApp")
        .version("1.0")
        .author("Kevin K. <kbknapp@gmail.com>")
        .about("Does awesome things")
        .arg(
            Arg::new("config")
                .short('c')
                .long("config")
                .value_name("FILE")
                .about("Sets a custom config file")
                .takes_value(true),
        )
        .arg(
            Arg::new("output")
                .about("Sets an optional output file")
                .index(1),
        )
        .arg(
            Arg::new("debug")
                .short('d')
                .multiple(true)
                .about("Turn debugging information on"),
        )
        .subcommand(
            App::new("test")
                .about("does testing things")
                .arg(Arg::new("list").short('l').about("lists test values")),
        )
        .get_matches();

    // You can check the value provided by positional arguments, or option arguments
    if let Some(o) = matches.value_of("output") {
        println!("Value for output: {}", o);
    }

    if let Some(c) = matches.value_of("config") {
        println!("Value for config: {}", c);
    }

    // You can see how many times a particular flag or argument occurred
    // Note, only flags can have multiple occurrences
    match matches.occurrences_of("debug") {
        0 => println!("Debug mode is off"),
        1 => println!("Debug mode is kind of on"),
        2 => println!("Debug mode is on"),
        _ => println!("Don't be crazy"),
    }

    // You can check for the existence of subcommands, and if found use their
    // matches just as you would the top level app
    if let Some(ref matches) = matches.subcommand_matches("test") {
        // "$ myapp test" was run
        if matches.is_present("list") {
            // "$ myapp test -l" was run
            println!("Printing testing lists...");
        } else {
            println!("Not printing testing lists...");
        }
    }

    // Continued program logic goes here...
}
<commit_msg>Modify debug option to conform to specification.<commit_after>use clap::{App, Arg};

fn main() {
    // This method shows the traditional, and slightly more configurable way to set up arguments. This method is
    // more verbose, but allows setting more configuration options, and even supports easier dynamic generation.
    //
    // The example below is functionally identical to the 01a_quick_example.rs and 01c_quick_example.rs
    //
    // *NOTE:* You can actually achieve the best of both worlds by using Arg::from() (instead of Arg::new())
    // and *then* setting any additional properties.
    //
    // Create an application with 5 possible arguments (2 auto generated) and 2 subcommands (1 auto generated)
    //    - A config file
    //        + Uses "-c filename" or "--config filename"
    //    - An output file
    //        + A positional argument (i.e. "$ myapp output_filename")
    //    - A debug flag
    //        + Uses "-d" or "--debug"
    //        + Allows multiple occurrences of such as "-dd" (for vary levels of debugging, as an example)
    //    - A help flag (automatically generated by clap)
    //        + Uses "-h" or "--help" (Only autogenerated if you do NOT specify your own "-h" or "--help")
    //    - A version flag (automatically generated by clap)
    //        + Uses "-V" or "--version" (Only autogenerated if you do NOT specify your own "-V" or "--version")
    //    - A subcommand "test" (subcommands behave like their own apps, with their own arguments
    //        + Used by "$ myapp test" with the following arguments
    //            > A list flag
    //                = Uses "-l" (usage is "$ myapp test -l"
    //            > A help flag (automatically generated by clap
    //                = Uses "-h" or "--help" (full usage "$ myapp test -h" or "$ myapp test --help")
    //            > A version flag (automatically generated by clap
    //                = Uses "-V" or "--version" (full usage "$ myapp test -V" or "$ myapp test --version")
    //    - A subcommand "help" (automatically generated by clap because we specified a subcommand of our own)
    //        + Used by "$ myapp help" (same functionality as "-h" or "--help")
    let matches = App::new("MyApp")
        .version("1.0")
        .author("Kevin K. <kbknapp@gmail.com>")
        .about("Does awesome things")
        .arg(
            Arg::new("config")
                .short('c')
                .long("config")
                .value_name("FILE")
                .about("Sets a custom config file")
                .takes_value(true),
        )
        .arg(
            Arg::new("output")
                .about("Sets an optional output file")
                .index(1),
        )
        .arg(
            Arg::new("debug")
                .short('d')
                .long("debug")
                .multiple_occurrences(true)
                .about("Turn debugging information on"),
        )
        .subcommand(
            App::new("test")
                .about("does testing things")
                .arg(Arg::new("list").short('l').about("lists test values")),
        )
        .get_matches();

    // You can check the value provided by positional arguments, or option arguments
    if let Some(o) = matches.value_of("output") {
        println!("Value for output: {}", o);
    }

    if let Some(c) = matches.value_of("config") {
        println!("Value for config: {}", c);
    }

    // You can see how many times a particular flag or argument occurred
    // Note, only flags can have multiple occurrences
    match matches.occurrences_of("debug") {
        0 => println!("Debug mode is off"),
        1 => println!("Debug mode is kind of on"),
        2 => println!("Debug mode is on"),
        _ => println!("Don't be crazy"),
    }

    // You can check for the existence of subcommands, and if found use their
    // matches just as you would the top level app
    if let Some(ref matches) = matches.subcommand_matches("test") {
        // "$ myapp test" was run
        if matches.is_present("list") {
            // "$ myapp test -l" was run
            println!("Printing testing lists...");
        } else {
            println!("Not printing testing lists...");
        }
    }

    // Continued program logic goes here...
}
<|endoftext|>


<commit_before>use std::fs::File;
use std::io::prelude::*;
use std::path::PathBuf;

use rustc_feature::UnstableFeatures;
use rustc_span::edition::Edition;
use rustc_span::source_map::DUMMY_SP;

use crate::config::{Options, RenderOptions};
use crate::externalfiles::{load_string, LoadStringError};
use crate::html::escape::Escape;
use crate::html::markdown;
use crate::html::markdown::{find_testable_code, ErrorCodes, IdMap, Markdown, MarkdownWithToc};
use crate::test::{Collector, TestOptions};

/// Separate any lines at the start of the file that begin with `# ` or `%`.
fn extract_leading_metadata(s: &str) -> (Vec<&str>, &str) {
    let mut metadata = Vec::new();
    let mut count = 0;

    for line in s.lines() {
        if line.starts_with("# ") || line.starts_with('%') {
            // trim the whitespace after the symbol
            metadata.push(line[1..].trim_start());
            count += line.len() + 1;
        } else {
            return (metadata, &s[count..]);
        }
    }

    // if we're here, then all lines were metadata `# ` or `%` lines.
    (metadata, "")
}

/// Render `input` (e.g., "foo.md") into an HTML file in `output`
/// (e.g., output = "bar" => "bar/foo.html").
pub fn render(
    input: PathBuf,
    options: RenderOptions,
    diag: &rustc_errors::Handler,
    edition: Edition,
) -> i32 {
    let mut output = options.output;
    output.push(input.file_name().unwrap());
    output.set_extension("html");

    let mut css = String::new();
    for name in &options.markdown_css {
        let s = format!("<link rel=\"stylesheet\" type=\"text/css\" href=\"{}\">\n", name);
        css.push_str(&s)
    }

    let input_str = match load_string(&input, diag) {
        Ok(s) => s,
        Err(LoadStringError::ReadFail) => return 1,
        Err(LoadStringError::BadUtf8) => return 2,
    };
    let playground_url = options.markdown_playground_url.or(options.playground_url);
    let playground = playground_url.map(|url| markdown::Playground { crate_name: None, url });

    let mut out = match File::create(&output) {
        Err(e) => {
            diag.struct_err(&format!("{}: {}", output.display(), e)).emit();
            return 4;
        }
        Ok(f) => f,
    };

    let (metadata, text) = extract_leading_metadata(&input_str);
    if metadata.is_empty() {
        diag.struct_err("invalid markdown file: no initial lines starting with `# ` or `%`").emit();
        return 5;
    }
    let title = metadata[0];

    let mut ids = IdMap::new();
    let error_codes = ErrorCodes::from(UnstableFeatures::from_environment().is_nightly_build());
    let text = if !options.markdown_no_toc {
        MarkdownWithToc(text, &mut ids, error_codes, edition, &playground).to_string()
    } else {
        Markdown(text, &[], &mut ids, error_codes, edition, &playground).to_string()
    };

    let err = write!(
        &mut out,
        r#"<!DOCTYPE html>

    <meta charset="utf-8">
    <meta name="viewport" content="width=device-width, initial-scale=1.0">
    <meta name="generator" content="rustdoc">
    <title>{title}</title>

    {css}
    {in_header}

    {before_content}

{title}

    {text}
    {after_content}

"#,
        title = Escape(title),
        css = css,
        in_header = options.external_html.in_header,
        before_content = options.external_html.before_content,
        text = text,
        after_content = options.external_html.after_content,
    );

    match err {
        Err(e) => {
            diag.struct_err(&format!("cannot write to `{}`: {}", output.display(), e)).emit();
            6
        }
        Ok(_) => 0,
    }
}

/// Runs any tests/code examples in the markdown file `input`.
pub fn test(mut options: Options, diag: &rustc_errors::Handler) -> i32 {
    let input_str = match load_string(&options.input, diag) {
        Ok(s) => s,
        Err(LoadStringError::ReadFail) => return 1,
        Err(LoadStringError::BadUtf8) => return 2,
    };

    let mut opts = TestOptions::default();
    opts.no_crate_inject = true;
    opts.display_warnings = options.display_warnings;
    let mut collector = Collector::new(
        options.input.display().to_string(),
        options.clone(),
        true,
        opts,
        None,
        Some(options.input),
        options.enable_per_target_ignores,
    );
    collector.set_position(DUMMY_SP);
    let codes = ErrorCodes::from(UnstableFeatures::from_environment().is_nightly_build());

    find_testable_code(&input_str, &mut collector, codes, options.enable_per_target_ignores);

    options.test_args.insert(0, "rustdoctest".to_string());
    testing::test_main(
        &options.test_args,
        collector.tests,
        Some(testing::Options::new().display_output(options.display_warnings)),
    );
    0
}
<commit_msg>Rollup merge of #70448 - TimotheeGerber:rustdoc-create-output-dir, r=GuillaumeGomez<commit_after>use std::fs::{create_dir_all, File};
use std::io::prelude::*;
use std::path::PathBuf;

use rustc_feature::UnstableFeatures;
use rustc_span::edition::Edition;
use rustc_span::source_map::DUMMY_SP;

use crate::config::{Options, RenderOptions};
use crate::externalfiles::{load_string, LoadStringError};
use crate::html::escape::Escape;
use crate::html::markdown;
use crate::html::markdown::{find_testable_code, ErrorCodes, IdMap, Markdown, MarkdownWithToc};
use crate::test::{Collector, TestOptions};

/// Separate any lines at the start of the file that begin with `# ` or `%`.
fn extract_leading_metadata(s: &str) -> (Vec<&str>, &str) {
    let mut metadata = Vec::new();
    let mut count = 0;

    for line in s.lines() {
        if line.starts_with("# ") || line.starts_with('%') {
            // trim the whitespace after the symbol
            metadata.push(line[1..].trim_start());
            count += line.len() + 1;
        } else {
            return (metadata, &s[count..]);
        }
    }

    // if we're here, then all lines were metadata `# ` or `%` lines.
    (metadata, "")
}

/// Render `input` (e.g., "foo.md") into an HTML file in `output`
/// (e.g., output = "bar" => "bar/foo.html").
pub fn render(
    input: PathBuf,
    options: RenderOptions,
    diag: &rustc_errors::Handler,
    edition: Edition,
) -> i32 {
    if let Err(e) = create_dir_all(&options.output) {
        diag.struct_err(&format!("{}: {}", options.output.display(), e)).emit();
        return 4;
    }

    let mut output = options.output;
    output.push(input.file_name().unwrap());
    output.set_extension("html");

    let mut css = String::new();
    for name in &options.markdown_css {
        let s = format!("<link rel=\"stylesheet\" type=\"text/css\" href=\"{}\">\n", name);
        css.push_str(&s)
    }

    let input_str = match load_string(&input, diag) {
        Ok(s) => s,
        Err(LoadStringError::ReadFail) => return 1,
        Err(LoadStringError::BadUtf8) => return 2,
    };
    let playground_url = options.markdown_playground_url.or(options.playground_url);
    let playground = playground_url.map(|url| markdown::Playground { crate_name: None, url });

    let mut out = match File::create(&output) {
        Err(e) => {
            diag.struct_err(&format!("{}: {}", output.display(), e)).emit();
            return 4;
        }
        Ok(f) => f,
    };

    let (metadata, text) = extract_leading_metadata(&input_str);
    if metadata.is_empty() {
        diag.struct_err("invalid markdown file: no initial lines starting with `# ` or `%`").emit();
        return 5;
    }
    let title = metadata[0];

    let mut ids = IdMap::new();
    let error_codes = ErrorCodes::from(UnstableFeatures::from_environment().is_nightly_build());
    let text = if !options.markdown_no_toc {
        MarkdownWithToc(text, &mut ids, error_codes, edition, &playground).to_string()
    } else {
        Markdown(text, &[], &mut ids, error_codes, edition, &playground).to_string()
    };

    let err = write!(
        &mut out,
        r#"<!DOCTYPE html>

    <meta charset="utf-8">
    <meta name="viewport" content="width=device-width, initial-scale=1.0">
    <meta name="generator" content="rustdoc">
    <title>{title}</title>

    {css}
    {in_header}

    {before_content}

{title}

    {text}
    {after_content}

"#,
        title = Escape(title),
        css = css,
        in_header = options.external_html.in_header,
        before_content = options.external_html.before_content,
        text = text,
        after_content = options.external_html.after_content,
    );

    match err {
        Err(e) => {
            diag.struct_err(&format!("cannot write to `{}`: {}", output.display(), e)).emit();
            6
        }
        Ok(_) => 0,
    }
}

/// Runs any tests/code examples in the markdown file `input`.
pub fn test(mut options: Options, diag: &rustc_errors::Handler) -> i32 {
    let input_str = match load_string(&options.input, diag) {
        Ok(s) => s,
        Err(LoadStringError::ReadFail) => return 1,
        Err(LoadStringError::BadUtf8) => return 2,
    };

    let mut opts = TestOptions::default();
    opts.no_crate_inject = true;
    opts.display_warnings = options.display_warnings;
    let mut collector = Collector::new(
        options.input.display().to_string(),
        options.clone(),
        true,
        opts,
        None,
        Some(options.input),
        options.enable_per_target_ignores,
    );
    collector.set_position(DUMMY_SP);
    let codes = ErrorCodes::from(UnstableFeatures::from_environment().is_nightly_build());

    find_testable_code(&input_str, &mut collector, codes, options.enable_per_target_ignores);

    options.test_args.insert(0, "rustdoctest".to_string());
    testing::test_main(
        &options.test_args,
        collector.tests,
        Some(testing::Options::new().display_output(options.display_warnings)),
    );
    0
}
<|endoftext|>


<commit_before>// Copyright 2014 The Rust Project Developers. See the COPYRIGHT
// file at the top-level directory of this distribution and at
// http://rust-lang.org/COPYRIGHT.
//
// Licensed under the Apache License, Version 2.0 <LICENSE-APACHE or
// http://www.apache.org/licenses/LICENSE-2.0> or the MIT license
// <LICENSE-MIT or http://opensource.org/licenses/MIT>, at your
// option. This file may not be copied, modified, or distributed
// except according to those terms.

use prelude::v1::*;

use sys::fs::FileDesc;
use libc::{self, c_int};
use io::{self, IoResult, IoError};
use sys_common;

pub struct TTY {
    pub fd: FileDesc,
}

impl TTY {
    pub fn new(fd: c_int) -> IoResult<TTY> {
        if unsafe { libc::isatty(fd) } != 0 {
            Ok(TTY { fd: FileDesc::new(fd, true) })
        } else {
            Err(IoError {
                kind: io::MismatchedFileTypeForOperation,
                desc: "file descriptor is not a TTY",
                detail: None,
            })
        }
    }

    pub fn read(&mut self, buf: &mut [u8]) -> IoResult<uint> {
        self.fd.read(buf)
    }
    pub fn write(&mut self, buf: &[u8]) -> IoResult<()> {
        self.fd.write(buf)
    }
    pub fn set_raw(&mut self, _raw: bool) -> IoResult<()> {
        Err(sys_common::unimpl())
    }
    pub fn get_winsize(&mut self) -> IoResult<(int, int)> {
        Err(sys_common::unimpl())
    }
    pub fn isatty(&self) -> bool { false }
}
<commit_msg>Implement winsize() for unix.<commit_after>// Copyright 2014 The Rust Project Developers. See the COPYRIGHT
// file at the top-level directory of this distribution and at
// http://rust-lang.org/COPYRIGHT.
//
// Licensed under the Apache License, Version 2.0 <LICENSE-APACHE or
// http://www.apache.org/licenses/LICENSE-2.0> or the MIT license
// <LICENSE-MIT or http://opensource.org/licenses/MIT>, at your
// option. This file may not be copied, modified, or distributed
// except according to those terms.

use prelude::v1::*;

use sys::fs::FileDesc;
use libc::{self, c_int, c_ulong, funcs};
use io::{self, IoResult, IoError};
use sys::c;
use sys_common;

pub struct TTY {
    pub fd: FileDesc,
}

#[cfg(any(target_os = "macos",
          target_os = "freebsd"))]
const TIOCGWINSZ: c_ulong = 0x40087468;

#[cfg(any(target_os = "linux", target_os = "android"))]
const TIOCGWINSZ: c_ulong = 0x00005413;

impl TTY {
    pub fn new(fd: c_int) -> IoResult<TTY> {
        if unsafe { libc::isatty(fd) } != 0 {
            Ok(TTY { fd: FileDesc::new(fd, true) })
        } else {
            Err(IoError {
                kind: io::MismatchedFileTypeForOperation,
                desc: "file descriptor is not a TTY",
                detail: None,
            })
        }
    }

    pub fn read(&mut self, buf: &mut [u8]) -> IoResult<uint> {
        self.fd.read(buf)
    }
    pub fn write(&mut self, buf: &[u8]) -> IoResult<()> {
        self.fd.write(buf)
    }
    pub fn set_raw(&mut self, _raw: bool) -> IoResult<()> {
        Err(sys_common::unimpl())
    }

    #[cfg(any(target_os = "linux",
              target_os = "android",
              target_os = "macos",
              target_os = "freebsd"))]
    pub fn get_winsize(&mut self) -> IoResult<(int, int)> {
        unsafe {
            #[repr(C)]
            struct winsize {
                ws_row: u16,
                ws_col: u16,
                ws_xpixel: u16,
                ws_ypixel: u16
            }

            let mut size = winsize { ws_row: 0, ws_col: 0, ws_xpixel: 0, ws_ypixel: 0 };
            if c::ioctl(self.fd.fd(), TIOCGWINSZ, &mut size) == -1 {
                Err(IoError {
                    kind: io::OtherIoError,
                    desc: "Size of terminal could not be determined",
                    detail: None,
                })
            } else {
                Ok((size.ws_col as int, size.ws_row as int))
            }
        }
    }

    #[cfg(any(target_os = "ios",
              target_os = "dragonfly"))]
    pub fn get_winsize(&mut self) -> IoResult<(int, int)> {
        Err(sys_common::unimpl())
    }

    pub fn isatty(&self) -> bool { false }
}
<|endoftext|>


<commit_before><commit_msg>Mark some functions private.<commit_after><|endoftext|>


<commit_before><commit_msg>Add f32 constructors for ImVec2/ImVec4<commit_after><|endoftext|>


<commit_before><commit_msg>Test doc update<commit_after><|endoftext|>


<commit_before><commit_msg>better forbidden error message<commit_after><|endoftext|>


<commit_before><commit_msg>Debugging<commit_after><|endoftext|>


<commit_before><commit_msg>Change at_location and named to use the actual spec, rather than a mutable reference<commit_after><|endoftext|>


<commit_before><commit_msg>feat: add (de)serialization for time::Tm<commit_after><|endoftext|>


<commit_before><commit_msg>refactor<commit_after><|endoftext|>


<commit_before><commit_msg>Fixed merge fuckup<commit_after><|endoftext|>


<commit_before><commit_msg>Allow non-Unicode file names on Unix-like platforms<commit_after><|endoftext|>


<commit_before><commit_msg>Added errors<commit_after><|endoftext|>


<commit_before><commit_msg>rustfmt<commit_after><|endoftext|>


<commit_before><commit_msg>Added note about unsafe to doc about AsyncRead::prepare_uninitialized_buffer()<commit_after><|endoftext|>


<commit_before><commit_msg>Implement calc_record_size_in_leaf<commit_after><|endoftext|>


<commit_before><commit_msg>Implement From instead of Into for EpisodeId (since From gives Into)<commit_after><|endoftext|>


<commit_before><commit_msg>Added some debug printouts for OSX testing<commit_after><|endoftext|>


<commit_before><commit_msg>Implement bcmp (#315)<commit_after><|endoftext|>


<commit_before>use std::sync::Arc;

use ra_db::SourceDatabaseExt;

use super::*;

fn check_def_map_is_not_recomputed(ra_fixture_initial: &str, ra_fixture_change: &str) {
    let (mut db, pos) = TestDB::with_position(ra_fixture_initial);
    let krate = db.test_crate();
    {
        let events = db.log_executed(|| {
            db.crate_def_map(krate);
        });
        assert!(format!("{:?}", events).contains("crate_def_map"), "{:#?}", events)
    }
    db.set_file_text(pos.file_id, Arc::new(ra_fixture_change.to_string()));

    {
        let events = db.log_executed(|| {
            db.crate_def_map(krate);
        });
        assert!(!format!("{:?}", events).contains("crate_def_map"), "{:#?}", events)
    }
}

#[test]
fn typing_inside_a_function_should_not_invalidate_def_map() {
    check_def_map_is_not_recomputed(
        r"
        //- /lib.rs
        mod foo;<|>

        use crate::foo::bar::Baz;

        enum E { A, B }
        use E::*;

        fn foo() -> i32 {
            1 + 1
        }
        //- /foo/mod.rs
        pub mod bar;

        //- /foo/bar.rs
        pub struct Baz;
        ",
        r"
        mod foo;

        use crate::foo::bar::Baz;

        enum E { A, B }
        use E::*;

        fn foo() -> i32 { 92 }
        ",
    );
}

#[test]
fn adding_inner_items_should_not_invalidate_def_map() {
    check_def_map_is_not_recomputed(
        r"
        //- /lib.rs
        struct S { a: i32}
        enum E { A }
        trait T {
            fn a() {}
        }
        mod foo;<|>
        impl S {
            fn a() {}
        }
        use crate::foo::bar::Baz;
        //- /foo/mod.rs
        pub mod bar;

        //- /foo/bar.rs
        pub struct Baz;
        ",
        r"
        struct S { a: i32, b: () }
        enum E { A, B }
        trait T {
            fn a() {}
            fn b() {}
        }
        mod foo;<|>
        impl S {
            fn a() {}
            fn b() {}
        }
        use crate::foo::bar::Baz;
        ",
    );
}

#[test]
fn typing_inside_a_macro_should_not_invalidate_def_map() {
    let (mut db, pos) = TestDB::with_position(
        r"
        //- /lib.rs
        macro_rules! m {
            ($ident:ident) => {
                fn f() {
                    $ident + $ident;
                };
            }
        }
        mod foo;

        //- /foo/mod.rs
        pub mod bar;

        //- /foo/bar.rs
        <|>
        m!(X);
        ",
    );
    let krate = db.test_crate();
    {
        let events = db.log_executed(|| {
            let crate_def_map = db.crate_def_map(krate);
            let (_, module_data) = crate_def_map.modules.iter().last().unwrap();
            assert_eq!(module_data.scope.resolutions().count(), 1);
        });
        assert!(format!("{:?}", events).contains("crate_def_map"), "{:#?}", events)
    }
    db.set_file_text(pos.file_id, Arc::new("m!(Y);".to_string()));

    {
        let events = db.log_executed(|| {
            let crate_def_map = db.crate_def_map(krate);
            let (_, module_data) = crate_def_map.modules.iter().last().unwrap();
            assert_eq!(module_data.scope.resolutions().count(), 1);
        });
        assert!(!format!("{:?}", events).contains("crate_def_map"), "{:#?}", events)
    }
}
<commit_msg>Remove now-broken incremental test<commit_after>use std::sync::Arc;

use ra_db::SourceDatabaseExt;

use super::*;

fn check_def_map_is_not_recomputed(ra_fixture_initial: &str, ra_fixture_change: &str) {
    let (mut db, pos) = TestDB::with_position(ra_fixture_initial);
    let krate = db.test_crate();
    {
        let events = db.log_executed(|| {
            db.crate_def_map(krate);
        });
        assert!(format!("{:?}", events).contains("crate_def_map"), "{:#?}", events)
    }
    db.set_file_text(pos.file_id, Arc::new(ra_fixture_change.to_string()));

    {
        let events = db.log_executed(|| {
            db.crate_def_map(krate);
        });
        assert!(!format!("{:?}", events).contains("crate_def_map"), "{:#?}", events)
    }
}

#[test]
fn typing_inside_a_function_should_not_invalidate_def_map() {
    check_def_map_is_not_recomputed(
        r"
        //- /lib.rs
        mod foo;<|>

        use crate::foo::bar::Baz;

        enum E { A, B }
        use E::*;

        fn foo() -> i32 {
            1 + 1
        }
        //- /foo/mod.rs
        pub mod bar;

        //- /foo/bar.rs
        pub struct Baz;
        ",
        r"
        mod foo;

        use crate::foo::bar::Baz;

        enum E { A, B }
        use E::*;

        fn foo() -> i32 { 92 }
        ",
    );
}

#[test]
fn typing_inside_a_macro_should_not_invalidate_def_map() {
    let (mut db, pos) = TestDB::with_position(
        r"
        //- /lib.rs
        macro_rules! m {
            ($ident:ident) => {
                fn f() {
                    $ident + $ident;
                };
            }
        }
        mod foo;

        //- /foo/mod.rs
        pub mod bar;

        //- /foo/bar.rs
        <|>
        m!(X);
        ",
    );
    let krate = db.test_crate();
    {
        let events = db.log_executed(|| {
            let crate_def_map = db.crate_def_map(krate);
            let (_, module_data) = crate_def_map.modules.iter().last().unwrap();
            assert_eq!(module_data.scope.resolutions().count(), 1);
        });
        assert!(format!("{:?}", events).contains("crate_def_map"), "{:#?}", events)
    }
    db.set_file_text(pos.file_id, Arc::new("m!(Y);".to_string()));

    {
        let events = db.log_executed(|| {
            let crate_def_map = db.crate_def_map(krate);
            let (_, module_data) = crate_def_map.modules.iter().last().unwrap();
            assert_eq!(module_data.scope.resolutions().count(), 1);
        });
        assert!(!format!("{:?}", events).contains("crate_def_map"), "{:#?}", events)
    }
}
<|endoftext|>