<commit_before>/*
 * Copyright (c) Meta Platforms, Inc. and affiliates.
 *
 * This source code is licensed under the MIT license found in the
 * LICENSE file in the root directory of this source tree.
 */

use std::sync::{Arc, RwLock};

use common::{PerfLogEvent, PerfLogger};
use dashmap::mapref::entry::Entry;
use fnv::FnvHashMap;
use graphql_watchman::WatchmanFileSourceSubscriptionNextChange;
use intern::string_key::{StringKey, StringKeySet};
use log::debug;
use rayon::iter::ParallelIterator;
use relay_compiler::{
    build_project::{get_project_asts, ProjectAstData, ProjectAsts},
    build_raw_program, build_schema,
    compiler_state::{CompilerState, ProjectName},
    config::ProjectConfig,
    errors::BuildProjectError,
    errors::Error,
    transform_program, validate_program, BuildProjectFailure, FileSource, FileSourceResult,
    FileSourceSubscription, FileSourceSubscriptionNextChange, GraphQLAsts,
    SourceControlUpdateStatus,
};
use schema::SDLSchema;
use schema_documentation::SchemaDocumentation;
use tokio::{task, task::JoinHandle};

use crate::{
    status_updater::set_ready_status, status_updater::update_in_progress_status, LSPState,
};

use super::lsp_state::{ProjectStatus, Task};

/// This structure is responsible for keeping schemas/programs in sync with the current state of the world
pub(crate) struct LSPStateResources<
    TPerfLogger: PerfLogger + 'static,
    TSchemaDocumentation: SchemaDocumentation + 'static,
> {
    lsp_state: Arc<LSPState<TPerfLogger, TSchemaDocumentation>>,
}

impl<TPerfLogger: PerfLogger + 'static, TSchemaDocumentation: SchemaDocumentation + 'static>
    LSPStateResources<TPerfLogger, TSchemaDocumentation>
{
    pub(crate) fn new(lsp_state: Arc<LSPState<TPerfLogger, TSchemaDocumentation>>) -> Self {
        Self { lsp_state }
    }

    pub(crate) fn watch(self) {
        tokio::spawn(async move {
            self.internal_watch().await;
        });
    }

    /// Create an end-less loop of keeping the resources up-to-date with the source control changes
    async fn internal_watch(&self) {
        'outer: loop {
            debug!("Initializing resources for LSP server");

            update_in_progress_status(
                "Relay: watchman...",
                Some("Sending watchman query to get source files and possible saved state"),
                &self.lsp_state.sender,
            );
            let setup_event = self
                .lsp_state
                .perf_logger
                .create_event("lsp_state_initialize_resources");
            let timer = setup_event.start("lsp_state_initialize_resources_time");

            let file_source = match FileSource::connect(&self.lsp_state.config, &setup_event).await
            {
                Ok(f) => f,
                Err(error) => {
                    self.log_errors("watch_build_error", &error);
                    continue;
                }
            };
            let (mut compiler_state, file_source_subscription) = match file_source
                .subscribe(&setup_event, self.lsp_state.perf_logger.as_ref())
                .await
            {
                Ok(f) => f,
                Err(error) => {
                    self.log_errors("watch_build_error", &error);
                    continue;
                }
            };

            let pending_file_source_changes =
                Arc::clone(&compiler_state.pending_file_source_changes);

            // Start a separate task to receive watchman subscription updates
            // this process will notify, when the change is detected.
            // If watchman detects the source control update, it will update the state
            // of the source_control_update_status, so we can cancel building schemas/
            // and programs earlier
            let subscription_handle = self.watchman_subscription_handler(
                file_source_subscription,
                pending_file_source_changes,
                Arc::clone(&compiler_state.source_control_update_status),
            );
            update_in_progress_status(
                "Relay: creating state...",
                Some("Building schemas and source programs for LSP"),
                &self.lsp_state.sender,
            );

            self.lsp_state
                .diagnostic_reporter
                .clear_regular_diagnostics();

            // Run initial build, before entering the watch changes loop
            if let Err(error) = self.build_projects(&mut compiler_state, &setup_event) {
                self.publish_errors(&error, "lsp_state_error");
            }
            set_ready_status(&self.lsp_state.sender);

            setup_event.stop(timer);
            setup_event.complete();

            debug!("LSP server initialization completed!");

            // Here we will wait for changes from watchman
            'inner: loop {
                self.lsp_state.notify_lsp_state_resources.notified().await;

                // Source control update started, we can ignore all pending changes, and wait for it to complete,
                // we may change the status bar to `Source Control Update...`
                if compiler_state.source_control_update_status.is_started() {
                    update_in_progress_status(
                        "Relay: hg update...",
                        Some("Waiting for source control update"),
                        &self.lsp_state.sender,
                    );
                    continue 'inner;
                }

                // SC Update completed, we need to abort current subscription, and re-initialize resource for LSP
                if compiler_state.source_control_update_status.is_completed() {
                    debug!("Watchman indicated the the source control update has completed!");
                    subscription_handle.abort();
                    continue 'outer;
                }

                let log_event = self
                    .lsp_state
                    .perf_logger
                    .create_event("lsp_state_watchman_event");
                let log_time = log_event.start("lsp_state_watchman_event_time");

                if let Err(error) = self.incremental_build(&mut compiler_state, &log_event) {
                    self.publish_errors(&error, "lsp_state_user_error");
                }
                set_ready_status(&self.lsp_state.sender);

                log_event.stop(log_time);
                log_event.complete();
            }
        }
    }

    fn incremental_build(
        &self,
        compiler_state: &mut CompilerState,
        log_event: &impl PerfLogEvent,
    ) -> Result<(), Error> {
        let has_new_changes = compiler_state.merge_file_source_changes(
            &self.lsp_state.config,
            self.lsp_state.perf_logger.as_ref(),
            false,
        )?;

        // Rebuild if there are pending files or if a new project is activated
        if has_new_changes
            || self
                .lsp_state
                .project_status
                .iter()
                .any(|r| r.value() == &ProjectStatus::Activated)
        {
            debug!("LSP server detected changes...");

            self.lsp_state
                .diagnostic_reporter
                .clear_regular_diagnostics();

            update_in_progress_status(
                "Relay: checking...",
                Some("Validating changes, and updating source programs with the latest changes."),
                &self.lsp_state.sender,
            );

            self.build_projects(compiler_state, log_event)?;
        }

        Ok(())
    }

    fn build_projects(
        &self,
        compiler_state: &mut CompilerState,
        log_event: &impl PerfLogEvent,
    ) -> Result<(), Error> {
        let graphql_asts = log_event.time("parse_sources_time", || {
            GraphQLAsts::from_graphql_sources_map(
                &compiler_state.graphql_sources,
                &compiler_state.get_dirty_definitions(&self.lsp_state.config),
            )
        })?;

        if compiler_state.should_cancel_current_build() {
            debug!("Build is cancelled: new file changes are pending.");
            return Err(Error::Cancelled);
        }

        let timer = log_event.start("build_lsp_projects");
        let build_results: Vec<_> = self
            .lsp_state
            .config
            .par_enabled_projects()
            .filter(|project_config| {
                // Filter inactive projects
                if !self
                    .lsp_state
                    .project_status
                    .contains_key(&project_config.name)
                {
                    return false;
                }
                // When the source programs is empty, we need to compile all source programs once
                if !self
                    .lsp_state
                    .source_programs
                    .contains_key(&project_config.name)
                {
                    return true;
                }
                if let Some(base) = project_config.base {
                    if compiler_state.project_has_pending_changes(base) {
                        return true;
                    }
                }
                compiler_state.project_has_pending_changes(project_config.name)
            })
            .map(|project_config| self.build_project(project_config, compiler_state, &graphql_asts))
            .collect();
        log_event.stop(timer);

        let mut errors = vec![];
        for build_result in build_results {
            match build_result {
                Err(BuildProjectFailure::Error(err)) => {
                    errors.push(err);
                }
                Ok(project_name) => {
                    compiler_state.complete_project_compilation(&project_name);
                }
                _ => {}
            }
        }
        if errors.is_empty() {
            Ok(())
        } else {
            Err(Error::BuildProjectsErrors { errors })
        }
    }

    fn build_project(
        &self,
        project_config: &ProjectConfig,
        compiler_state: &CompilerState,
        graphql_asts: &FnvHashMap<ProjectName, GraphQLAsts>,
    ) -> Result<StringKey, BuildProjectFailure> {
        self.lsp_state
            .project_status
            .insert(project_config.name, ProjectStatus::Completed);
        let log_event = self.lsp_state.perf_logger.create_event("build_lsp_project");
        let project_name = project_config.name;
        let build_time = log_event.start("build_lsp_project_time");
        log_event.string("project", project_name.to_string());

        let ProjectAstData {
            project_asts,
            base_fragment_names,
        } = get_project_asts(&graphql_asts, project_config)?;

        let schema = log_event.time("build_schema_time", || {
            self.build_schema(compiler_state, project_config)
        })?;

        // This will kick-off the validation for all synced sources
        self.lsp_state.schedule_task(Task::ValidateSyncedSources);

        self.build_programs(
            project_config,
            project_asts,
            base_fragment_names,
            compiler_state,
            graphql_asts,
            schema,
            &log_event,
        )?;

        log_event.stop(build_time);
        Ok(project_name)
    }

    fn build_schema(
        &self,
        compiler_state: &CompilerState,
        project_config: &ProjectConfig,
    ) -> Result<Arc<SDLSchema>, BuildProjectFailure> {
        match self.lsp_state.schemas.entry(project_config.name) {
            Entry::Vacant(e) => {
                let schema = build_schema(compiler_state, project_config).map_err(|errors| {
                    BuildProjectFailure::Error(BuildProjectError::ValidationErrors {
                        errors,
                        project_name: project_config.name,
                    })
                })?;
                e.insert(Arc::clone(&schema));
                Ok(schema)
            }
            Entry::Occupied(mut e) => {
                if !compiler_state.project_has_pending_schema_changes(project_config.name) {
                    Ok(Arc::clone(e.get()))
                } else {
                    let schema =
                        build_schema(compiler_state, project_config).map_err(|errors| {
                            debug!("build error");
                            BuildProjectFailure::Error(BuildProjectError::ValidationErrors {
                                errors,
                                project_name: project_config.name,
                            })
                        })?;
                    e.insert(Arc::clone(&schema));
                    Ok(schema)
                }
            }
        }
    }

    fn build_programs(
        &self,
        project_config: &ProjectConfig,
        project_asts: ProjectAsts,
        base_fragment_names: StringKeySet,
        compiler_state: &CompilerState,
        graphql_asts: &FnvHashMap<ProjectName, GraphQLAsts>,
        schema: Arc<SDLSchema>,
        log_event: &impl PerfLogEvent,
    ) -> Result<(), BuildProjectFailure> {
        let is_incremental_build = self
            .lsp_state
            .source_programs
            .contains_key(&project_config.name)
            && compiler_state.has_processed_changes()
            && !compiler_state
                .has_breaking_schema_change(project_config.name, &project_config.schema_config)
            && if let Some(base) = project_config.base {
                !compiler_state.has_breaking_schema_change(base, &project_config.schema_config)
            } else {
                true
            };

        let (base_program, _) = build_raw_program(
            project_config,
            &compiler_state.implicit_dependencies.read().unwrap(),
            project_asts,
            schema,
            log_event,
            is_incremental_build,
        )?;

        if compiler_state.should_cancel_current_build() {
            debug!("Build is cancelled: updates in source code/or new file changes are pending.");
            return Err(BuildProjectFailure::Cancelled);
        }

        match self.lsp_state.source_programs.entry(project_config.name) {
            Entry::Vacant(e) => {
                e.insert(base_program.clone());
            }
            Entry::Occupied(mut e) => {
                let program = e.get_mut();
                let removed_definition_names = graphql_asts
                    .get(&project_config.name)
                    .map(|ast| ast.removed_definition_names.as_ref());
                program.merge_program(&base_program, removed_definition_names);
            }
        }

        validate_program(
            &self.lsp_state.config,
            project_config,
            &base_program,
            log_event,
        )?;

        transform_program(
            project_config,
            Arc::new(base_program),
            Arc::new(base_fragment_names),
            Arc::clone(&self.lsp_state.perf_logger),
            log_event,
            self.lsp_state.config.custom_transforms.as_ref(),
        )?;
        Ok(())
    }

    fn watchman_subscription_handler(
        &self,
        mut file_source_subscription: FileSourceSubscription,
        pending_file_source_changes: Arc<RwLock<Vec<FileSourceResult>>>,
        source_code_update_status: Arc<SourceControlUpdateStatus>,
    ) -> JoinHandle<()> {
        let notify_sender = self.lsp_state.notify_lsp_state_resources.clone();
        task::spawn(async move {
            loop {
                match file_source_subscription.next_change().await {
                    Err(_) => {
                        // do nothing? compiler should panic, and restart the lsp
                    }
                    Ok(FileSourceSubscriptionNextChange::Watchman(watchman_next_change)) => {
                        match watchman_next_change {
                            WatchmanFileSourceSubscriptionNextChange::None => {}
                            WatchmanFileSourceSubscriptionNextChange::SourceControlUpdateEnter => {
                                source_code_update_status.mark_as_started();
                            }
                            WatchmanFileSourceSubscriptionNextChange::SourceControlUpdateLeave => {
                                source_code_update_status.set_to_default();
                            }
                            WatchmanFileSourceSubscriptionNextChange::SourceControlUpdate => {
                                source_code_update_status.mark_as_completed();
                                notify_sender.notify_one();
                                break;
                            }
                            WatchmanFileSourceSubscriptionNextChange::Result(
                                file_source_changes,
                            ) => {
                                pending_file_source_changes
                                .write()
                                .expect("LSPState::watch_and_update_schemas: expect to acquire write lock on pending_file_source_changes")
                                .push(FileSourceResult::Watchman(file_source_changes));

                                notify_sender.notify_one();
                            }
                        }
                    }
                }
            }
        })
    }

    fn log_errors(&self, log_event_name: &'static str, error: &Error) {
        let error_event = self.lsp_state.perf_logger.create_event(log_event_name);
        error_event.string("error", error.to_string());
        error_event.complete();
    }

    /// Log errors and report the diagnostics to IDE
    fn publish_errors(&self, error: &Error, log_event_name: &'static str) {
        self.lsp_state.diagnostic_reporter.report_error(error);
        self.lsp_state.diagnostic_reporter.commit_diagnostics();
        self.log_errors(log_event_name, error)
    }
}
<commit_msg>Avoid infinite loop when watchman query always returns error<commit_after>/*
 * Copyright (c) Meta Platforms, Inc. and affiliates.
 *
 * This source code is licensed under the MIT license found in the
 * LICENSE file in the root directory of this source tree.
 */

use std::sync::{Arc, RwLock};

use common::{PerfLogEvent, PerfLogger};
use dashmap::mapref::entry::Entry;
use fnv::FnvHashMap;
use graphql_watchman::WatchmanFileSourceSubscriptionNextChange;
use intern::string_key::{StringKey, StringKeySet};
use log::debug;
use rayon::iter::ParallelIterator;
use relay_compiler::{
    build_project::{get_project_asts, ProjectAstData, ProjectAsts},
    build_raw_program, build_schema,
    compiler_state::{CompilerState, ProjectName},
    config::ProjectConfig,
    errors::BuildProjectError,
    errors::Error,
    transform_program, validate_program, BuildProjectFailure, FileSource, FileSourceResult,
    FileSourceSubscription, FileSourceSubscriptionNextChange, GraphQLAsts,
    SourceControlUpdateStatus,
};
use schema::SDLSchema;
use schema_documentation::SchemaDocumentation;
use tokio::{task, task::JoinHandle};

use crate::{
    status_updater::set_ready_status, status_updater::update_in_progress_status, LSPState,
};

use super::lsp_state::{ProjectStatus, Task};

/// This structure is responsible for keeping schemas/programs in sync with the current state of the world
pub(crate) struct LSPStateResources<
    TPerfLogger: PerfLogger + 'static,
    TSchemaDocumentation: SchemaDocumentation + 'static,
> {
    lsp_state: Arc<LSPState<TPerfLogger, TSchemaDocumentation>>,
}

const MAX_ERROR_COUNT: usize = 3;

impl<TPerfLogger: PerfLogger + 'static, TSchemaDocumentation: SchemaDocumentation + 'static>
    LSPStateResources<TPerfLogger, TSchemaDocumentation>
{
    pub(crate) fn new(lsp_state: Arc<LSPState<TPerfLogger, TSchemaDocumentation>>) -> Self {
        Self { lsp_state }
    }

    pub(crate) fn watch(self) {
        tokio::spawn(async move {
            self.internal_watch().await;
        });
    }

    /// Create an end-less loop of keeping the resources up-to-date with the source control changes
    async fn internal_watch(&self) {
        // avoid dead loop when watchman has an error
        let mut error_count = 0;
        'outer: loop {
            debug!("Initializing resources for LSP server");

            update_in_progress_status(
                "Relay: watchman...",
                Some("Sending watchman query to get source files and possible saved state"),
                &self.lsp_state.sender,
            );
            let setup_event = self
                .lsp_state
                .perf_logger
                .create_event("lsp_state_initialize_resources");
            let timer = setup_event.start("lsp_state_initialize_resources_time");

            let file_source = match FileSource::connect(&self.lsp_state.config, &setup_event).await
            {
                Ok(f) => f,
                Err(error) => {
                    self.log_errors("watch_build_error", &error);
                    error_count += 1;
                    if error_count == MAX_ERROR_COUNT {
                        panic!("{}", error);
                    }
                    continue;
                }
            };
            let (mut compiler_state, file_source_subscription) = match file_source
                .subscribe(&setup_event, self.lsp_state.perf_logger.as_ref())
                .await
            {
                Ok(f) => f,
                Err(error) => {
                    self.log_errors("watch_build_error", &error);
                    error_count += 1;
                    if error_count == MAX_ERROR_COUNT {
                        panic!("{}", error);
                    }
                    continue;
                }
            };

            let pending_file_source_changes =
                Arc::clone(&compiler_state.pending_file_source_changes);

            // Start a separate task to receive watchman subscription updates
            // this process will notify, when the change is detected.
            // If watchman detects the source control update, it will update the state
            // of the source_control_update_status, so we can cancel building schemas/
            // and programs earlier
            let subscription_handle = self.watchman_subscription_handler(
                file_source_subscription,
                pending_file_source_changes,
                Arc::clone(&compiler_state.source_control_update_status),
            );
            update_in_progress_status(
                "Relay: creating state...",
                Some("Building schemas and source programs for LSP"),
                &self.lsp_state.sender,
            );

            self.lsp_state
                .diagnostic_reporter
                .clear_regular_diagnostics();

            // Run initial build, before entering the watch changes loop
            if let Err(error) = self.build_projects(&mut compiler_state, &setup_event) {
                self.publish_errors(&error, "lsp_state_error");
            }
            set_ready_status(&self.lsp_state.sender);

            setup_event.stop(timer);
            setup_event.complete();

            debug!("LSP server initialization completed!");
            error_count = 0;

            // Here we will wait for changes from watchman
            'inner: loop {
                self.lsp_state.notify_lsp_state_resources.notified().await;

                // Source control update started, we can ignore all pending changes, and wait for it to complete,
                // we may change the status bar to `Source Control Update...`
                if compiler_state.source_control_update_status.is_started() {
                    update_in_progress_status(
                        "Relay: hg update...",
                        Some("Waiting for source control update"),
                        &self.lsp_state.sender,
                    );
                    continue 'inner;
                }

                // SC Update completed, we need to abort current subscription, and re-initialize resource for LSP
                if compiler_state.source_control_update_status.is_completed() {
                    debug!("Watchman indicated the the source control update has completed!");
                    subscription_handle.abort();
                    continue 'outer;
                }

                let log_event = self
                    .lsp_state
                    .perf_logger
                    .create_event("lsp_state_watchman_event");
                let log_time = log_event.start("lsp_state_watchman_event_time");

                if let Err(error) = self.incremental_build(&mut compiler_state, &log_event) {
                    self.publish_errors(&error, "lsp_state_user_error");
                }
                set_ready_status(&self.lsp_state.sender);

                log_event.stop(log_time);
                log_event.complete();
            }
        }
    }

    fn incremental_build(
        &self,
        compiler_state: &mut CompilerState,
        log_event: &impl PerfLogEvent,
    ) -> Result<(), Error> {
        let has_new_changes = compiler_state.merge_file_source_changes(
            &self.lsp_state.config,
            self.lsp_state.perf_logger.as_ref(),
            false,
        )?;

        // Rebuild if there are pending files or if a new project is activated
        if has_new_changes
            || self
                .lsp_state
                .project_status
                .iter()
                .any(|r| r.value() == &ProjectStatus::Activated)
        {
            debug!("LSP server detected changes...");

            self.lsp_state
                .diagnostic_reporter
                .clear_regular_diagnostics();

            update_in_progress_status(
                "Relay: checking...",
                Some("Validating changes, and updating source programs with the latest changes."),
                &self.lsp_state.sender,
            );

            self.build_projects(compiler_state, log_event)?;
        }

        Ok(())
    }

    fn build_projects(
        &self,
        compiler_state: &mut CompilerState,
        log_event: &impl PerfLogEvent,
    ) -> Result<(), Error> {
        let graphql_asts = log_event.time("parse_sources_time", || {
            GraphQLAsts::from_graphql_sources_map(
                &compiler_state.graphql_sources,
                &compiler_state.get_dirty_definitions(&self.lsp_state.config),
            )
        })?;

        if compiler_state.should_cancel_current_build() {
            debug!("Build is cancelled: new file changes are pending.");
            return Err(Error::Cancelled);
        }

        let timer = log_event.start("build_lsp_projects");
        let build_results: Vec<_> = self
            .lsp_state
            .config
            .par_enabled_projects()
            .filter(|project_config| {
                // Filter inactive projects
                if !self
                    .lsp_state
                    .project_status
                    .contains_key(&project_config.name)
                {
                    return false;
                }
                // When the source programs is empty, we need to compile all source programs once
                if !self
                    .lsp_state
                    .source_programs
                    .contains_key(&project_config.name)
                {
                    return true;
                }
                if let Some(base) = project_config.base {
                    if compiler_state.project_has_pending_changes(base) {
                        return true;
                    }
                }
                compiler_state.project_has_pending_changes(project_config.name)
            })
            .map(|project_config| self.build_project(project_config, compiler_state, &graphql_asts))
            .collect();
        log_event.stop(timer);

        let mut errors = vec![];
        for build_result in build_results {
            match build_result {
                Err(BuildProjectFailure::Error(err)) => {
                    errors.push(err);
                }
                Ok(project_name) => {
                    compiler_state.complete_project_compilation(&project_name);
                }
                _ => {}
            }
        }
        if errors.is_empty() {
            Ok(())
        } else {
            Err(Error::BuildProjectsErrors { errors })
        }
    }

    fn build_project(
        &self,
        project_config: &ProjectConfig,
        compiler_state: &CompilerState,
        graphql_asts: &FnvHashMap<ProjectName, GraphQLAsts>,
    ) -> Result<StringKey, BuildProjectFailure> {
        self.lsp_state
            .project_status
            .insert(project_config.name, ProjectStatus::Completed);
        let log_event = self.lsp_state.perf_logger.create_event("build_lsp_project");
        let project_name = project_config.name;
        let build_time = log_event.start("build_lsp_project_time");
        log_event.string("project", project_name.to_string());

        let ProjectAstData {
            project_asts,
            base_fragment_names,
        } = get_project_asts(&graphql_asts, project_config)?;

        let schema = log_event.time("build_schema_time", || {
            self.build_schema(compiler_state, project_config)
        })?;

        // This will kick-off the validation for all synced sources
        self.lsp_state.schedule_task(Task::ValidateSyncedSources);

        self.build_programs(
            project_config,
            project_asts,
            base_fragment_names,
            compiler_state,
            graphql_asts,
            schema,
            &log_event,
        )?;

        log_event.stop(build_time);
        Ok(project_name)
    }

    fn build_schema(
        &self,
        compiler_state: &CompilerState,
        project_config: &ProjectConfig,
    ) -> Result<Arc<SDLSchema>, BuildProjectFailure> {
        match self.lsp_state.schemas.entry(project_config.name) {
            Entry::Vacant(e) => {
                let schema = build_schema(compiler_state, project_config).map_err(|errors| {
                    BuildProjectFailure::Error(BuildProjectError::ValidationErrors {
                        errors,
                        project_name: project_config.name,
                    })
                })?;
                e.insert(Arc::clone(&schema));
                Ok(schema)
            }
            Entry::Occupied(mut e) => {
                if !compiler_state.project_has_pending_schema_changes(project_config.name) {
                    Ok(Arc::clone(e.get()))
                } else {
                    let schema =
                        build_schema(compiler_state, project_config).map_err(|errors| {
                            debug!("build error");
                            BuildProjectFailure::Error(BuildProjectError::ValidationErrors {
                                errors,
                                project_name: project_config.name,
                            })
                        })?;
                    e.insert(Arc::clone(&schema));
                    Ok(schema)
                }
            }
        }
    }

    fn build_programs(
        &self,
        project_config: &ProjectConfig,
        project_asts: ProjectAsts,
        base_fragment_names: StringKeySet,
        compiler_state: &CompilerState,
        graphql_asts: &FnvHashMap<ProjectName, GraphQLAsts>,
        schema: Arc<SDLSchema>,
        log_event: &impl PerfLogEvent,
    ) -> Result<(), BuildProjectFailure> {
        let is_incremental_build = self
            .lsp_state
            .source_programs
            .contains_key(&project_config.name)
            && compiler_state.has_processed_changes()
            && !compiler_state
                .has_breaking_schema_change(project_config.name, &project_config.schema_config)
            && if let Some(base) = project_config.base {
                !compiler_state.has_breaking_schema_change(base, &project_config.schema_config)
            } else {
                true
            };

        let (base_program, _) = build_raw_program(
            project_config,
            &compiler_state.implicit_dependencies.read().unwrap(),
            project_asts,
            schema,
            log_event,
            is_incremental_build,
        )?;

        if compiler_state.should_cancel_current_build() {
            debug!("Build is cancelled: updates in source code/or new file changes are pending.");
            return Err(BuildProjectFailure::Cancelled);
        }

        match self.lsp_state.source_programs.entry(project_config.name) {
            Entry::Vacant(e) => {
                e.insert(base_program.clone());
            }
            Entry::Occupied(mut e) => {
                let program = e.get_mut();
                let removed_definition_names = graphql_asts
                    .get(&project_config.name)
                    .map(|ast| ast.removed_definition_names.as_ref());
                program.merge_program(&base_program, removed_definition_names);
            }
        }

        validate_program(
            &self.lsp_state.config,
            project_config,
            &base_program,
            log_event,
        )?;

        transform_program(
            project_config,
            Arc::new(base_program),
            Arc::new(base_fragment_names),
            Arc::clone(&self.lsp_state.perf_logger),
            log_event,
            self.lsp_state.config.custom_transforms.as_ref(),
        )?;
        Ok(())
    }

    fn watchman_subscription_handler(
        &self,
        mut file_source_subscription: FileSourceSubscription,
        pending_file_source_changes: Arc<RwLock<Vec<FileSourceResult>>>,
        source_code_update_status: Arc<SourceControlUpdateStatus>,
    ) -> JoinHandle<()> {
        let notify_sender = self.lsp_state.notify_lsp_state_resources.clone();
        task::spawn(async move {
            loop {
                match file_source_subscription.next_change().await {
                    Err(_) => {
                        // do nothing? compiler should panic, and restart the lsp
                    }
                    Ok(FileSourceSubscriptionNextChange::Watchman(watchman_next_change)) => {
                        match watchman_next_change {
                            WatchmanFileSourceSubscriptionNextChange::None => {}
                            WatchmanFileSourceSubscriptionNextChange::SourceControlUpdateEnter => {
                                source_code_update_status.mark_as_started();
                            }
                            WatchmanFileSourceSubscriptionNextChange::SourceControlUpdateLeave => {
                                source_code_update_status.set_to_default();
                            }
                            WatchmanFileSourceSubscriptionNextChange::SourceControlUpdate => {
                                source_code_update_status.mark_as_completed();
                                notify_sender.notify_one();
                                break;
                            }
                            WatchmanFileSourceSubscriptionNextChange::Result(
                                file_source_changes,
                            ) => {
                                pending_file_source_changes
                                .write()
                                .expect("LSPState::watch_and_update_schemas: expect to acquire write lock on pending_file_source_changes")
                                .push(FileSourceResult::Watchman(file_source_changes));

                                notify_sender.notify_one();
                            }
                        }
                    }
                }
            }
        })
    }

    fn log_errors(&self, log_event_name: &'static str, error: &Error) {
        let error_event = self.lsp_state.perf_logger.create_event(log_event_name);
        error_event.string("error", error.to_string());
        error_event.complete();
    }

    /// Log errors and report the diagnostics to IDE
    fn publish_errors(&self, error: &Error, log_event_name: &'static str) {
        self.lsp_state.diagnostic_reporter.report_error(error);
        self.lsp_state.diagnostic_reporter.commit_diagnostics();
        self.log_errors(log_event_name, error)
    }
}
<|endoftext|>


<commit_before>use crate::language::access::sym_accessible_from;
use crate::language::error::msg::SemError;
use crate::language::report_sym_shadow;
use crate::language::sem_analysis::{module_package, ModuleDefinitionId, SemAnalysis};
use crate::language::sym::{NestedSymTable, Sym};

use dora_parser::ast::{self, UsePathComponent, UsePathComponentValue, UseTargetDescriptor};
use dora_parser::interner::Name;
use dora_parser::lexer::position::Position;

use super::sem_analysis::SourceFileId;

pub fn check<'a>(sa: &SemAnalysis) {
    for use_elem in &sa.uses {
        let _ = check_use(
            sa,
            &use_elem.ast,
            use_elem.module_id,
            use_elem.file_id,
            None,
        );
    }
}

fn check_use(
    sa: &SemAnalysis,
    use_declaration: &ast::Use,
    use_module_id: ModuleDefinitionId,
    use_file_id: SourceFileId,
    previous_sym: Option<Sym>,
) -> Result<(), ()> {
    let (start_idx, mut previous_sym) = initial_module(
        sa,
        use_declaration,
        use_module_id,
        use_file_id,
        previous_sym,
    )?;

    for (idx, component) in use_declaration
        .common_path
        .iter()
        .enumerate()
        .skip(start_idx)
    {
        if !previous_sym.is_enum() && !previous_sym.is_module() {
            let msg = SemError::ExpectedPath;
            let pos = use_declaration.common_path[idx - 1].pos;
            sa.diag.lock().report(use_file_id, pos, msg);
            return Err(());
        }

        previous_sym = process_component(sa, use_module_id, use_file_id, previous_sym, component)?;
    }

    match &use_declaration.target {
        UseTargetDescriptor::Default => {
            let last_component = use_declaration.common_path.last().expect("no component");

            let name = match last_component.value {
                UsePathComponentValue::Name(name) => name,
                UsePathComponentValue::Package
                | UsePathComponentValue::Super
                | UsePathComponentValue::This => {
                    sa.diag
                        .lock()
                        .report(use_file_id, last_component.pos, SemError::ExpectedPath);
                    return Err(());
                }
            };

            define_use_target(
                sa,
                use_file_id,
                last_component.pos,
                use_module_id,
                name,
                previous_sym,
            )
        }
        UseTargetDescriptor::As(target) => {
            let last_component = use_declaration.common_path.last().expect("no component");

            let name = target.name.expect("target expected");

            define_use_target(
                sa,
                use_file_id,
                last_component.pos,
                use_module_id,
                name,
                previous_sym,
            )
        }
        UseTargetDescriptor::Group(ref group) => {
            if group.targets.is_empty() {
                sa.diag
                    .lock()
                    .report(use_file_id, group.pos, SemError::ExpectedPath);
                return Err(());
            }

            for nested_use in &group.targets {
                check_use(
                    sa,
                    nested_use,
                    use_module_id,
                    use_file_id,
                    Some(previous_sym.clone()),
                )?;
            }
        }
    }

    Ok(())
}

fn initial_module(
    sa: &SemAnalysis,
    use_declaration: &ast::Use,
    use_module_id: ModuleDefinitionId,
    use_file_id: SourceFileId,
    previous_sym: Option<Sym>,
) -> Result<(usize, Sym), ()> {
    if let Some(namespace) = previous_sym {
        return Ok((0, namespace));
    }

    if let Some(first_component) = use_declaration.common_path.first() {
        match first_component.value {
            UsePathComponentValue::This => Ok((1, Sym::Module(use_module_id))),
            UsePathComponentValue::Package => {
                Ok((1, Sym::Module(module_package(sa, use_module_id))))
            }
            UsePathComponentValue::Super => {
                let module = &sa.modules[use_module_id].read();
                if let Some(module_id) = module.parent_module_id {
                    Ok((1, Sym::Module(module_id)))
                } else {
                    sa.diag.lock().report(
                        use_file_id.into(),
                        first_component.pos,
                        SemError::NoSuperModule,
                    );
                    Err(())
                }
            }
            UsePathComponentValue::Name(_) => Ok((0, Sym::Module(use_module_id))),
        }
    } else {
        Err(())
    }
}

fn process_component(
    sa: &SemAnalysis,
    use_module_id: ModuleDefinitionId,
    use_file_id: SourceFileId,
    previous_sym: Sym,
    component: &UsePathComponent,
) -> Result<Sym, ()> {
    let component_name = match component.value {
        UsePathComponentValue::Name(name) => name,
        UsePathComponentValue::Package
        | UsePathComponentValue::Super
        | UsePathComponentValue::This => {
            sa.diag
                .lock()
                .report(use_file_id, component.pos, SemError::ExpectedPath);
            return Err(());
        }
    };

    match previous_sym {
        Sym::Module(module_id) => {
            let symtable = NestedSymTable::new(sa, module_id);
            let current_sym = symtable.get(component_name);

            if let Some(current_sym) = current_sym {
                if sym_accessible_from(sa, current_sym.clone(), use_module_id) {
                    Ok(current_sym)
                } else {
                    let module = &sa.modules[module_id].read();
                    let name = sa.interner.str(component_name).to_string();
                    let msg = SemError::NotAccessibleInModule(module.name(sa), name);
                    sa.diag.lock().report(use_file_id, component.pos, msg);
                    Err(())
                }
            } else {
                let module = sa.modules.idx(module_id);
                let module = module.read();
                let name = sa.interner.str(component_name).to_string();
                let module_name = module.name(sa);
                sa.diag.lock().report(
                    use_file_id,
                    component.pos,
                    SemError::UnknownIdentifierInModule(module_name, name),
                );
                Err(())
            }
        }

        Sym::Enum(enum_id) => {
            let enum_ = sa.enums[enum_id].read();

            if let Some(&variant_idx) = enum_.name_to_value.get(&component_name) {
                Ok(Sym::EnumVariant(enum_id, variant_idx as usize))
            } else {
                let name = sa.interner.str(component_name).to_string();
                sa.diag.lock().report(
                    use_file_id,
                    component.pos,
                    SemError::UnknownEnumVariant(name),
                );
                Err(())
            }
        }

        _ => unreachable!(),
    }
}

fn define_use_target(
    sa: &SemAnalysis,
    use_file_id: SourceFileId,
    use_pos: Position,
    module_id: ModuleDefinitionId,
    name: Name,
    sym: Sym,
) {
    let module = sa.modules.idx(module_id);
    let module = module.read();

    let table = module.table.clone();
    let mut table = table.write();

    if let Some(old_sym) = table.insert(name, sym) {
        report_sym_shadow(sa, name, use_file_id, use_pos, old_sym);
    }
}

#[cfg(test)]
mod tests {
    use crate::language::error::msg::SemError;
    use crate::language::tests::*;

    #[test]
    fn check_initializer() {
        ok("let a: Int32 = 0i32;");
        ok("let a: Int32 = 0i32; var b: Int32 = a + 1i32;");
        err(
            "var a: Int32 = foo;",
            pos(1, 16),
            SemError::UnknownIdentifier("foo".into()),
        );
    }

    #[test]
    fn check_type() {
        err(
            "var x: Foo = 0;",
            pos(1, 8),
            SemError::UnknownIdentifier("Foo".into()),
        );
    }

    #[test]
    fn use_module() {
        err(
            "
            use foo::bar::Foo;
            mod foo {
                mod bar {
                    class Foo
                }
            }
        ",
            pos(2, 22),
            SemError::NotAccessibleInModule("foo".into(), "bar".into()),
        );
    }

    #[test]
    fn use_class() {
        err(
            "
            use foo::bar::Foo;
            mod foo {
                @pub mod bar {
                    class Foo
                }
            }
        ",
            pos(2, 27),
            SemError::NotAccessibleInModule("foo::bar".into(), "Foo".into()),
        );
    }

    #[test]
    fn use_fct() {
        err(
            "
            use foo::bar;
            mod foo {
                fn bar() {}
            }
        ",
            pos(2, 22),
            SemError::NotAccessibleInModule("foo".into(), "bar".into()),
        );
    }

    #[test]
    fn use_global() {
        err(
            "
            use foo::bar;
            mod foo {
                var bar: Int32 = 12;
            }
        ",
            pos(2, 22),
            SemError::NotAccessibleInModule("foo".into(), "bar".into()),
        );
    }

    #[test]
    fn use_const() {
        err(
            "
            use foo::bar;
            mod foo {
                const bar: Int32 = 12;
            }
        ",
            pos(2, 22),
            SemError::NotAccessibleInModule("foo".into(), "bar".into()),
        );
    }

    #[test]
    fn use_enum() {
        err(
            "
            use foo::Bar;
            @pub mod foo {
                enum Bar { A, B, C }
            }
        ",
            pos(2, 22),
            SemError::NotAccessibleInModule("foo".into(), "Bar".into()),
        );
    }

    #[test]
    fn use_enum_value() {
        err(
            "
            use foo::Bar::A;
            mod foo {
                enum Bar { A, B, C }
            }
        ",
            pos(2, 22),
            SemError::NotAccessibleInModule("foo".into(), "Bar".into()),
        );

        ok("
            use foo::Bar::{A, B, C};
            mod foo {
                @pub enum Bar { A, B, C }
            }
        ");
    }

    #[test]
    fn use_trait() {
        err(
            "
            use foo::Bar;
            mod foo {
                trait Bar {}
            }
        ",
            pos(2, 22),
            SemError::NotAccessibleInModule("foo".into(), "Bar".into()),
        );
    }

    #[test]
    fn use_struct() {
        ok("
            use foo::Bar;
            mod foo {
                @pub struct Bar { f: Int32 }
            }
        ");

        err(
            "
            use foo::Bar;
            mod foo {
                struct Bar { f: Int32 }
            }
        ",
            pos(2, 22),
            SemError::NotAccessibleInModule("foo".into(), "Bar".into()),
        );
    }

    #[test]
    fn use_public() {
        ok("
            @pub use foo::Bar;
            @pub mod foo {
                @pub enum Bar { A, B, C }
            }
        ");
    }

    #[test]
    fn use_keyword_only() {
        err("use self;", pos(1, 5), SemError::ExpectedPath);
        err("use package;", pos(1, 5), SemError::ExpectedPath);
        err("mod foo { use super; }", pos(1, 15), SemError::ExpectedPath);
    }

    #[test]
    fn use_keyword_in_path() {
        err(
            "use foo::bar::self; mod foo { @pub mod bar {} }",
            pos(1, 15),
            SemError::ExpectedPath,
        );
        err(
            "use foo::bar::super; mod foo { @pub mod bar {} }",
            pos(1, 15),
            SemError::ExpectedPath,
        );
        err(
            "use foo::bar::package; mod foo { @pub mod bar {} }",
            pos(1, 15),
            SemError::ExpectedPath,
        );
    }

    #[test]
    fn no_use_targets() {
        err(
            "use foo::bar:: {}; mod foo { @pub mod bar {} }",
            pos(1, 16),
            SemError::ExpectedPath,
        );
    }
}
<commit_msg>useck: prepare for allowing errors<commit_after>use crate::language::access::sym_accessible_from;
use crate::language::error::msg::SemError;
use crate::language::report_sym_shadow;
use crate::language::sem_analysis::{module_package, ModuleDefinitionId, SemAnalysis};
use crate::language::sym::{NestedSymTable, Sym};

use dora_parser::ast::{self, UsePathComponent, UsePathComponentValue, UseTargetDescriptor};
use dora_parser::interner::Name;
use dora_parser::lexer::position::Position;

use super::sem_analysis::SourceFileId;

pub fn check<'a>(sa: &SemAnalysis) {
    for use_elem in &sa.uses {
        let _ = check_use(
            sa,
            &use_elem.ast,
            use_elem.module_id,
            use_elem.file_id,
            None,
            false,
        );
    }
}

enum UseError {
    Unresolved,
    Fatal,
}

fn check_use(
    sa: &SemAnalysis,
    use_declaration: &ast::Use,
    use_module_id: ModuleDefinitionId,
    use_file_id: SourceFileId,
    previous_sym: Option<Sym>,
    ignore_errors: bool,
) -> Result<(), UseError> {
    let (start_idx, mut previous_sym) = initial_module(
        sa,
        use_declaration,
        use_module_id,
        use_file_id,
        previous_sym,
    )?;

    for (idx, component) in use_declaration
        .common_path
        .iter()
        .enumerate()
        .skip(start_idx)
    {
        if !previous_sym.is_enum() && !previous_sym.is_module() {
            let msg = SemError::ExpectedPath;
            let pos = use_declaration.common_path[idx - 1].pos;
            sa.diag.lock().report(use_file_id, pos, msg);
            return Err(UseError::Fatal);
        }

        previous_sym = process_component(
            sa,
            use_module_id,
            use_file_id,
            previous_sym,
            component,
            ignore_errors,
        )?;
    }

    match &use_declaration.target {
        UseTargetDescriptor::Default => {
            let last_component = use_declaration.common_path.last().expect("no component");

            let name = match last_component.value {
                UsePathComponentValue::Name(name) => name,
                UsePathComponentValue::Package
                | UsePathComponentValue::Super
                | UsePathComponentValue::This => {
                    sa.diag
                        .lock()
                        .report(use_file_id, last_component.pos, SemError::ExpectedPath);
                    return Err(UseError::Fatal);
                }
            };

            define_use_target(
                sa,
                use_file_id,
                last_component.pos,
                use_module_id,
                name,
                previous_sym,
            )?;
        }
        UseTargetDescriptor::As(target) => {
            let last_component = use_declaration.common_path.last().expect("no component");

            let name = target.name.expect("target expected");

            define_use_target(
                sa,
                use_file_id,
                last_component.pos,
                use_module_id,
                name,
                previous_sym,
            )?;
        }
        UseTargetDescriptor::Group(ref group) => {
            if group.targets.is_empty() {
                sa.diag
                    .lock()
                    .report(use_file_id, group.pos, SemError::ExpectedPath);
                return Err(UseError::Fatal);
            }

            for nested_use in &group.targets {
                check_use(
                    sa,
                    nested_use,
                    use_module_id,
                    use_file_id,
                    Some(previous_sym.clone()),
                    ignore_errors,
                )?;
            }
        }
    }

    Ok(())
}

fn initial_module(
    sa: &SemAnalysis,
    use_declaration: &ast::Use,
    use_module_id: ModuleDefinitionId,
    use_file_id: SourceFileId,
    previous_sym: Option<Sym>,
) -> Result<(usize, Sym), UseError> {
    if let Some(namespace) = previous_sym {
        return Ok((0, namespace));
    }

    if let Some(first_component) = use_declaration.common_path.first() {
        match first_component.value {
            UsePathComponentValue::This => Ok((1, Sym::Module(use_module_id))),
            UsePathComponentValue::Package => {
                Ok((1, Sym::Module(module_package(sa, use_module_id))))
            }
            UsePathComponentValue::Super => {
                let module = &sa.modules[use_module_id].read();
                if let Some(module_id) = module.parent_module_id {
                    Ok((1, Sym::Module(module_id)))
                } else {
                    sa.diag.lock().report(
                        use_file_id.into(),
                        first_component.pos,
                        SemError::NoSuperModule,
                    );
                    Err(UseError::Fatal)
                }
            }
            UsePathComponentValue::Name(_) => Ok((0, Sym::Module(use_module_id))),
        }
    } else {
        Err(UseError::Fatal)
    }
}

fn process_component(
    sa: &SemAnalysis,
    use_module_id: ModuleDefinitionId,
    use_file_id: SourceFileId,
    previous_sym: Sym,
    component: &UsePathComponent,
    ignore_errors: bool,
) -> Result<Sym, UseError> {
    let component_name = match component.value {
        UsePathComponentValue::Name(name) => name,
        UsePathComponentValue::Package
        | UsePathComponentValue::Super
        | UsePathComponentValue::This => {
            sa.diag
                .lock()
                .report(use_file_id, component.pos, SemError::ExpectedPath);
            return Err(UseError::Fatal);
        }
    };

    match previous_sym {
        Sym::Module(module_id) => {
            let symtable = NestedSymTable::new(sa, module_id);
            let current_sym = symtable.get(component_name);

            if let Some(current_sym) = current_sym {
                if sym_accessible_from(sa, current_sym.clone(), use_module_id) {
                    Ok(current_sym)
                } else {
                    let module = &sa.modules[module_id].read();
                    let name = sa.interner.str(component_name).to_string();
                    let msg = SemError::NotAccessibleInModule(module.name(sa), name);
                    sa.diag.lock().report(use_file_id, component.pos, msg);
                    Err(UseError::Fatal)
                }
            } else if ignore_errors {
                Err(UseError::Unresolved)
            } else {
                let module = sa.modules.idx(module_id);
                let module = module.read();
                let name = sa.interner.str(component_name).to_string();
                let module_name = module.name(sa);
                sa.diag.lock().report(
                    use_file_id,
                    component.pos,
                    SemError::UnknownIdentifierInModule(module_name, name),
                );
                Err(UseError::Unresolved)
            }
        }

        Sym::Enum(enum_id) => {
            let enum_ = sa.enums[enum_id].read();

            if let Some(&variant_idx) = enum_.name_to_value.get(&component_name) {
                Ok(Sym::EnumVariant(enum_id, variant_idx as usize))
            } else {
                let name = sa.interner.str(component_name).to_string();
                sa.diag.lock().report(
                    use_file_id,
                    component.pos,
                    SemError::UnknownEnumVariant(name),
                );
                Err(UseError::Fatal)
            }
        }

        _ => unreachable!(),
    }
}

fn define_use_target(
    sa: &SemAnalysis,
    use_file_id: SourceFileId,
    use_pos: Position,
    module_id: ModuleDefinitionId,
    name: Name,
    sym: Sym,
) -> Result<(), UseError> {
    let module = sa.modules.idx(module_id);
    let module = module.read();

    let table = module.table.clone();
    let mut table = table.write();

    if let Some(old_sym) = table.insert(name, sym) {
        report_sym_shadow(sa, name, use_file_id, use_pos, old_sym);
        Err(UseError::Fatal)
    } else {
        Ok(())
    }
}

#[cfg(test)]
mod tests {
    use crate::language::error::msg::SemError;
    use crate::language::tests::*;

    #[test]
    fn check_initializer() {
        ok("let a: Int32 = 0i32;");
        ok("let a: Int32 = 0i32; var b: Int32 = a + 1i32;");
        err(
            "var a: Int32 = foo;",
            pos(1, 16),
            SemError::UnknownIdentifier("foo".into()),
        );
    }

    #[test]
    fn check_type() {
        err(
            "var x: Foo = 0;",
            pos(1, 8),
            SemError::UnknownIdentifier("Foo".into()),
        );
    }

    #[test]
    fn use_module() {
        err(
            "
            use foo::bar::Foo;
            mod foo {
                mod bar {
                    class Foo
                }
            }
        ",
            pos(2, 22),
            SemError::NotAccessibleInModule("foo".into(), "bar".into()),
        );
    }

    #[test]
    fn use_class() {
        err(
            "
            use foo::bar::Foo;
            mod foo {
                @pub mod bar {
                    class Foo
                }
            }
        ",
            pos(2, 27),
            SemError::NotAccessibleInModule("foo::bar".into(), "Foo".into()),
        );
    }

    #[test]
    fn use_fct() {
        err(
            "
            use foo::bar;
            mod foo {
                fn bar() {}
            }
        ",
            pos(2, 22),
            SemError::NotAccessibleInModule("foo".into(), "bar".into()),
        );
    }

    #[test]
    fn use_global() {
        err(
            "
            use foo::bar;
            mod foo {
                var bar: Int32 = 12;
            }
        ",
            pos(2, 22),
            SemError::NotAccessibleInModule("foo".into(), "bar".into()),
        );
    }

    #[test]
    fn use_const() {
        err(
            "
            use foo::bar;
            mod foo {
                const bar: Int32 = 12;
            }
        ",
            pos(2, 22),
            SemError::NotAccessibleInModule("foo".into(), "bar".into()),
        );
    }

    #[test]
    fn use_enum() {
        err(
            "
            use foo::Bar;
            @pub mod foo {
                enum Bar { A, B, C }
            }
        ",
            pos(2, 22),
            SemError::NotAccessibleInModule("foo".into(), "Bar".into()),
        );
    }

    #[test]
    fn use_enum_value() {
        err(
            "
            use foo::Bar::A;
            mod foo {
                enum Bar { A, B, C }
            }
        ",
            pos(2, 22),
            SemError::NotAccessibleInModule("foo".into(), "Bar".into()),
        );

        ok("
            use foo::Bar::{A, B, C};
            mod foo {
                @pub enum Bar { A, B, C }
            }
        ");
    }

    #[test]
    fn use_trait() {
        err(
            "
            use foo::Bar;
            mod foo {
                trait Bar {}
            }
        ",
            pos(2, 22),
            SemError::NotAccessibleInModule("foo".into(), "Bar".into()),
        );
    }

    #[test]
    fn use_struct() {
        ok("
            use foo::Bar;
            mod foo {
                @pub struct Bar { f: Int32 }
            }
        ");

        err(
            "
            use foo::Bar;
            mod foo {
                struct Bar { f: Int32 }
            }
        ",
            pos(2, 22),
            SemError::NotAccessibleInModule("foo".into(), "Bar".into()),
        );
    }

    #[test]
    fn use_public() {
        ok("
            @pub use foo::Bar;
            @pub mod foo {
                @pub enum Bar { A, B, C }
            }
        ");
    }

    #[test]
    fn use_keyword_only() {
        err("use self;", pos(1, 5), SemError::ExpectedPath);
        err("use package;", pos(1, 5), SemError::ExpectedPath);
        err("mod foo { use super; }", pos(1, 15), SemError::ExpectedPath);
    }

    #[test]
    fn use_keyword_in_path() {
        err(
            "use foo::bar::self; mod foo { @pub mod bar {} }",
            pos(1, 15),
            SemError::ExpectedPath,
        );
        err(
            "use foo::bar::super; mod foo { @pub mod bar {} }",
            pos(1, 15),
            SemError::ExpectedPath,
        );
        err(
            "use foo::bar::package; mod foo { @pub mod bar {} }",
            pos(1, 15),
            SemError::ExpectedPath,
        );
    }

    #[test]
    fn no_use_targets() {
        err(
            "use foo::bar:: {}; mod foo { @pub mod bar {} }",
            pos(1, 16),
            SemError::ExpectedPath,
        );
    }
}
<|endoftext|>


<commit_before>/*
 * Copyright Amazon.com, Inc. or its affiliates. All Rights Reserved.
 * SPDX-License-Identifier: Apache-2.0.
 */

use aws_sdk_ec2::{Client, Config, Error, Region, PKG_VERSION};
use aws_types::region;
use aws_types::region::ProvideRegion;
use structopt::StructOpt;

#[derive(Debug, StructOpt)]
struct Opt {
    /// The AWS Region.
    #[structopt(short, long)]
    region: Option<String>,

    /// Whether to display additional runtime information.
    #[structopt(short, long)]
    verbose: bool,
}

/// Shows the scheduled events for the instances in the Region.
async fn show_events(reg: String) {
    let region = Region::new(reg.clone());
    let config = Config::builder().region(region).build();
    let client = Client::from_conf(config);

    let resp = client.describe_instance_status().send().await;

    println!("Instances in region {}:", reg);
    println!();

    for status in resp.unwrap().instance_statuses.unwrap_or_default() {
        println!(
            "  Events scheduled for instance ID: {}",
            status.instance_id.as_deref().unwrap_or_default()
        );
        for event in status.events.unwrap_or_default() {
            println!("    Event ID:     {}", event.instance_event_id.unwrap());
            println!("    Description:  {}", event.description.unwrap());
            /* Event codes:
               InstanceReboot
               InstanceRetirement
               InstanceStop
               SystemMaintenance
               SystemReboot
               Unknown
            */
            println!("    Event code:   {}", event.code.unwrap().as_ref());
            println!();
        }
    }
}

/// Lists the events of your EC2 instances in all available regions.
/// # Arguments
///
/// * `[-r REGION]` - The Region in which the client is created.
///    If not supplied, uses the value of the **AWS_REGION** environment variable.
///    If the environment variable is not set, defaults to **us-west-2**.
/// * `[-v]` - Whether to display additional information.
#[tokio::main]
async fn main() -> Result<(), Error> {
    tracing_subscriber::fmt::init();
    let Opt { region, verbose } = Opt::from_args();

    let region = region::ChainProvider::first_try(region.map(Region::new))
        .or_default_provider()
        .or_else(Region::new("us-west-2"));

    println!();

    if verbose {
        println!("EC2 client version: {}", PKG_VERSION);
        println!("Region:             {}", region.region().unwrap().as_ref());
        println!();
    }

    // Get list of available regions.
    let config = Config::builder().region(region).build();
    let ec2_client = Client::from_conf(config);
    let resp = ec2_client.describe_regions().send().await;

    // Show the events for that EC2 instances in that Region.
    for region in resp.unwrap().regions.unwrap_or_default() {
        show_events(region.region_name.unwrap()).await;
    }

    Ok(())
}
<commit_msg>Updated comment in EC2 list-all-instance-events code example based on feedback<commit_after>/*
 * Copyright Amazon.com, Inc. or its affiliates. All Rights Reserved.
 * SPDX-License-Identifier: Apache-2.0.
 */

use aws_sdk_ec2::{Client, Config, Error, Region, PKG_VERSION};
use aws_types::region;
use aws_types::region::ProvideRegion;
use structopt::StructOpt;

#[derive(Debug, StructOpt)]
struct Opt {
    /// The AWS Region.
    #[structopt(short, long)]
    region: Option<String>,

    /// Whether to display additional runtime information.
    #[structopt(short, long)]
    verbose: bool,
}

/// Shows the scheduled events for the Amazon Elastic Compute Cloud (Amazon EC2) instances in the Region.
async fn show_events(reg: String) {
    let region = Region::new(reg.clone());
    let config = Config::builder().region(region).build();
    let client = Client::from_conf(config);

    let resp = client.describe_instance_status().send().await;

    println!("Instances in region {}:", reg);
    println!();

    for status in resp.unwrap().instance_statuses.unwrap_or_default() {
        println!(
            "  Events scheduled for instance ID: {}",
            status.instance_id.as_deref().unwrap_or_default()
        );
        for event in status.events.unwrap_or_default() {
            println!("    Event ID:     {}", event.instance_event_id.unwrap());
            println!("    Description:  {}", event.description.unwrap());
            /* Event codes:
               InstanceReboot
               InstanceRetirement
               InstanceStop
               SystemMaintenance
               SystemReboot
               Unknown
            */
            println!("    Event code:   {}", event.code.unwrap().as_ref());
            println!();
        }
    }
}

/// Lists the events of your EC2 instances in all available regions.
/// # Arguments
///
/// * `[-r REGION]` - The Region in which the client is created.
///    If not supplied, uses the value of the **AWS_REGION** environment variable.
///    If the environment variable is not set, defaults to **us-west-2**.
/// * `[-v]` - Whether to display additional information.
#[tokio::main]
async fn main() -> Result<(), Error> {
    tracing_subscriber::fmt::init();
    let Opt { region, verbose } = Opt::from_args();

    let region = region::ChainProvider::first_try(region.map(Region::new))
        .or_default_provider()
        .or_else(Region::new("us-west-2"));

    println!();

    if verbose {
        println!("EC2 client version: {}", PKG_VERSION);
        println!("Region:             {}", region.region().unwrap().as_ref());
        println!();
    }

    // Get list of available regions.
    let config = Config::builder().region(region).build();
    let ec2_client = Client::from_conf(config);
    let resp = ec2_client.describe_regions().send().await;

    // Show the events for that EC2 instances in that Region.
    for region in resp.unwrap().regions.unwrap_or_default() {
        show_events(region.region_name.unwrap()).await;
    }

    Ok(())
}
<|endoftext|>


<commit_before>pub struct PLPWriter
{
	pub use_tabs: bool,
	pub indent_level: u16,
	pub code: String,
}

impl PLPWriter
{
	pub fn new() -> PLPWriter
	{
		PLPWriter {
			use_tabs: true,
			indent_level: 0,
			code: String::new(),
			}
	}

	pub fn reset(&mut self)
	{
		self.indent_level = 0;
		self.code = String::new();
	}

	pub fn li(&mut self, register: &str, value: &str) -> String
	{
		let mut code = String::new();
		code.push_str("li ");
		code.push_str(register);
		code.push_str(", ");
		code.push_str(value);
		code.push_str("\n");

		self.code.push_str(&*code);
		return code;
	}

	pub fn mov(&mut self, register_to: &str, register_from: &str) -> String
	{
		let mut code = String::new();
		code.push_str("move ");
		code.push_str(register_to);
		code.push_str(", ");
		code.push_str(register_from);
		code.push_str("\n");

		self.code.push_str(&*code);
		return code;
	}

	pub fn addu(&mut self, register_sum: &str, register_addend1: &str, register_addend2: &str) -> String
	{
		let mut code = String::new();
		code.push_str("addu ");
		code.push_str(register_sum);
		code.push_str(", ");
		code.push_str(register_addend1);
		code.push_str(", ");
		code.push_str(register_addend2);
		code.push_str("\n");

		self.code.push_str(&*code);
		return code;
	}

	pub fn subu(&mut self, register_difference: &str, register_addend1: &str, register_addend2: &str) -> String
	{
		let mut code = String::new();
		code.push_str("subu ");
		code.push_str(register_difference);
		code.push_str(", ");
		code.push_str(register_addend1);
		code.push_str(", ");
		code.push_str(register_addend2);
		code.push_str("\n");

		self.code.push_str(&*code);
		return code;
	}

	pub fn mullo(&mut self, register_product: &str, register_multiplicand1: &str, register_multiplicand2: &str) -> String
	{
		let mut code = String::new();
		code.push_str("subu ");
		code.push_str(register_product);
		code.push_str(", ");
		code.push_str(register_multiplicand1);
		code.push_str(", ");
		code.push_str(register_multiplicand2);
		code.push_str("\n");

		self.code.push_str(&*code);
		return code;
	}

	pub fn sw(&mut self, register_target: &str, offset: u16, register_address: &str) -> String
	{
		let mut code = String::new();
		code.push_str("sw ");
		code.push_str(register_target);
		code.push_str(", ");
		code.push_str(&*offset.to_string());
		code.push_str(" (");
		code.push_str(register_address);
		code.push_str(")\n");

		self.code.push_str(&*code);
		return code;
	}

	pub fn lw(&mut self, register_target: &str, offset: u16, register_address: &str) -> String
	{
		let mut code = String::new();
		code.push_str("lw ");
		code.push_str(register_target);
		code.push_str(", ");
		code.push_str(&*offset.to_string());
		code.push_str(" (");
		code.push_str(register_address);
		code.push_str(")\n");

		self.code.push_str(&*code);
		return code;
	}
}
<commit_msg>Add support for pop and push<commit_after>pub struct PLPWriter
{
	pub use_tabs: bool,
	pub indent_level: u16,
	pub code: String,
}

impl PLPWriter
{
	// TODO: implement indentation writing
	pub fn new() -> PLPWriter
	{
		PLPWriter {
			use_tabs: true,
			indent_level: 0,
			code: String::new(),
			}
	}

	pub fn reset(&mut self)
	{
		self.indent_level = 0;
		self.code = String::new();
	}

	pub fn li(&mut self, register: &str, value: &str) -> String
	{
		let mut code = String::new();
		code.push_str("li ");
		code.push_str(register);
		code.push_str(", ");
		code.push_str(value);
		code.push_str("\n");

		self.code.push_str(&*code);
		return code;
	}

	pub fn mov(&mut self, register_to: &str, register_from: &str) -> String
	{
		let mut code = String::new();
		code.push_str("move ");
		code.push_str(register_to);
		code.push_str(", ");
		code.push_str(register_from);
		code.push_str("\n");

		self.code.push_str(&*code);
		return code;
	}

	pub fn addu(&mut self, register_sum: &str, register_addend1: &str, register_addend2: &str) -> String
	{
		let mut code = String::new();
		code.push_str("addu ");
		code.push_str(register_sum);
		code.push_str(", ");
		code.push_str(register_addend1);
		code.push_str(", ");
		code.push_str(register_addend2);
		code.push_str("\n");

		self.code.push_str(&*code);
		return code;
	}

	pub fn subu(&mut self, register_difference: &str, register_addend1: &str, register_addend2: &str) -> String
	{
		let mut code = String::new();
		code.push_str("subu ");
		code.push_str(register_difference);
		code.push_str(", ");
		code.push_str(register_addend1);
		code.push_str(", ");
		code.push_str(register_addend2);
		code.push_str("\n");

		self.code.push_str(&*code);
		return code;
	}

	pub fn mullo(&mut self, register_product: &str, register_multiplicand1: &str, register_multiplicand2: &str) -> String
	{
		let mut code = String::new();
		code.push_str("subu ");
		code.push_str(register_product);
		code.push_str(", ");
		code.push_str(register_multiplicand1);
		code.push_str(", ");
		code.push_str(register_multiplicand2);
		code.push_str("\n");

		self.code.push_str(&*code);
		return code;
	}

	pub fn sw(&mut self, register_target: &str, offset: u16, register_address: &str) -> String
	{
		let mut code = String::new();
		code.push_str("sw ");
		code.push_str(register_target);
		code.push_str(", ");
		code.push_str(&*offset.to_string());
		code.push_str(" (");
		code.push_str(register_address);
		code.push_str(")\n");

		self.code.push_str(&*code);
		return code;
	}

	pub fn lw(&mut self, register_target: &str, offset: u16, register_address: &str) -> String
	{
		let mut code = String::new();
		code.push_str("lw ");
		code.push_str(register_target);
		code.push_str(", ");
		code.push_str(&*offset.to_string());
		code.push_str(" (");
		code.push_str(register_address);
		code.push_str(")\n");

		self.code.push_str(&*code);
		return code;
	}

	pub fn push(&mut self, register_target: &str) -> String
	{
		let mut code = String::new();
		code.push_str("push ");
		code.push_str(register_target);
		code.push_str(")\n");

		self.code.push_str(&*code);
		return code;
	}

	pub fn pop(&mut self, register_target: &str) -> String
	{
		let mut code = String::new();
		code.push_str("pop ");
		code.push_str(register_target);
		code.push_str(")\n");

		self.code.push_str(&*code);
		return code;
	}
}
<|endoftext|>


<commit_before>use std::os;
use std::collections::HashMap;
use std::io;
use std::io::fs::PathExtensions;
use std::borrow::ToOwned;

use vobject::{Component,Property,parse_component,write_component};
use email::rfc5322::Rfc5322Parser;
use uuid::Uuid;

macro_rules! main_try {
    ($result: expr, $errmsg: expr) => (
        match $result {
            Ok(m) => m,
            Err(e) => {
                println!("{}: {}", $errmsg, e);
                os::set_exit_status(1);
                return;
            }
        }
    )
}

fn get_env() -> HashMap<String, String> {
    let mut env = HashMap::new();
    env.extend(os::env().into_iter().filter(|&(ref key, ref value)| {
        (key.as_slice() == "EDITOR" || key.as_slice().starts_with("MATES_")) &&
            value.len() > 0
    }));
    env
}

fn expect_env<'a>(env: &'a HashMap<String, String>, key: &str) -> &'a String {
    env.get(key).expect(
        format!("The {} environment variable must be set.", key).as_slice()
    )
}

fn build_index(outfile: &Path, dir: &Path) -> io::IoResult<()> {
    if !dir.is_dir() {
        return Err(io::IoError {
            kind: io::MismatchedFileTypeForOperation,
            desc: "MATES_DIR must be a directory.",
            detail: None
        });
    };

    let mut outf = io::File::create(outfile);
    let entries = try!(io::fs::readdir(dir));
    for entry in entries.iter() {
        if !entry.is_file() {
            continue;
        }

        print!("Processing {}\n", entry.display());

        let itemstr = try!(io::File::open(entry).read_to_string());
        let item = match parse_component(itemstr.as_slice()) {
            Ok(item) => item,
            Err(e) => {
                println!("Error: Failed to parse item {}: {}\n", entry.display(), e);
                os::set_exit_status(1);
                continue;
            }
        };

        let name = match item.single_prop("FN") {
            Some(name) => name.value_as_string(),
            None => {
                print!("Warning: No name in {}, skipping.\n", entry.display());
                continue;
            }
        };

        let emails = item.all_props("EMAIL");
        for email in emails.iter() {
            try!(outf.write_str(
                format!("{}\t{}\t{}\n", email.value_as_string(), name, entry.display()).as_slice()
            ))
        };
    };
    return Ok(());
}

pub fn cli_main() {
    let env = get_env();
    let mut args = os::args().into_iter();
    let program = args.next().unwrap_or("mates".to_string());

    let help = format!("Usage: {} COMMAND
Commands:
    index:
        Rewrite/create the index.
    mutt-query <query>:
        Search for contact, output is usable for mutt's query_command.
    file-query <query>:
        Search for contact, return just the filename.
    email-query <query>:
        Search for contact, return \"name <email>\".
    add:
        Take mail from stdin, add sender to contacts. Print filename.
    edit <file-or-query>:
        Open contact (given by filepath or search-string) in $MATES_EDITOR. If
        the file is cleared, the contact is removed.", program);

    let print_help = |&:| {
        println!("{}", help);
        println!("Environment variables:");
        println!("- MATES_INDEX: Path to index file, which is basically a cache of all");
        println!("               contacts.");
        println!("- MATES_DIR:   The vdir to use.");
        println!("- MATES_GREP:  The grep executable to use.");
    };

    let command = args.next().unwrap_or("".to_string());

    match command.as_slice() {
        "index" => {
            let index_file = expect_env(&env, "MATES_INDEX");
            let mates_dir = expect_env(&env, "MATES_DIR");
            println!("Rebuilding index file \"{}\"...", index_file);
            main_try!(build_index(
                &Path::new(index_file.as_slice()),
                &Path::new(mates_dir.as_slice())
            ), "Failed to build index");
        },
        "mutt-query" => {
            let query = args.next().unwrap_or("".to_string());
            main_try!(mutt_query(&env, query.as_slice()), "Failed to execute grep");
        },
        "file-query" => {
            let query = args.next().unwrap_or("".to_string());
            main_try!(file_query(&env, query.as_slice()), "Failed to execute grep");
        },
        "email-query" => {
            let query = args.next().unwrap_or("".to_string());
            main_try!(email_query(&env, query.as_slice()), "Failed to execute grep");
        },
        "add" => {
            let mates_dir = expect_env(&env, "MATES_DIR");
            main_try!(add_contact(mates_dir.as_slice()), "Failed to add contact");
        },
        "edit" => {
            let query = args.next().unwrap_or("".to_string());
            let mates_dir = expect_env(&env, "MATES_DIR");
            main_try!(edit_contact(&env, query.as_slice(), mates_dir.as_slice()),
                      "Failed to edit contact");
        },
        _ => {
            print_help();
            if command != "help" && command != "--help" && command != "-h" {
                os::set_exit_status(1);
            }
        }
    };
}

fn add_contact(contact_dir: &str) -> io::IoResult<()> {
    let stdin = try!(io::stdin().lock().read_to_string());
    let from_header = match read_sender_from_email(stdin.as_slice()) {
        Some(x) => x,
        None => return Err(io::IoError {
            kind: io::InvalidInput,
            desc: "Couldn't find From-header in email.",
            detail: None
        })
    };
    let (fullname, email) = parse_from_header(&from_header);

    let (uid, contact_path) = {
        let mut uid;
        let mut contact_path;
        loop {
            uid = Uuid::new_v4().to_simple_string();
            contact_path = Path::new(contact_dir).join(Path::new(format!("{}.vcf", uid)));
            if !contact_path.exists() {
                break
            }
        };
        (uid, contact_path)
    };
    let contact = generate_contact(uid, fullname, email);
    let contact_string = write_component(&contact);
    let mut fp = try!(io::File::create(&contact_path));
    try!(fp.write_str(contact_string.as_slice()));
    println!("{}", contact_path.display());
    Ok(())
}

fn generate_contact(uid: String, fullname: Option<&str>, email: Option<&str>) -> Component {
    let mut contact = Component::new("VCARD".to_string());

    match fullname {
        Some(x) => contact.all_props_mut("FN").push(Property::new(x)),
        None => ()
    };

    match email {
        Some(x) => contact.all_props_mut("EMAIL").push(Property::new(x)),
        None => ()
    };
    contact.all_props_mut("UID").push(Property::new(uid.as_slice()));
    contact
}

/// Return a tuple (fullname, email)
fn parse_from_header<'a>(s: &'a String) -> (Option<&'a str>, Option<&'a str>) {
    let mut split = s.rsplitn(1, ' ');
    let email = match split.next() {
        Some(x) => Some(x.trim_left_matches('<').trim_right_matches('>')),
        None => Some(s.as_slice())
    };
    let name = split.next();
    (name, email)
}

/// Given an email, return value of From header.
fn read_sender_from_email(email: &str) -> Option<String> {
    let mut parser = Rfc5322Parser::new(email);
    while !parser.eof() {
        match parser.consume_header() {
            Some(header) => {
                if header.name == "From" {
                    return header.get_value()
                };
            },
            None => return None
        };
    };
    None
}

fn edit_contact(env: &HashMap<String, String>, query: &str, mates_dir: &str) -> Result<(), String> {
    let editor_cmd = match env.get("MATES_EDITOR") {
        Some(x) => x.as_slice(),
        None => match env.get("EDITOR") {
            Some(x) => x.as_slice(),
            None => return Err("Either MATES_EDITOR or EDITOR has to be set.".to_string())
        }
    };

    let results = {
        if Path::new(mates_dir).join(query).exists() {
            vec![query.to_string()]
        } else {
            let results_iter = match index_query(env, query) {
                Ok(x) => x,
                Err(e) => return Err(format!("Error while fetching index: {}", e))
            };

            results_iter.filter_map(|x| {
                if x.filepath.len() > 0 {
                    Some(x.filepath)
                } else {
                    None
                }}).collect()
        }
    };

    if results.len() < 1 {
        return Err("No such contact.".to_string());
    } else if results.len() > 1 {
        for fname in results.iter() {
            println!("{}", fname);
        };
        return Err("Ambiguous query.".to_string());
    }

    let fpath = results[0].as_slice();
    let mut process = match io::Command::new("sh")
        .arg("-c")
        // clear stdin, http://unix.stackexchange.com/a/77593
        .arg(format!("$0 -- \"$1\" < $2"))
        .arg(editor_cmd.as_slice())
        .arg(fpath)
        .arg("/dev/tty")
        .stdin(io::process::InheritFd(0))
        .stdout(io::process::InheritFd(1))
        .stderr(io::process::InheritFd(2))
        .spawn() {
            Ok(x) => x,
            Err(e) => return Err(format!("Error while invoking editor: {}", e))
        };

    match process.wait() {
        Ok(_) => (),
        Err(e) => return Err(format!("Error while invoking editor: {}", e))
    };

    if match io::File::open(&Path::new(fpath)).read_to_string() {
        Ok(x) => x,
        Err(e) => return Err(format!("File can't be read after user edited it: {}", e))
    }.as_slice().trim().len() == 0 {
        return Err(format!("Contact emptied, file removed."));
    };

    Ok(())
}

fn mutt_query<'a>(env: &HashMap<String, String>, query: &str) -> io::IoResult<()> {
    println!("");  // For some reason mutt requires an empty line
    for item in try!(index_query(env, query)) {
        if item.email.len() > 0 && item.name.len() > 0 {
            println!("{}\t{}\t{}", item.email, item.name, item.filepath);
        };
    };
    Ok(())
}

fn file_query<'a>(env: &HashMap<String, String>, query: &str) -> io::IoResult<()> {
    for item in try!(index_query(env, query)) {
        if item.filepath.len() > 0 {
            println!("{}", item.filepath)
        };
    };
    Ok(())
}

fn email_query<'a>(env: &HashMap<String, String>, query: &str) -> io::IoResult<()> {
    for item in try!(index_query(env, query)) {
        if item.name.len() > 0 && item.email.len() > 0 {
            println!("{} <{}>", item.name, item.email);
        };
    };
    Ok(())
}

fn index_query<'a>(env: &HashMap<String, String>, query: &str) -> io::IoResult<IndexIterator<'a>> {
    let default_grep = "grep".to_owned();
    let grep_cmd = match env.get("MATES_GREP") {
        Some(x) => x,
        None => &default_grep
    };

    let index_path = Path::new(expect_env(env, "MATES_INDEX"));
    let mut process = try!(io::Command::new(grep_cmd.as_slice())
        .arg(query.as_slice())
        .stderr(io::process::InheritFd(2))
        .spawn());

    {
        let mut index_fp = try!(io::File::open(&index_path));
        let mut stdin = process.stdin.take().unwrap();
        try!(stdin.write_str(try!(index_fp.read_to_string()).as_slice()));
    }

    let stream = match process.stdout.as_mut() {
        Some(x) => x,
        None => return Err(io::IoError {
            kind: io::IoUnavailable,
            desc: "Failed to get stdout from grep process.",
            detail: None
        })
    };

    let output = try!(stream.read_to_string());
    Ok(IndexIterator::new(&output))
}

struct IndexItem<'a> {
    pub email: String,
    pub name: String,
    pub filepath: String
}

impl<'a> IndexItem<'a> {
    fn new(line: String) -> IndexItem<'a> {
        let mut parts = line.split('\t');

        IndexItem {
            email: parts.next().unwrap_or("").to_string(),
            name: parts.next().unwrap_or("").to_string(),
            filepath: parts.next().unwrap_or("").to_string()
        }
    }
}

struct IndexIterator<'a> {
    linebuffer: Vec<String>
}

impl<'a> IndexIterator<'a> {
    fn new(output: &String) -> IndexIterator<'a> {

        let rv = output.split('\n').map(|x: &str| x.to_string()).collect();
        IndexIterator {
            linebuffer: rv
        }
    }
}

impl<'a> Iterator for IndexIterator<'a> {
    type Item = IndexItem<'a>;

    fn next(&mut self) -> Option<IndexItem<'a>> {
        match self.linebuffer.pop() {
            Some(x) => Some(IndexItem::new(x)),
            None => None
        }
    }
}
<commit_msg>Remove environment variables from CLI help<commit_after>use std::os;
use std::collections::HashMap;
use std::io;
use std::io::fs::PathExtensions;
use std::borrow::ToOwned;

use vobject::{Component,Property,parse_component,write_component};
use email::rfc5322::Rfc5322Parser;
use uuid::Uuid;

macro_rules! main_try {
    ($result: expr, $errmsg: expr) => (
        match $result {
            Ok(m) => m,
            Err(e) => {
                println!("{}: {}", $errmsg, e);
                os::set_exit_status(1);
                return;
            }
        }
    )
}

fn get_env() -> HashMap<String, String> {
    let mut env = HashMap::new();
    env.extend(os::env().into_iter().filter(|&(ref key, ref value)| {
        (key.as_slice() == "EDITOR" || key.as_slice().starts_with("MATES_")) &&
            value.len() > 0
    }));
    env
}

fn expect_env<'a>(env: &'a HashMap<String, String>, key: &str) -> &'a String {
    env.get(key).expect(
        format!("The {} environment variable must be set.", key).as_slice()
    )
}

fn build_index(outfile: &Path, dir: &Path) -> io::IoResult<()> {
    if !dir.is_dir() {
        return Err(io::IoError {
            kind: io::MismatchedFileTypeForOperation,
            desc: "MATES_DIR must be a directory.",
            detail: None
        });
    };

    let mut outf = io::File::create(outfile);
    let entries = try!(io::fs::readdir(dir));
    for entry in entries.iter() {
        if !entry.is_file() {
            continue;
        }

        print!("Processing {}\n", entry.display());

        let itemstr = try!(io::File::open(entry).read_to_string());
        let item = match parse_component(itemstr.as_slice()) {
            Ok(item) => item,
            Err(e) => {
                println!("Error: Failed to parse item {}: {}\n", entry.display(), e);
                os::set_exit_status(1);
                continue;
            }
        };

        let name = match item.single_prop("FN") {
            Some(name) => name.value_as_string(),
            None => {
                print!("Warning: No name in {}, skipping.\n", entry.display());
                continue;
            }
        };

        let emails = item.all_props("EMAIL");
        for email in emails.iter() {
            try!(outf.write_str(
                format!("{}\t{}\t{}\n", email.value_as_string(), name, entry.display()).as_slice()
            ))
        };
    };
    return Ok(());
}

pub fn cli_main() {
    let env = get_env();
    let mut args = os::args().into_iter();
    let program = args.next().unwrap_or("mates".to_string());

    let help = format!("Usage: {} COMMAND
Commands:
    index:
        Rewrite/create the index.
    mutt-query <query>:
        Search for contact, output is usable for mutt's query_command.
    file-query <query>:
        Search for contact, return just the filename.
    email-query <query>:
        Search for contact, return \"name <email>\".
    add:
        Take mail from stdin, add sender to contacts. Print filename.
    edit <file-or-query>:
        Open contact (given by filepath or search-string) in $MATES_EDITOR. If
        the file is cleared, the contact is removed.", program);

    let print_help = |&:| {
        println!("{}", help);
    };

    let command = args.next().unwrap_or("".to_string());

    match command.as_slice() {
        "index" => {
            let index_file = expect_env(&env, "MATES_INDEX");
            let mates_dir = expect_env(&env, "MATES_DIR");
            println!("Rebuilding index file \"{}\"...", index_file);
            main_try!(build_index(
                &Path::new(index_file.as_slice()),
                &Path::new(mates_dir.as_slice())
            ), "Failed to build index");
        },
        "mutt-query" => {
            let query = args.next().unwrap_or("".to_string());
            main_try!(mutt_query(&env, query.as_slice()), "Failed to execute grep");
        },
        "file-query" => {
            let query = args.next().unwrap_or("".to_string());
            main_try!(file_query(&env, query.as_slice()), "Failed to execute grep");
        },
        "email-query" => {
            let query = args.next().unwrap_or("".to_string());
            main_try!(email_query(&env, query.as_slice()), "Failed to execute grep");
        },
        "add" => {
            let mates_dir = expect_env(&env, "MATES_DIR");
            main_try!(add_contact(mates_dir.as_slice()), "Failed to add contact");
        },
        "edit" => {
            let query = args.next().unwrap_or("".to_string());
            let mates_dir = expect_env(&env, "MATES_DIR");
            main_try!(edit_contact(&env, query.as_slice(), mates_dir.as_slice()),
                      "Failed to edit contact");
        },
        _ => {
            print_help();
            if command != "help" && command != "--help" && command != "-h" {
                os::set_exit_status(1);
            }
        }
    };
}

fn add_contact(contact_dir: &str) -> io::IoResult<()> {
    let stdin = try!(io::stdin().lock().read_to_string());
    let from_header = match read_sender_from_email(stdin.as_slice()) {
        Some(x) => x,
        None => return Err(io::IoError {
            kind: io::InvalidInput,
            desc: "Couldn't find From-header in email.",
            detail: None
        })
    };
    let (fullname, email) = parse_from_header(&from_header);

    let (uid, contact_path) = {
        let mut uid;
        let mut contact_path;
        loop {
            uid = Uuid::new_v4().to_simple_string();
            contact_path = Path::new(contact_dir).join(Path::new(format!("{}.vcf", uid)));
            if !contact_path.exists() {
                break
            }
        };
        (uid, contact_path)
    };
    let contact = generate_contact(uid, fullname, email);
    let contact_string = write_component(&contact);
    let mut fp = try!(io::File::create(&contact_path));
    try!(fp.write_str(contact_string.as_slice()));
    println!("{}", contact_path.display());
    Ok(())
}

fn generate_contact(uid: String, fullname: Option<&str>, email: Option<&str>) -> Component {
    let mut contact = Component::new("VCARD".to_string());

    match fullname {
        Some(x) => contact.all_props_mut("FN").push(Property::new(x)),
        None => ()
    };

    match email {
        Some(x) => contact.all_props_mut("EMAIL").push(Property::new(x)),
        None => ()
    };
    contact.all_props_mut("UID").push(Property::new(uid.as_slice()));
    contact
}

/// Return a tuple (fullname, email)
fn parse_from_header<'a>(s: &'a String) -> (Option<&'a str>, Option<&'a str>) {
    let mut split = s.rsplitn(1, ' ');
    let email = match split.next() {
        Some(x) => Some(x.trim_left_matches('<').trim_right_matches('>')),
        None => Some(s.as_slice())
    };
    let name = split.next();
    (name, email)
}

/// Given an email, return value of From header.
fn read_sender_from_email(email: &str) -> Option<String> {
    let mut parser = Rfc5322Parser::new(email);
    while !parser.eof() {
        match parser.consume_header() {
            Some(header) => {
                if header.name == "From" {
                    return header.get_value()
                };
            },
            None => return None
        };
    };
    None
}

fn edit_contact(env: &HashMap<String, String>, query: &str, mates_dir: &str) -> Result<(), String> {
    let editor_cmd = match env.get("MATES_EDITOR") {
        Some(x) => x.as_slice(),
        None => match env.get("EDITOR") {
            Some(x) => x.as_slice(),
            None => return Err("Either MATES_EDITOR or EDITOR has to be set.".to_string())
        }
    };

    let results = {
        if Path::new(mates_dir).join(query).exists() {
            vec![query.to_string()]
        } else {
            let results_iter = match index_query(env, query) {
                Ok(x) => x,
                Err(e) => return Err(format!("Error while fetching index: {}", e))
            };

            results_iter.filter_map(|x| {
                if x.filepath.len() > 0 {
                    Some(x.filepath)
                } else {
                    None
                }}).collect()
        }
    };

    if results.len() < 1 {
        return Err("No such contact.".to_string());
    } else if results.len() > 1 {
        for fname in results.iter() {
            println!("{}", fname);
        };
        return Err("Ambiguous query.".to_string());
    }

    let fpath = results[0].as_slice();
    let mut process = match io::Command::new("sh")
        .arg("-c")
        // clear stdin, http://unix.stackexchange.com/a/77593
        .arg(format!("$0 -- \"$1\" < $2"))
        .arg(editor_cmd.as_slice())
        .arg(fpath)
        .arg("/dev/tty")
        .stdin(io::process::InheritFd(0))
        .stdout(io::process::InheritFd(1))
        .stderr(io::process::InheritFd(2))
        .spawn() {
            Ok(x) => x,
            Err(e) => return Err(format!("Error while invoking editor: {}", e))
        };

    match process.wait() {
        Ok(_) => (),
        Err(e) => return Err(format!("Error while invoking editor: {}", e))
    };

    if match io::File::open(&Path::new(fpath)).read_to_string() {
        Ok(x) => x,
        Err(e) => return Err(format!("File can't be read after user edited it: {}", e))
    }.as_slice().trim().len() == 0 {
        return Err(format!("Contact emptied, file removed."));
    };

    Ok(())
}

fn mutt_query<'a>(env: &HashMap<String, String>, query: &str) -> io::IoResult<()> {
    println!("");  // For some reason mutt requires an empty line
    for item in try!(index_query(env, query)) {
        if item.email.len() > 0 && item.name.len() > 0 {
            println!("{}\t{}\t{}", item.email, item.name, item.filepath);
        };
    };
    Ok(())
}

fn file_query<'a>(env: &HashMap<String, String>, query: &str) -> io::IoResult<()> {
    for item in try!(index_query(env, query)) {
        if item.filepath.len() > 0 {
            println!("{}", item.filepath)
        };
    };
    Ok(())
}

fn email_query<'a>(env: &HashMap<String, String>, query: &str) -> io::IoResult<()> {
    for item in try!(index_query(env, query)) {
        if item.name.len() > 0 && item.email.len() > 0 {
            println!("{} <{}>", item.name, item.email);
        };
    };
    Ok(())
}

fn index_query<'a>(env: &HashMap<String, String>, query: &str) -> io::IoResult<IndexIterator<'a>> {
    let default_grep = "grep".to_owned();
    let grep_cmd = match env.get("MATES_GREP") {
        Some(x) => x,
        None => &default_grep
    };

    let index_path = Path::new(expect_env(env, "MATES_INDEX"));
    let mut process = try!(io::Command::new(grep_cmd.as_slice())
        .arg(query.as_slice())
        .stderr(io::process::InheritFd(2))
        .spawn());

    {
        let mut index_fp = try!(io::File::open(&index_path));
        let mut stdin = process.stdin.take().unwrap();
        try!(stdin.write_str(try!(index_fp.read_to_string()).as_slice()));
    }

    let stream = match process.stdout.as_mut() {
        Some(x) => x,
        None => return Err(io::IoError {
            kind: io::IoUnavailable,
            desc: "Failed to get stdout from grep process.",
            detail: None
        })
    };

    let output = try!(stream.read_to_string());
    Ok(IndexIterator::new(&output))
}

struct IndexItem<'a> {
    pub email: String,
    pub name: String,
    pub filepath: String
}

impl<'a> IndexItem<'a> {
    fn new(line: String) -> IndexItem<'a> {
        let mut parts = line.split('\t');

        IndexItem {
            email: parts.next().unwrap_or("").to_string(),
            name: parts.next().unwrap_or("").to_string(),
            filepath: parts.next().unwrap_or("").to_string()
        }
    }
}

struct IndexIterator<'a> {
    linebuffer: Vec<String>
}

impl<'a> IndexIterator<'a> {
    fn new(output: &String) -> IndexIterator<'a> {

        let rv = output.split('\n').map(|x: &str| x.to_string()).collect();
        IndexIterator {
            linebuffer: rv
        }
    }
}

impl<'a> Iterator for IndexIterator<'a> {
    type Item = IndexItem<'a>;

    fn next(&mut self) -> Option<IndexItem<'a>> {
        match self.linebuffer.pop() {
            Some(x) => Some(IndexItem::new(x)),
            None => None
        }
    }
}
<|endoftext|>