<commit_before>/*
 * Copyright (C) 2017  Sebastian Wiesner <swiesner@lunaryorn.com>
 *
 * This program is free software: you can redistribute it and/or modify
 * it under the terms of the GNU General Public License as published by
 * the Free Software Foundation, either version 3 of the License, or
 * (at your option) any later version.
 *
 * This program is distributed in the hope that it will be useful,
 * but WITHOUT ANY WARRANTY; without even the implied warranty of
 * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
 * GNU General Public License for more details.
 *
 * You should have received a copy of the GNU General Public License
 * along with this program.  If not, see <http://www.gnu.org/licenses/>.
 */

#[test]
fn it_prints_a_single_password() {
    assert!(false);
}
<commit_msg>Add a couple of test cases<commit_after>/*
 * Copyright (C) 2017  Sebastian Wiesner <swiesner@lunaryorn.com>
 *
 * This program is free software: you can redistribute it and/or modify
 * it under the terms of the GNU General Public License as published by
 * the Free Software Foundation, either version 3 of the License, or
 * (at your option) any later version.
 *
 * This program is distributed in the hope that it will be useful,
 * but WITHOUT ANY WARRANTY; without even the implied warranty of
 * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
 * GNU General Public License for more details.
 *
 * You should have received a copy of the GNU General Public License
 * along with this program.  If not, see <http://www.gnu.org/licenses/>.
 */

use std::ffi::OsStr;
use std::process::{Command, ExitStatus, Output};

struct Result {
    stdout: String,
    stderr: String,
    status: ExitStatus,
}

impl From<Output> for Result {
    fn from(output: Output) -> Result {
        Result {
            status: output.status,
            stdout: String::from_utf8(output.stdout).expect("Failed to decode stdout"),
            stderr: String::from_utf8(output.stderr).expect("Failed to decode stderr"),
        }
    }
}

fn run<S: AsRef<OsStr>>(args: &[S]) -> Result {
    Command::new("cargo")
        .arg("run")
        .arg("--quiet")
        .arg("--")
        .args(args)
        .output()
        .expect("Failed to run xkpwgen for testing")
        .into()
}

fn assert_run<S: AsRef<OsStr>>(args: &[S]) -> Result {
    let result = run(args);
    assert!(result.status.success(),
            "xkpwgen failed with output:
stdout:
{}
stderr:
{}
",
            result.stdout,
            result.stderr);
    result
}

fn parse_phrases<'a>(s: &'a str, sep: &str) -> Vec<Vec<&'a str>> {
    s.lines().map(|w| w.split(sep).collect()).collect()
}

#[test]
fn it_prints_a_single_phrase_by_default() {
    let result = assert_run::<String>(&[]);
    let phrases = parse_phrases(&result.stdout, " ");
    assert!(phrases.len() == 1,
            "Expected one phrase, got {}",
            phrases.len());
}

#[test]
fn it_prints_four_words_per_phrase_by_default() {
    for _ in 0..10 {
        let result = assert_run::<String>(&[]);
        let phrases = parse_phrases(&result.stdout, " ");
        for words in phrases {
            assert!(words.len() == 4, "Expected 4 words, got {}", words.len());
        }
    }
}

#[test]
fn it_prints_no_words_with_whitespace() {
    for _ in 0..10 {
        let result = assert_run::<String>(&[]);
        for word in parse_phrases(&result.stdout, " ").iter().flat_map(|ws| ws.iter()) {
            assert!(!word.contains(|c: char| c.is_whitespace()),
                    "Word {} contained whitespace!",
                    word);
        }
    }
}

#[test]
fn it_prints_no_empty_words() {
    for _ in 0..10 {
        let result = assert_run::<String>(&[]);
        for word in parse_phrases(&result.stdout, " ").iter().flat_map(|ws| ws.iter()) {
            assert!(word.len() > 0, "Got empty word");
        }
    }
}
<|endoftext|>


<commit_before>use codespan::{CodeMap, LineIndex, RawIndex};
use std::io;
use termcolor::{Color, ColorSpec, WriteColor};

use crate::{Diagnostic, LabelStyle, Severity};

#[derive(Clone, Debug)]
pub struct Config {
    pub bug_color: Color,
    pub error_color: Color,
    pub warning_color: Color,
    pub note_color: Color,
    pub help_color: Color,
    pub secondary_color: Color,
    pub gutter_color: Color,
    pub primary_mark: char,
    pub secondary_mark: char,
}

impl Default for Config {
    fn default() -> Config {
        // Blue is really difficult to see on the standard windows command line
        #[cfg(windows)]
        const BLUE: Color = Color::Cyan;
        #[cfg(not(windows))]
        const BLUE: Color = Color::Blue;

        Config {
            bug_color: Color::Red,
            error_color: Color::Red,
            warning_color: Color::Yellow,
            note_color: Color::Green,
            help_color: Color::Cyan,
            secondary_color: BLUE,
            gutter_color: BLUE,
            primary_mark: '^',
            secondary_mark: '-',
        }
    }
}

impl Config {
    /// The color used to mark a given severity.
    pub fn severity_color(&self, severity: Severity) -> Color {
        match severity {
            Severity::Bug => self.bug_color,
            Severity::Error => self.error_color,
            Severity::Warning => self.warning_color,
            Severity::Note => self.note_color,
            Severity::Help => self.help_color,
        }
    }

    /// The style used to mark the labelled section of code.
    pub fn label_color(&self, severity: Severity, label_style: LabelStyle) -> Color {
        match label_style {
            LabelStyle::Primary => self.severity_color(severity),
            LabelStyle::Secondary => self.secondary_color,
        }
    }

    /// The character used for the underlined section of code.
    fn underline_char(&self, label_style: LabelStyle) -> char {
        match label_style {
            LabelStyle::Primary => self.primary_mark,
            LabelStyle::Secondary => self.secondary_mark,
        }
    }
}

pub fn emit(
    mut writer: impl WriteColor,
    config: &Config,
    codemap: &CodeMap<impl AsRef<str>>,
    diagnostic: &Diagnostic,
) -> io::Result<()> {
    let severity_color = config.severity_color(diagnostic.severity);
    let gutter_spec = ColorSpec::new().set_fg(Some(config.gutter_color)).clone();
    let header_message_spec = ColorSpec::new().set_bold(true).set_intense(true).clone();
    let header_primary_spec = ColorSpec::new()
        .set_bold(true)
        .set_intense(true)
        .set_fg(Some(severity_color))
        .clone();

    // Diagnostic header
    //
    // ```
    // error[E0001]: Unexpected type in `+` application
    // ```

    // Write severity name
    //
    // ```
    // error
    // ```
    writer.set_color(&header_primary_spec)?;
    write!(writer, "{}", severity_name(diagnostic.severity))?;
    if let Some(code) = &diagnostic.code {
        // Write error code
        //
        // ```
        // [E0001]
        // ```
        write!(writer, "[{}]", code)?;
    }

    // Write diagnostic message
    //
    // ```
    // : Unexpected type in `+` application
    // ```
    writer.set_color(&header_message_spec)?;
    write!(writer, ": {}", diagnostic.message)?;
    write!(writer, "\n")?;
    writer.reset()?;

    for label in &diagnostic.labels {
        match codemap.find_file(label.span.start()) {
            None => {
                if !label.message.is_empty() {
                    write!(writer, "- {}", label.message)?;
                    write!(writer, "\n")?;
                }
            },
            Some(file) => {
                let (start_line, start_column) =
                    file.location(label.span.start()).expect("location_start");
                let (end_line, _) = file.location(label.span.end()).expect("location_end");
                let start_line_span = file.line_span(start_line).expect("line_span");
                let end_line_span = file.line_span(end_line).expect("line_span");

                // Use the length of the last line number as the gutter padding
                let gutter_padding = format!("{}", end_line.number()).len();

                // File name
                //
                // ```
                // ┌╴ <test>:2:9
                // ```

                // Write gutter
                writer.set_color(&gutter_spec)?;
                write!(writer, "{: >width$} ┌╴ ", "", width = gutter_padding)?;
                writer.reset()?;

                // Write file name
                write!(
                    writer,
                    "{file}:{line}:{column}",
                    file = file.name(),
                    line = start_line.number(),
                    column = start_column.number(),
                )?;
                write!(writer, "\n")?;

                // Source code snippet
                //
                // ```
                //   │
                // 2 │ (+ test "")
                //   │         ^^ Expected integer but got string
                //   ╵
                // ```

                // Write line number and gutter
                writer.set_color(&gutter_spec)?;
                write!(writer, "{: >width$} │ ", "", width = gutter_padding)?;
                write!(writer, "\n")?;
                write!(
                    writer,
                    "{: >width$} │ ",
                    start_line.number(),
                    width = gutter_padding,
                )?;
                writer.reset()?;

                // Write source prefix before marked section
                let source_prefix = file
                    .src_slice(start_line_span.with_end(label.span.start()))
                    .expect("prefix");
                write!(writer, "{}", source_prefix)?;

                let label_color = config.label_color(diagnostic.severity, label.style);
                let label_spec = ColorSpec::new().set_fg(Some(label_color)).clone();

                // Write marked section
                let mark_len = if start_line == end_line {
                    // Single line

                    // Write marked source section
                    let marked_source = file.src_slice(label.span).expect("marked_source");
                    writer.set_color(&label_spec)?;
                    write!(writer, "{}", marked_source)?;
                    writer.reset()?;
                    marked_source.len()
                } else {
                    // Multiple lines

                    // Write marked source section
                    let marked_source = file
                        .src_slice(start_line_span.with_start(label.span.start()))
                        .expect("start_of_marked");
                    writer.set_color(&label_spec)?;
                    write!(writer, "{}", marked_source)?;

                    for line_index in ((start_line.to_usize() + 1)..end_line.to_usize())
                        .map(|i| LineIndex::from(i as RawIndex))
                    {
                        // Write line number and gutter
                        writer.set_color(&gutter_spec)?;
                        write!(
                            writer,
                            "{: >width$} │ ",
                            line_index.number(),
                            width = gutter_padding,
                        )?;

                        // Write marked source section
                        let line_span = file.line_span(line_index).expect("marked_line_span");
                        let marked_source = file
                            .src_slice(line_span)
                            .expect("marked_source")
                            .trim_end_matches(|ch: char| ch == '\r' || ch == '\n');
                        writer.set_color(&label_spec)?;
                        write!(writer, "{}", marked_source)?;
                        write!(writer, "\n")?;
                    }

                    // Write line number and gutter
                    writer.set_color(&gutter_spec)?;
                    write!(
                        writer,
                        "{: >width$} │ ",
                        end_line.number(),
                        width = gutter_padding,
                    )?;

                    // Write marked source section
                    let marked_source = file
                        .src_slice(end_line_span.with_end(label.span.end()))
                        .expect("marked_source");
                    writer.set_color(&label_spec)?;
                    write!(writer, "{}", marked_source)?;
                    writer.reset()?;
                    marked_source.len()
                };

                // Write source suffix after marked section
                let source = file
                    .src_slice(end_line_span.with_start(label.span.end()))
                    .expect("suffix")
                    .trim_end_matches(|ch: char| ch == '\r' || ch == '\n');
                write!(writer, "{}", source)?;
                write!(writer, "\n")?;

                // Write underline gutter
                writer.set_color(&gutter_spec)?;
                write!(writer, "{: >width$} │ ", "", width = gutter_padding)?;
                writer.reset()?;

                // Write underline and label
                writer.set_color(&label_spec)?;
                write!(writer, "{: >width$}", "", width = source_prefix.len())?;
                for _ in 0..mark_len {
                    write!(writer, "{}", config.underline_char(label.style))?;
                }
                if !label.message.is_empty() {
                    write!(writer, " {}", label.message)?;
                    write!(writer, "\n")?;
                }
                writer.reset()?;

                // Write final gutter
                writer.set_color(&gutter_spec)?;
                write!(writer, "{: >width$} ╵", "", width = gutter_padding)?;
                write!(writer, "\n")?;
                writer.reset()?;
            },
        }
    }
    Ok(())
}

/// A string that explains this diagnostic severity.
fn severity_name(severity: Severity) -> &'static str {
    match severity {
        Severity::Bug => "bug",
        Severity::Error => "error",
        Severity::Warning => "warning",
        Severity::Note => "note",
        Severity::Help => "help",
    }
}
<commit_msg>Add Header struct<commit_after>use codespan::{CodeMap, LineIndex, RawIndex};
use std::io;
use termcolor::{Color, ColorSpec, WriteColor};

use crate::{Diagnostic, LabelStyle, Severity};

#[derive(Clone, Debug)]
pub struct Config {
    pub bug_color: Color,
    pub error_color: Color,
    pub warning_color: Color,
    pub note_color: Color,
    pub help_color: Color,
    pub secondary_color: Color,
    pub gutter_color: Color,
    pub primary_mark: char,
    pub secondary_mark: char,
}

impl Default for Config {
    fn default() -> Config {
        // Blue is really difficult to see on the standard windows command line
        #[cfg(windows)]
        const BLUE: Color = Color::Cyan;
        #[cfg(not(windows))]
        const BLUE: Color = Color::Blue;

        Config {
            bug_color: Color::Red,
            error_color: Color::Red,
            warning_color: Color::Yellow,
            note_color: Color::Green,
            help_color: Color::Cyan,
            secondary_color: BLUE,
            gutter_color: BLUE,
            primary_mark: '^',
            secondary_mark: '-',
        }
    }
}

impl Config {
    /// The color used to mark a given severity.
    pub fn severity_color(&self, severity: Severity) -> Color {
        match severity {
            Severity::Bug => self.bug_color,
            Severity::Error => self.error_color,
            Severity::Warning => self.warning_color,
            Severity::Note => self.note_color,
            Severity::Help => self.help_color,
        }
    }

    /// The style used to mark the labelled section of code.
    pub fn label_color(&self, severity: Severity, label_style: LabelStyle) -> Color {
        match label_style {
            LabelStyle::Primary => self.severity_color(severity),
            LabelStyle::Secondary => self.secondary_color,
        }
    }

    /// The character used for the underlined section of code.
    fn underline_char(&self, label_style: LabelStyle) -> char {
        match label_style {
            LabelStyle::Primary => self.primary_mark,
            LabelStyle::Secondary => self.secondary_mark,
        }
    }
}

pub fn emit(
    mut writer: impl WriteColor,
    config: &Config,
    codemap: &CodeMap<impl AsRef<str>>,
    diagnostic: &Diagnostic,
) -> io::Result<()> {
    Header::new(diagnostic).emit(&mut writer, config)?;

    for label in &diagnostic.labels {
        match codemap.find_file(label.span.start()) {
            None => {
                if !label.message.is_empty() {
                    write!(writer, "- {}", label.message)?;
                    write!(writer, "\n")?;
                }
            },
            Some(file) => {
                let gutter_spec = ColorSpec::new().set_fg(Some(config.gutter_color)).clone();
                let (start_line, start_column) =
                    file.location(label.span.start()).expect("location_start");
                let (end_line, _) = file.location(label.span.end()).expect("location_end");
                let start_line_span = file.line_span(start_line).expect("line_span");
                let end_line_span = file.line_span(end_line).expect("line_span");

                // Use the length of the last line number as the gutter padding
                let gutter_padding = format!("{}", end_line.number()).len();

                // File name
                //
                // ```
                // ┌╴ <test>:2:9
                // ```

                // Write gutter
                writer.set_color(&gutter_spec)?;
                write!(writer, "{: >width$} ┌╴ ", "", width = gutter_padding)?;
                writer.reset()?;

                // Write file name
                write!(
                    writer,
                    "{file}:{line}:{column}",
                    file = file.name(),
                    line = start_line.number(),
                    column = start_column.number(),
                )?;
                write!(writer, "\n")?;

                // Source code snippet
                //
                // ```
                //   │
                // 2 │ (+ test "")
                //   │         ^^ Expected integer but got string
                //   ╵
                // ```

                // Write line number and gutter
                writer.set_color(&gutter_spec)?;
                write!(writer, "{: >width$} │ ", "", width = gutter_padding)?;
                write!(writer, "\n")?;
                write!(
                    writer,
                    "{: >width$} │ ",
                    start_line.number(),
                    width = gutter_padding,
                )?;
                writer.reset()?;

                // Write source prefix before marked section
                let source_prefix = file
                    .src_slice(start_line_span.with_end(label.span.start()))
                    .expect("prefix");
                write!(writer, "{}", source_prefix)?;

                let label_color = config.label_color(diagnostic.severity, label.style);
                let label_spec = ColorSpec::new().set_fg(Some(label_color)).clone();

                // Write marked section
                let mark_len = if start_line == end_line {
                    // Single line

                    // Write marked source section
                    let marked_source = file.src_slice(label.span).expect("marked_source");
                    writer.set_color(&label_spec)?;
                    write!(writer, "{}", marked_source)?;
                    writer.reset()?;
                    marked_source.len()
                } else {
                    // Multiple lines

                    // Write marked source section
                    let marked_source = file
                        .src_slice(start_line_span.with_start(label.span.start()))
                        .expect("start_of_marked");
                    writer.set_color(&label_spec)?;
                    write!(writer, "{}", marked_source)?;

                    for line_index in ((start_line.to_usize() + 1)..end_line.to_usize())
                        .map(|i| LineIndex::from(i as RawIndex))
                    {
                        // Write line number and gutter
                        writer.set_color(&gutter_spec)?;
                        write!(
                            writer,
                            "{: >width$} │ ",
                            line_index.number(),
                            width = gutter_padding,
                        )?;

                        // Write marked source section
                        let line_span = file.line_span(line_index).expect("marked_line_span");
                        let marked_source = file
                            .src_slice(line_span)
                            .expect("marked_source")
                            .trim_end_matches(|ch: char| ch == '\r' || ch == '\n');
                        writer.set_color(&label_spec)?;
                        write!(writer, "{}", marked_source)?;
                        write!(writer, "\n")?;
                    }

                    // Write line number and gutter
                    writer.set_color(&gutter_spec)?;
                    write!(
                        writer,
                        "{: >width$} │ ",
                        end_line.number(),
                        width = gutter_padding,
                    )?;

                    // Write marked source section
                    let marked_source = file
                        .src_slice(end_line_span.with_end(label.span.end()))
                        .expect("marked_source");
                    writer.set_color(&label_spec)?;
                    write!(writer, "{}", marked_source)?;
                    writer.reset()?;
                    marked_source.len()
                };

                // Write source suffix after marked section
                let source = file
                    .src_slice(end_line_span.with_start(label.span.end()))
                    .expect("suffix")
                    .trim_end_matches(|ch: char| ch == '\r' || ch == '\n');
                write!(writer, "{}", source)?;
                write!(writer, "\n")?;

                // Write underline gutter
                writer.set_color(&gutter_spec)?;
                write!(writer, "{: >width$} │ ", "", width = gutter_padding)?;
                writer.reset()?;

                // Write underline and label
                writer.set_color(&label_spec)?;
                write!(writer, "{: >width$}", "", width = source_prefix.len())?;
                for _ in 0..mark_len {
                    write!(writer, "{}", config.underline_char(label.style))?;
                }
                if !label.message.is_empty() {
                    write!(writer, " {}", label.message)?;
                    write!(writer, "\n")?;
                }
                writer.reset()?;

                // Write final gutter
                writer.set_color(&gutter_spec)?;
                write!(writer, "{: >width$} ╵", "", width = gutter_padding)?;
                write!(writer, "\n")?;
                writer.reset()?;
            },
        }
    }
    Ok(())
}

/// Diagnostic header
///
/// ```
/// error[E0001]: Unexpected type in `+` application
/// ```
#[derive(Copy, Clone, Debug)]
struct Header<'doc> {
    severity: Severity,
    code: Option<&'doc str>,
    message: &'doc str,
}

impl<'doc> Header<'doc> {
    fn new(diagnostic: &'doc Diagnostic) -> Header<'doc> {
        Header {
            severity: diagnostic.severity,
            code: diagnostic.code.as_ref().map(String::as_str),
            message: &diagnostic.message,
        }
    }

    fn severity_name(&self) -> &'static str {
        match self.severity {
            Severity::Bug => "bug",
            Severity::Error => "error",
            Severity::Warning => "warning",
            Severity::Help => "help",
            Severity::Note => "note",
        }
    }

    fn emit(&self, writer: &mut impl WriteColor, config: &Config) -> io::Result<()> {
        let message_spec = ColorSpec::new().set_bold(true).set_intense(true).clone();
        let primary_spec = ColorSpec::new()
            .set_bold(true)
            .set_intense(true)
            .set_fg(Some(config.severity_color(self.severity)))
            .clone();

        // Write severity name
        //
        // ```
        // error
        // ```
        writer.set_color(&primary_spec)?;
        write!(writer, "{}", self.severity_name())?;
        if let Some(code) = &self.code {
            // Write error code
            //
            // ```
            // [E0001]
            // ```
            write!(writer, "[{}]", code)?;
        }

        // Write diagnostic message
        //
        // ```
        // : Unexpected type in `+` application
        // ```
        writer.set_color(&message_spec)?;
        write!(writer, ": {}", self.message)?;
        write!(writer, "\n")?;
        writer.reset()?;

        Ok(())
    }
}
<|endoftext|>


<commit_before>//! Complete commands within shells

/// Complete commands within bash
pub mod bash {
    use std::ffi::OsString;
    use std::io::Write;

    use unicode_xid::UnicodeXID;

    #[derive(clap::Subcommand)]
    #[clap(hide = true)]
    #[allow(missing_docs)]
    #[derive(Clone, Debug)]
    pub enum CompleteCommand {
        /// Register shell completions for this program
        Complete(CompleteArgs),
    }

    #[derive(clap::Args)]
    #[clap(group = clap::ArgGroup::new("complete").multiple(true).conflicts_with("register"))]
    #[allow(missing_docs)]
    #[derive(Clone, Debug)]
    pub struct CompleteArgs {
        /// Path to write completion-registration to
        #[clap(long, required = true, parse(from_os_str))]
        register: Option<std::path::PathBuf>,

        #[clap(
            long,
            required = true,
            value_name = "COMP_CWORD",
            hide_short_help = true,
            group = "complete"
        )]
        index: Option<usize>,

        #[clap(
            long,
            required = true,
            value_name = "COMP_CWORD",
            hide_short_help = true,
            group = "complete"
        )]
        ifs: Option<String>,

        #[clap(
            long = "type",
            required = true,
            arg_enum,
            hide_short_help = true,
            group = "complete"
        )]
        comp_type: Option<CompType>,

        #[clap(long, hide_short_help = true, group = "complete")]
        space: bool,

        #[clap(
            long,
            conflicts_with = "space",
            hide_short_help = true,
            group = "complete"
        )]
        no_space: bool,

        #[clap(raw = true, hide_short_help = true, group = "complete")]
        comp_words: Vec<OsString>,
    }

    impl CompleteCommand {
        /// Process the completion request
        pub fn complete(&self, cmd: &mut clap::Command) -> std::convert::Infallible {
            self.try_complete(cmd).unwrap_or_else(|e| e.exit());
            std::process::exit(0)
        }

        /// Process the completion request
        pub fn try_complete(&self, cmd: &mut clap::Command) -> clap::Result<()> {
            debug!("CompleteCommand::try_complete: {:?}", self);
            let CompleteCommand::Complete(args) = self;
            if let Some(out_path) = args.register.as_deref() {
                let mut buf = Vec::new();
                let name = cmd.get_name();
                let bin = cmd.get_bin_name().unwrap_or(cmd.get_name());
                register(name, [bin], bin, &Behavior::default(), &mut buf)?;
                if out_path == std::path::Path::new("-") {
                    std::io::stdout().write(&buf)?;
                } else {
                    if out_path.is_dir() {
                        let out_path = out_path.join(file_name(name));
                        std::fs::write(out_path, buf)?;
                    } else {
                        std::fs::write(out_path, buf)?;
                    }
                }
            } else {
                let index = args.index.unwrap_or_default();
                let comp_type = args.comp_type.unwrap_or_default();
                let space = match (args.space, args.no_space) {
                    (true, false) => Some(true),
                    (false, true) => Some(false),
                    (true, true) => {
                        unreachable!("`--space` and `--no-space` set, clap should prevent this")
                    }
                    (false, false) => None,
                }
                .unwrap();
                let current_dir = std::env::current_dir().ok();
                let completions = complete(
                    cmd,
                    args.comp_words.clone(),
                    index,
                    comp_type,
                    space,
                    current_dir.as_deref(),
                )?;

                let mut buf = Vec::new();
                for completion in &completions {
                    write!(&mut buf, "{}", completion.to_string_lossy())?;
                    write!(&mut buf, "{}", args.ifs.as_deref().unwrap_or("\n"))?;
                }
                std::io::stdout().write(&buf)?;
            }

            Ok(())
        }
    }

    /// The recommended file name for the registration code
    pub fn file_name(name: &str) -> String {
        format!("{}.bash", name)
    }

    /// Define the completion behavior
    pub enum Behavior {
        /// Bare bones behavior
        Minimal,
        /// Fallback to readline behavior when no matches are generated
        Readline,
        /// Customize bash's completion behavior
        Custom(String),
    }

    impl Default for Behavior {
        fn default() -> Self {
            Self::Readline
        }
    }

    /// Generate code to register the dynamic completion
    pub fn register(
        name: &str,
        executables: impl IntoIterator<Item = impl AsRef<str>>,
        completer: &str,
        behavior: &Behavior,
        buf: &mut dyn Write,
    ) -> Result<(), std::io::Error> {
        let escaped_name = name.replace("-", "_");
        debug_assert!(
            escaped_name.chars().all(|c| c.is_xid_continue()),
            "`name` must be an identifier, got `{}`",
            escaped_name
        );
        let mut upper_name = escaped_name.clone();
        upper_name.make_ascii_uppercase();

        let executables = executables
            .into_iter()
            .map(|s| shlex::quote(s.as_ref()).into_owned())
            .collect::<Vec<_>>()
            .join(" ");

        let options = match behavior {
            Behavior::Minimal => "-o nospace -o bashdefault",
            Behavior::Readline => "-o nospace -o default -o bashdefault",
            Behavior::Custom(c) => c.as_str(),
        };

        let completer = shlex::quote(completer);

        let script = r#"
_clap_complete_NAME() {
    local IFS=$'\013'
    local SUPPRESS_SPACE=0
    if compopt +o nospace 2> /dev/null; then
        SUPPRESS_SPACE=1
    fi
    if [[ ${SUPPRESS_SPACE} == 1 ]]; then
        SPACE_ARG="--no-space"
    else
        SPACE_ARG="--space"
    fi
    COMPREPLY=( $("COMPLETER" complete --index ${COMP_CWORD} --type ${COMP_TYPE} ${SPACE_ARG} --ifs="$IFS" -- "${COMP_WORDS[@]}") )
    if [[ $? != 0 ]]; then
        unset COMPREPLY
    elif [[ $SUPPRESS_SPACE == 1 ]] && [[ "${COMPREPLY-}" =~ [=/:]$ ]]; then
        compopt -o nospace
    fi
}
complete OPTIONS -F _clap_complete_NAME EXECUTABLES
"#
        .replace("NAME", &escaped_name)
        .replace("EXECUTABLES", &executables)
        .replace("OPTIONS", options)
        .replace("COMPLETER", &completer)
        .replace("UPPER", &upper_name);

        writeln!(buf, "{}", script)?;
        Ok(())
    }

    /// Type of completion attempted that caused a completion function to be called
    #[derive(Copy, Clone, Debug, PartialEq, Eq)]
    #[non_exhaustive]
    pub enum CompType {
        /// Normal completion
        Normal,
        /// List completions after successive tabs
        Successive,
        /// List alternatives on partial word completion
        Alternatives,
        /// List completions if the word is not unmodified
        Unmodified,
        /// Menu completion
        Menu,
    }

    impl clap::ArgEnum for CompType {
        fn value_variants<'a>() -> &'a [Self] {
            &[
                Self::Normal,
                Self::Successive,
                Self::Alternatives,
                Self::Unmodified,
                Self::Menu,
            ]
        }
        fn to_possible_value<'a>(&self) -> ::std::option::Option<clap::PossibleValue<'a>> {
            match self {
                Self::Normal => {
                    let value = "9";
                    debug_assert_eq!(b'\t'.to_string(), value);
                    Some(
                        clap::PossibleValue::new(value)
                            .alias("normal")
                            .help("Normal completion"),
                    )
                }
                Self::Successive => {
                    let value = "63";
                    debug_assert_eq!(b'?'.to_string(), value);
                    Some(
                        clap::PossibleValue::new(value)
                            .alias("successive")
                            .help("List completions after successive tabs"),
                    )
                }
                Self::Alternatives => {
                    let value = "33";
                    debug_assert_eq!(b'!'.to_string(), value);
                    Some(
                        clap::PossibleValue::new(value)
                            .alias("alternatives")
                            .help("List alternatives on partial word completion"),
                    )
                }
                Self::Unmodified => {
                    let value = "64";
                    debug_assert_eq!(b'@'.to_string(), value);
                    Some(
                        clap::PossibleValue::new(value)
                            .alias("unmodified")
                            .help("List completions if the word is not unmodified"),
                    )
                }
                Self::Menu => {
                    let value = "37";
                    debug_assert_eq!(b'%'.to_string(), value);
                    Some(
                        clap::PossibleValue::new(value)
                            .alias("menu")
                            .help("Menu completion"),
                    )
                }
            }
        }
    }

    impl Default for CompType {
        fn default() -> Self {
            Self::Normal
        }
    }

    /// Complete the command specified
    pub fn complete(
        cmd: &mut clap::Command,
        args: Vec<std::ffi::OsString>,
        arg_index: usize,
        _comp_type: CompType,
        _trailing_space: bool,
        current_dir: Option<&std::path::Path>,
    ) -> Result<Vec<std::ffi::OsString>, std::io::Error> {
        cmd.build();

        let raw_args = clap_lex::RawArgs::new(args.into_iter());
        let mut cursor = raw_args.cursor();
        let mut target_cursor = raw_args.cursor();
        raw_args.seek(
            &mut target_cursor,
            clap_lex::SeekFrom::Start(arg_index as u64),
        );

        // TODO: Multicall support
        if !cmd.is_no_binary_name_set() {
            raw_args.next_os(&mut cursor);
        }

        let mut current_cmd = &*cmd;
        while let Some(arg) = raw_args.next(&mut cursor) {
            if let Ok(value) = arg.to_value() {
                if let Some(next_cmd) = current_cmd.find_subcommand(value) {
                    current_cmd = next_cmd;
                }
            }
            if cursor == target_cursor {
                break;
            }
        }

        let completions = complete_new(current_cmd, current_dir)?;

        Ok(completions)
    }

    fn complete_new(
        cmd: &clap::Command,
        current_dir: Option<&std::path::Path>,
    ) -> Result<Vec<std::ffi::OsString>, std::io::Error> {
        let mut completions = Vec::new();

        completions.extend(
            crate::generator::utils::longs_and_visible_aliases(cmd)
                .into_iter()
                .map(|f| format!("--{}", f).into()),
        );
        completions.extend(
            crate::generator::utils::shorts_and_visible_aliases(cmd)
                .into_iter()
                .map(|f| format!("-{}", f).into()),
        );

        let mut positionals = Vec::new();
        let hints = cmd
            .get_positionals()
            .map(|p| p.get_value_hint())
            .map(|h| {
                if h == clap::ValueHint::Unknown {
                    clap::ValueHint::AnyPath
                } else {
                    h
                }
            })
            .collect::<std::collections::HashSet<_>>();
        for hint in hints {
            match hint {
                clap::ValueHint::Unknown => unreachable!("Filtered out"),
                clap::ValueHint::Other => {
                    // Should not complete
                }
                clap::ValueHint::AnyPath => {
                    positionals.extend(complete_path(current_dir, |_| true));
                }
                clap::ValueHint::FilePath => {
                    positionals.extend(complete_path(current_dir, |p| p.is_file()));
                }
                clap::ValueHint::DirPath => {
                    positionals.extend(complete_path(current_dir, |p| p.is_dir()));
                }
                clap::ValueHint::ExecutablePath => {
                    use is_executable::IsExecutable;
                    positionals.extend(complete_path(current_dir, |p| p.is_executable()));
                }
                clap::ValueHint::CommandName
                | clap::ValueHint::CommandString
                | clap::ValueHint::CommandWithArguments
                | clap::ValueHint::Username
                | clap::ValueHint::Hostname
                | clap::ValueHint::Url
                | clap::ValueHint::EmailAddress => {
                    // No completion implementation
                }
                _ => {
                    // Safe-ish fallback
                    positionals.extend(complete_path(current_dir, |_| true));
                }
            }
        }
        positionals.sort();
        positionals.dedup();
        completions.extend(positionals);

        completions.extend(all_subcommands(cmd).into_iter().map(|s| s));

        Ok(completions)
    }

    fn complete_path(
        current_dir: Option<&std::path::Path>,
        is_wanted: impl Fn(&std::path::Path) -> bool,
    ) -> Vec<OsString> {
        let mut completions = Vec::new();

        let current_dir = match current_dir {
            Some(current_dir) => current_dir,
            None => {
                return Vec::new();
            }
        };
        for entry in std::fs::read_dir(current_dir)
            .ok()
            .into_iter()
            .flatten()
            .filter_map(Result::ok)
        {
            let mut path = entry.path();
            if entry.metadata().map(|m| m.is_dir()).unwrap_or(false) {
                path.push(""); // Ensure trailing `/`
                let suggestion = path.strip_prefix(current_dir).unwrap();
                completions.push(suggestion.as_os_str().to_owned());
            } else {
                if is_wanted(&path) {
                    let suggestion = path.strip_prefix(current_dir).unwrap();
                    completions.push(suggestion.as_os_str().to_owned());
                }
            }
        }

        completions
    }

    fn all_subcommands(cmd: &clap::Command) -> Vec<OsString> {
        debug!("all_subcommands");

        let mut scs = crate::generator::utils::all_subcommands(cmd)
            .iter()
            .map(|x| OsString::from(&x.0))
            .collect::<Vec<_>>();
        scs.sort();
        scs.dedup();
        scs
    }
}
<commit_msg>feat(complete): Complete possible values<commit_after>//! Complete commands within shells

/// Complete commands within bash
pub mod bash {
    use std::ffi::OsString;
    use std::io::Write;

    use unicode_xid::UnicodeXID;

    #[derive(clap::Subcommand)]
    #[clap(hide = true)]
    #[allow(missing_docs)]
    #[derive(Clone, Debug)]
    pub enum CompleteCommand {
        /// Register shell completions for this program
        Complete(CompleteArgs),
    }

    #[derive(clap::Args)]
    #[clap(group = clap::ArgGroup::new("complete").multiple(true).conflicts_with("register"))]
    #[allow(missing_docs)]
    #[derive(Clone, Debug)]
    pub struct CompleteArgs {
        /// Path to write completion-registration to
        #[clap(long, required = true, parse(from_os_str))]
        register: Option<std::path::PathBuf>,

        #[clap(
            long,
            required = true,
            value_name = "COMP_CWORD",
            hide_short_help = true,
            group = "complete"
        )]
        index: Option<usize>,

        #[clap(
            long,
            required = true,
            value_name = "COMP_CWORD",
            hide_short_help = true,
            group = "complete"
        )]
        ifs: Option<String>,

        #[clap(
            long = "type",
            required = true,
            arg_enum,
            hide_short_help = true,
            group = "complete"
        )]
        comp_type: Option<CompType>,

        #[clap(long, hide_short_help = true, group = "complete")]
        space: bool,

        #[clap(
            long,
            conflicts_with = "space",
            hide_short_help = true,
            group = "complete"
        )]
        no_space: bool,

        #[clap(raw = true, hide_short_help = true, group = "complete")]
        comp_words: Vec<OsString>,
    }

    impl CompleteCommand {
        /// Process the completion request
        pub fn complete(&self, cmd: &mut clap::Command) -> std::convert::Infallible {
            self.try_complete(cmd).unwrap_or_else(|e| e.exit());
            std::process::exit(0)
        }

        /// Process the completion request
        pub fn try_complete(&self, cmd: &mut clap::Command) -> clap::Result<()> {
            debug!("CompleteCommand::try_complete: {:?}", self);
            let CompleteCommand::Complete(args) = self;
            if let Some(out_path) = args.register.as_deref() {
                let mut buf = Vec::new();
                let name = cmd.get_name();
                let bin = cmd.get_bin_name().unwrap_or(cmd.get_name());
                register(name, [bin], bin, &Behavior::default(), &mut buf)?;
                if out_path == std::path::Path::new("-") {
                    std::io::stdout().write(&buf)?;
                } else {
                    if out_path.is_dir() {
                        let out_path = out_path.join(file_name(name));
                        std::fs::write(out_path, buf)?;
                    } else {
                        std::fs::write(out_path, buf)?;
                    }
                }
            } else {
                let index = args.index.unwrap_or_default();
                let comp_type = args.comp_type.unwrap_or_default();
                let space = match (args.space, args.no_space) {
                    (true, false) => Some(true),
                    (false, true) => Some(false),
                    (true, true) => {
                        unreachable!("`--space` and `--no-space` set, clap should prevent this")
                    }
                    (false, false) => None,
                }
                .unwrap();
                let current_dir = std::env::current_dir().ok();
                let completions = complete(
                    cmd,
                    args.comp_words.clone(),
                    index,
                    comp_type,
                    space,
                    current_dir.as_deref(),
                )?;

                let mut buf = Vec::new();
                for completion in &completions {
                    write!(&mut buf, "{}", completion.to_string_lossy())?;
                    write!(&mut buf, "{}", args.ifs.as_deref().unwrap_or("\n"))?;
                }
                std::io::stdout().write(&buf)?;
            }

            Ok(())
        }
    }

    /// The recommended file name for the registration code
    pub fn file_name(name: &str) -> String {
        format!("{}.bash", name)
    }

    /// Define the completion behavior
    pub enum Behavior {
        /// Bare bones behavior
        Minimal,
        /// Fallback to readline behavior when no matches are generated
        Readline,
        /// Customize bash's completion behavior
        Custom(String),
    }

    impl Default for Behavior {
        fn default() -> Self {
            Self::Readline
        }
    }

    /// Generate code to register the dynamic completion
    pub fn register(
        name: &str,
        executables: impl IntoIterator<Item = impl AsRef<str>>,
        completer: &str,
        behavior: &Behavior,
        buf: &mut dyn Write,
    ) -> Result<(), std::io::Error> {
        let escaped_name = name.replace("-", "_");
        debug_assert!(
            escaped_name.chars().all(|c| c.is_xid_continue()),
            "`name` must be an identifier, got `{}`",
            escaped_name
        );
        let mut upper_name = escaped_name.clone();
        upper_name.make_ascii_uppercase();

        let executables = executables
            .into_iter()
            .map(|s| shlex::quote(s.as_ref()).into_owned())
            .collect::<Vec<_>>()
            .join(" ");

        let options = match behavior {
            Behavior::Minimal => "-o nospace -o bashdefault",
            Behavior::Readline => "-o nospace -o default -o bashdefault",
            Behavior::Custom(c) => c.as_str(),
        };

        let completer = shlex::quote(completer);

        let script = r#"
_clap_complete_NAME() {
    local IFS=$'\013'
    local SUPPRESS_SPACE=0
    if compopt +o nospace 2> /dev/null; then
        SUPPRESS_SPACE=1
    fi
    if [[ ${SUPPRESS_SPACE} == 1 ]]; then
        SPACE_ARG="--no-space"
    else
        SPACE_ARG="--space"
    fi
    COMPREPLY=( $("COMPLETER" complete --index ${COMP_CWORD} --type ${COMP_TYPE} ${SPACE_ARG} --ifs="$IFS" -- "${COMP_WORDS[@]}") )
    if [[ $? != 0 ]]; then
        unset COMPREPLY
    elif [[ $SUPPRESS_SPACE == 1 ]] && [[ "${COMPREPLY-}" =~ [=/:]$ ]]; then
        compopt -o nospace
    fi
}
complete OPTIONS -F _clap_complete_NAME EXECUTABLES
"#
        .replace("NAME", &escaped_name)
        .replace("EXECUTABLES", &executables)
        .replace("OPTIONS", options)
        .replace("COMPLETER", &completer)
        .replace("UPPER", &upper_name);

        writeln!(buf, "{}", script)?;
        Ok(())
    }

    /// Type of completion attempted that caused a completion function to be called
    #[derive(Copy, Clone, Debug, PartialEq, Eq)]
    #[non_exhaustive]
    pub enum CompType {
        /// Normal completion
        Normal,
        /// List completions after successive tabs
        Successive,
        /// List alternatives on partial word completion
        Alternatives,
        /// List completions if the word is not unmodified
        Unmodified,
        /// Menu completion
        Menu,
    }

    impl clap::ArgEnum for CompType {
        fn value_variants<'a>() -> &'a [Self] {
            &[
                Self::Normal,
                Self::Successive,
                Self::Alternatives,
                Self::Unmodified,
                Self::Menu,
            ]
        }
        fn to_possible_value<'a>(&self) -> ::std::option::Option<clap::PossibleValue<'a>> {
            match self {
                Self::Normal => {
                    let value = "9";
                    debug_assert_eq!(b'\t'.to_string(), value);
                    Some(
                        clap::PossibleValue::new(value)
                            .alias("normal")
                            .help("Normal completion"),
                    )
                }
                Self::Successive => {
                    let value = "63";
                    debug_assert_eq!(b'?'.to_string(), value);
                    Some(
                        clap::PossibleValue::new(value)
                            .alias("successive")
                            .help("List completions after successive tabs"),
                    )
                }
                Self::Alternatives => {
                    let value = "33";
                    debug_assert_eq!(b'!'.to_string(), value);
                    Some(
                        clap::PossibleValue::new(value)
                            .alias("alternatives")
                            .help("List alternatives on partial word completion"),
                    )
                }
                Self::Unmodified => {
                    let value = "64";
                    debug_assert_eq!(b'@'.to_string(), value);
                    Some(
                        clap::PossibleValue::new(value)
                            .alias("unmodified")
                            .help("List completions if the word is not unmodified"),
                    )
                }
                Self::Menu => {
                    let value = "37";
                    debug_assert_eq!(b'%'.to_string(), value);
                    Some(
                        clap::PossibleValue::new(value)
                            .alias("menu")
                            .help("Menu completion"),
                    )
                }
            }
        }
    }

    impl Default for CompType {
        fn default() -> Self {
            Self::Normal
        }
    }

    /// Complete the command specified
    pub fn complete(
        cmd: &mut clap::Command,
        args: Vec<std::ffi::OsString>,
        arg_index: usize,
        _comp_type: CompType,
        _trailing_space: bool,
        current_dir: Option<&std::path::Path>,
    ) -> Result<Vec<std::ffi::OsString>, std::io::Error> {
        cmd.build();

        let raw_args = clap_lex::RawArgs::new(args.into_iter());
        let mut cursor = raw_args.cursor();
        let mut target_cursor = raw_args.cursor();
        raw_args.seek(
            &mut target_cursor,
            clap_lex::SeekFrom::Start(arg_index as u64),
        );

        // TODO: Multicall support
        if !cmd.is_no_binary_name_set() {
            raw_args.next_os(&mut cursor);
        }

        let mut current_cmd = &*cmd;
        while let Some(arg) = raw_args.next(&mut cursor) {
            if let Ok(value) = arg.to_value() {
                if let Some(next_cmd) = current_cmd.find_subcommand(value) {
                    current_cmd = next_cmd;
                }
            }
            if cursor == target_cursor {
                break;
            }
        }

        let completions = complete_new(current_cmd, current_dir)?;

        Ok(completions)
    }

    fn complete_new(
        cmd: &clap::Command,
        current_dir: Option<&std::path::Path>,
    ) -> Result<Vec<std::ffi::OsString>, std::io::Error> {
        let mut completions = Vec::new();

        completions.extend(
            crate::generator::utils::longs_and_visible_aliases(cmd)
                .into_iter()
                .map(|f| format!("--{}", f).into()),
        );
        completions.extend(
            crate::generator::utils::shorts_and_visible_aliases(cmd)
                .into_iter()
                .map(|f| format!("-{}", f).into()),
        );

        let mut positionals = Vec::new();
        positionals.extend(
            cmd.get_positionals()
                .flat_map(|p| p.get_possible_values())
                .flatten()
                .map(|p| p.get_name().into()),
        );
        let hints = cmd
            .get_positionals()
            .map(|p| p.get_value_hint())
            .map(|h| {
                if h == clap::ValueHint::Unknown {
                    clap::ValueHint::AnyPath
                } else {
                    h
                }
            })
            .collect::<std::collections::HashSet<_>>();
        for hint in hints {
            match hint {
                clap::ValueHint::Unknown => unreachable!("Filtered out"),
                clap::ValueHint::Other => {
                    // Should not complete
                }
                clap::ValueHint::AnyPath => {
                    positionals.extend(complete_path(current_dir, |_| true));
                }
                clap::ValueHint::FilePath => {
                    positionals.extend(complete_path(current_dir, |p| p.is_file()));
                }
                clap::ValueHint::DirPath => {
                    positionals.extend(complete_path(current_dir, |p| p.is_dir()));
                }
                clap::ValueHint::ExecutablePath => {
                    use is_executable::IsExecutable;
                    positionals.extend(complete_path(current_dir, |p| p.is_executable()));
                }
                clap::ValueHint::CommandName
                | clap::ValueHint::CommandString
                | clap::ValueHint::CommandWithArguments
                | clap::ValueHint::Username
                | clap::ValueHint::Hostname
                | clap::ValueHint::Url
                | clap::ValueHint::EmailAddress => {
                    // No completion implementation
                }
                _ => {
                    // Safe-ish fallback
                    positionals.extend(complete_path(current_dir, |_| true));
                }
            }
        }
        positionals.sort();
        positionals.dedup();
        completions.extend(positionals);

        completions.extend(all_subcommands(cmd).into_iter().map(|s| s));

        Ok(completions)
    }

    fn complete_path(
        current_dir: Option<&std::path::Path>,
        is_wanted: impl Fn(&std::path::Path) -> bool,
    ) -> Vec<OsString> {
        let mut completions = Vec::new();

        let current_dir = match current_dir {
            Some(current_dir) => current_dir,
            None => {
                return Vec::new();
            }
        };
        for entry in std::fs::read_dir(current_dir)
            .ok()
            .into_iter()
            .flatten()
            .filter_map(Result::ok)
        {
            let mut path = entry.path();
            if entry.metadata().map(|m| m.is_dir()).unwrap_or(false) {
                path.push(""); // Ensure trailing `/`
                let suggestion = path.strip_prefix(current_dir).unwrap();
                completions.push(suggestion.as_os_str().to_owned());
            } else {
                if is_wanted(&path) {
                    let suggestion = path.strip_prefix(current_dir).unwrap();
                    completions.push(suggestion.as_os_str().to_owned());
                }
            }
        }

        completions
    }

    fn all_subcommands(cmd: &clap::Command) -> Vec<OsString> {
        debug!("all_subcommands");

        let mut scs = crate::generator::utils::all_subcommands(cmd)
            .iter()
            .map(|x| OsString::from(&x.0))
            .collect::<Vec<_>>();
        scs.sort();
        scs.dedup();
        scs
    }
}
<|endoftext|>


<commit_before>// Copyright (c) The Libra Core Contributors
// SPDX-License-Identifier: Apache-2.0

//! Convenience structs and functions for generating configuration for a swarm of libra nodes
use failure::{bail, ensure, Result};
use libra_config::{
    config::{NodeConfig, PersistableConfig, RoleType},
    generator,
};
use libra_crypto::{
    ed25519::{Ed25519PrivateKey, Ed25519PublicKey},
    test_utils::KeyPair,
};
use libra_types::transaction::Transaction;
use std::path::{Path, PathBuf};
use vm_genesis;

pub fn full_node_swarm(
    template: NodeConfig,
    num_nodes: usize,
    prune_seed_peers: bool,
    is_ipv4: bool,
    key_seed: Option<[u8; 32]>,
    output_dir: &Path,
    is_permissioned: bool,
    upstream_peer_dir: PathBuf,
) -> Result<Vec<PathBuf>> {
    ensure!(num_nodes > 0, "Cannot build 0 NodeConfigs");
    let mut upstream_peer = NodeConfig::load(&upstream_peer_dir.join("node.config.toml"))?;
    let mut configs = generator::full_node_swarm(
        &mut upstream_peer,
        template,
        num_nodes,
        prune_seed_peers,
        is_ipv4,
        key_seed,
        is_permissioned,
    )?;
    ensure!(
        configs.len() == num_nodes,
        format!(
            "Expected {}, found {} NodeConfigs",
            configs.len(),
            num_nodes
        )
    );
    let mut config_files = Vec::new();

    for (index, config) in configs.iter_mut().enumerate() {
        let node_dir = output_dir.join(format!("{}", index));
        std::fs::create_dir_all(&node_dir).expect("unable to create config dir");

        config.set_data_dir(node_dir.clone())?;
        config.save(&PathBuf::from("node.config.toml"));
        config_files.push(node_dir.join("node.config.toml"));
    }

    upstream_peer.save(&PathBuf::from("node.config.toml"));

    Ok(config_files)
}

pub fn validator_swarm(
    template: NodeConfig,
    num_nodes: usize,
    faucet_key: KeyPair<Ed25519PrivateKey, Ed25519PublicKey>,
    prune_seed_peers: bool,
    is_ipv4: bool,
    key_seed: Option<[u8; 32]>,
    output_dir: &Path,
) -> Result<Vec<PathBuf>> {
    ensure!(num_nodes > 0, "Cannot build 0 NodeConfigs");
    let mut configs =
        generator::validator_swarm(template, num_nodes, prune_seed_peers, is_ipv4, key_seed)?;
    ensure!(
        configs.len() == num_nodes,
        format!(
            "Expected {}, found {} NodeConfigs",
            configs.len(),
            num_nodes
        )
    );

    let mut config_files = Vec::new();
    let consensus_peers = &configs[0].consensus.consensus_peers;
    let network = match configs[0].validator_network.as_ref() {
        Some(network) => network,
        None => bail!("Unable to read validator_network"),
    };

    let genesis = Transaction::UserTransaction(
        vm_genesis::encode_genesis_transaction_with_validator(
            &faucet_key.private_key,
            faucet_key.public_key.clone(),
            consensus_peers.get_validator_set(&network.network_peers),
        )
        .into_inner(),
    );

    for (index, config) in configs.iter_mut().enumerate() {
        let node_dir = output_dir.join(format!("{}", index));
        std::fs::create_dir_all(&node_dir).expect("unable to create config dir");

        config.set_data_dir(node_dir.clone())?;
        config_files.push(node_dir.join("node.config.toml"));
        config.execution.genesis = Some(genesis.clone());
        config.save(&PathBuf::from("node.config.toml"));
    }

    Ok(config_files)
}

pub struct SwarmConfigBuilder {
    num_nodes: usize,
    template_path: Option<PathBuf>,
    output_dir: PathBuf,
    force_discovery: bool,
    is_ipv4: bool,
    key_seed: Option<[u8; 32]>,
    faucet_account_keypair_filepath: Option<PathBuf>,
    faucet_account_keypair: Option<KeyPair<Ed25519PrivateKey, Ed25519PublicKey>>,
    role: RoleType,
    upstream_peer_dir: Option<String>,
    is_permissioned: bool,
}

impl Default for SwarmConfigBuilder {
    fn default() -> Self {
        SwarmConfigBuilder {
            num_nodes: 1,
            template_path: None,
            output_dir: "configs".into(),
            force_discovery: false,
            is_ipv4: false,
            key_seed: None,
            faucet_account_keypair_filepath: None,
            faucet_account_keypair: None,
            role: RoleType::Validator,
            upstream_peer_dir: None,
            is_permissioned: true,
        }
    }
}

impl SwarmConfigBuilder {
    pub fn new() -> SwarmConfigBuilder {
        SwarmConfigBuilder::default()
    }

    pub fn with_base<P: AsRef<Path>>(&mut self, base_template_path: P) -> &mut Self {
        self.template_path = Some(base_template_path.as_ref().to_path_buf());
        self
    }

    pub fn with_output_dir<P: AsRef<Path>>(&mut self, output_dir: P) -> &mut Self {
        self.output_dir = output_dir.as_ref().to_path_buf();
        self
    }

    pub fn with_faucet_keypair_filepath<P: AsRef<Path>>(&mut self, keypair_file: P) -> &mut Self {
        self.faucet_account_keypair_filepath = Some(keypair_file.as_ref().to_path_buf());
        self
    }

    pub fn with_faucet_keypair(
        &mut self,
        keypair: KeyPair<Ed25519PrivateKey, Ed25519PublicKey>,
    ) -> &mut Self {
        self.faucet_account_keypair = Some(keypair);
        self
    }

    pub fn with_num_nodes(&mut self, num_nodes: usize) -> &mut Self {
        self.num_nodes = num_nodes;
        self
    }

    pub fn with_role(&mut self, role: RoleType) -> &mut Self {
        self.role = role;
        self
    }

    pub fn force_discovery(&mut self) -> &mut Self {
        self.force_discovery = true;
        self
    }

    pub fn with_ipv4(&mut self) -> &mut Self {
        self.is_ipv4 = true;
        self
    }

    pub fn with_ipv6(&mut self) -> &mut Self {
        self.is_ipv4 = false;
        self
    }

    pub fn with_key_seed(&mut self, seed: [u8; 32]) -> &mut Self {
        self.key_seed = Some(seed);
        self
    }

    pub fn with_upstream_config_dir(&mut self, upstream_peer_dir: Option<String>) -> &mut Self {
        self.upstream_peer_dir = upstream_peer_dir;
        self
    }

    pub fn with_is_permissioned(&mut self, is_permissioned: bool) -> &mut Self {
        self.is_permissioned = is_permissioned;
        self
    }

    pub fn build(mut self) -> Result<Vec<PathBuf>> {
        // verify required fields
        let faucet_key_path = self.faucet_account_keypair_filepath.clone();
        let faucet_key = self.faucet_account_keypair.take().unwrap_or_else(|| {
            generate_keypair::load_key_from_file(
                faucet_key_path.expect("Must provide faucet key file"),
            )
            .expect("Faucet account key is required to generate config")
        });

        // generate all things needed for generation
        if !self.output_dir.is_dir() {
            if !self.output_dir.exists() {
                // generate if doesn't exist
                std::fs::create_dir(&self.output_dir).expect("Failed to create output dir");
            }
            assert!(
                !self.output_dir.is_file(),
                "Output-dir is a file, expecting a directory"
            );
        }

        // read template
        let template = if let Some(template_path) = self.template_path {
            NodeConfig::load_config(template_path)
        } else {
            NodeConfig::default()
        };

        if self.role == RoleType::Validator {
            validator_swarm(
                template,
                self.num_nodes,
                faucet_key,
                self.force_discovery,
                self.is_ipv4,
                self.key_seed,
                &self.output_dir,
            )
        } else {
            full_node_swarm(
                template,
                self.num_nodes,
                self.force_discovery,
                self.is_ipv4,
                self.key_seed,
                &self.output_dir,
                self.is_permissioned,
                PathBuf::from(
                    self.upstream_peer_dir
                        .expect("Upstream config path not set"),
                ),
            )
        }
    }
}
<commit_msg>[config] Streamline config generation<commit_after>// Copyright (c) The Libra Core Contributors
// SPDX-License-Identifier: Apache-2.0

//! Convenience structs and functions for generating configuration for a swarm of libra nodes
use failure::{bail, ensure, format_err, Result};
use libra_config::{
    config::{NodeConfig, PersistableConfig, RoleType},
    generator,
};
use libra_crypto::{
    ed25519::{Ed25519PrivateKey, Ed25519PublicKey},
    test_utils::KeyPair,
};
use libra_types::transaction::Transaction;
use std::path::{Path, PathBuf};
use vm_genesis;

pub struct SwarmConfigBuilder {
    num_nodes: usize,
    template_path: Option<PathBuf>,
    output_dir: PathBuf,
    force_discovery: bool,
    is_ipv4: bool,
    key_seed: Option<[u8; 32]>,
    faucet_account_keypair_filepath: Option<PathBuf>,
    faucet_account_keypair: Option<KeyPair<Ed25519PrivateKey, Ed25519PublicKey>>,
    role: RoleType,
    upstream_peer_dir: Option<String>,
    is_permissioned: bool,
}

impl Default for SwarmConfigBuilder {
    fn default() -> Self {
        SwarmConfigBuilder {
            num_nodes: 1,
            template_path: None,
            output_dir: "configs".into(),
            force_discovery: false,
            is_ipv4: false,
            key_seed: None,
            faucet_account_keypair_filepath: None,
            faucet_account_keypair: None,
            role: RoleType::Validator,
            upstream_peer_dir: None,
            is_permissioned: true,
        }
    }
}

impl SwarmConfigBuilder {
    pub fn new() -> SwarmConfigBuilder {
        SwarmConfigBuilder::default()
    }

    pub fn with_base<P: AsRef<Path>>(&mut self, base_template_path: P) -> &mut Self {
        self.template_path = Some(base_template_path.as_ref().to_path_buf());
        self
    }

    pub fn with_output_dir<P: AsRef<Path>>(&mut self, output_dir: P) -> &mut Self {
        self.output_dir = output_dir.as_ref().to_path_buf();
        self
    }

    pub fn with_faucet_keypair_filepath<P: AsRef<Path>>(&mut self, keypair_file: P) -> &mut Self {
        self.faucet_account_keypair_filepath = Some(keypair_file.as_ref().to_path_buf());
        self
    }

    pub fn with_faucet_keypair(
        &mut self,
        keypair: KeyPair<Ed25519PrivateKey, Ed25519PublicKey>,
    ) -> &mut Self {
        self.faucet_account_keypair = Some(keypair);
        self
    }

    pub fn with_num_nodes(&mut self, num_nodes: usize) -> &mut Self {
        self.num_nodes = num_nodes;
        self
    }

    pub fn with_role(&mut self, role: RoleType) -> &mut Self {
        self.role = role;
        self
    }

    pub fn force_discovery(&mut self) -> &mut Self {
        self.force_discovery = true;
        self
    }

    pub fn with_ipv4(&mut self) -> &mut Self {
        self.is_ipv4 = true;
        self
    }

    pub fn with_ipv6(&mut self) -> &mut Self {
        self.is_ipv4 = false;
        self
    }

    pub fn with_key_seed(&mut self, seed: [u8; 32]) -> &mut Self {
        self.key_seed = Some(seed);
        self
    }

    pub fn with_upstream_config_dir(&mut self, upstream_peer_dir: Option<String>) -> &mut Self {
        self.upstream_peer_dir = upstream_peer_dir;
        self
    }

    pub fn with_is_permissioned(&mut self, is_permissioned: bool) -> &mut Self {
        self.is_permissioned = is_permissioned;
        self
    }

    pub fn build(&mut self) -> Result<Vec<PathBuf>> {
        ensure!(self.num_nodes > 0, "Cannot build 0 NodeConfigs");

        let faucet_key = if let Some(keypair) = self.faucet_account_keypair.take() {
            keypair
        } else {
            let faucet_key_path = self
                .faucet_account_keypair_filepath
                .as_ref()
                .ok_or_else(|| format_err!("Must provide faucet keys or faucet key file"))?;
            generate_keypair::load_key_from_file(faucet_key_path)?
        };

        if !self.output_dir.is_dir() {
            if !self.output_dir.exists() {
                std::fs::create_dir(&self.output_dir)?;
            }
            ensure!(
                !self.output_dir.is_file(),
                "Output-dir is a file, expecting a directory"
            );
        }

        let template = if let Some(template_path) = &&self.template_path {
            NodeConfig::load_config(template_path)
        } else {
            NodeConfig::default()
        };

        let mut configs;
        let genesis;

        if self.role.is_validator() {
            configs = generator::validator_swarm(
                template,
                self.num_nodes,
                self.force_discovery,
                self.is_ipv4,
                self.key_seed,
            )?;
            let config = configs
                .get(0)
                .ok_or_else(|| format_err!("Missing NodeConfig"))?;
            let consensus_peers = &config.consensus.consensus_peers;
            let network = match config.validator_network.as_ref() {
                Some(network) => network,
                None => bail!("Unable to read validator_network"),
            };

            genesis = Some(Transaction::UserTransaction(
                vm_genesis::encode_genesis_transaction_with_validator(
                    &faucet_key.private_key,
                    faucet_key.public_key.clone(),
                    consensus_peers.get_validator_set(&network.network_peers),
                )
                .into_inner(),
            ));
        } else {
            let upstream_peer_dir = self
                .upstream_peer_dir
                .as_ref()
                .ok_or_else(|| format_err!("Missing upstream_peer_dir"))?;
            let upstream_peer_dir = PathBuf::from(upstream_peer_dir);
            let mut upstream_peer = NodeConfig::load(upstream_peer_dir.join("node.config.toml"))?;
            genesis = upstream_peer.execution.genesis.clone();
            configs = generator::full_node_swarm(
                &mut upstream_peer,
                template,
                self.num_nodes,
                self.force_discovery,
                self.is_ipv4,
                self.key_seed,
                self.is_permissioned,
            )?;
            upstream_peer.save(&PathBuf::from("node.config.toml"));
        }

        ensure!(
            configs.len() == self.num_nodes,
            format!(
                "Expected {}, found {} NodeConfigs",
                configs.len(),
                self.num_nodes
            )
        );

        let mut config_files = vec![];

        for (index, config) in configs.iter_mut().enumerate() {
            let node_dir = self.output_dir.join(format!("{}", index));
            std::fs::create_dir_all(&node_dir).expect("unable to create config dir");

            config.set_data_dir(node_dir.clone())?;
            config_files.push(node_dir.join("node.config.toml"));
            config.execution.genesis = genesis.clone();
            config.save(&PathBuf::from("node.config.toml"));
        }

        Ok(config_files)
    }
}
<|endoftext|>


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

use crate::{
    completion::on_completion,
    goto_definition::on_goto_definition,
    hover::on_hover,
    lsp::{
        set_actual_server_status, set_starting_status, CompletionOptions, Connection,
        GotoDefinition, HoverRequest, InitializeParams, Message, ServerCapabilities,
        ServerResponse, TextDocumentSyncCapability, TextDocumentSyncKind, WorkDoneProgressOptions,
    },
    lsp_process_error::{LSPProcessError, LSPProcessResult},
    references::on_references,
    shutdown::{on_exit, on_shutdown},
    status_reporting::LSPStatusReporter,
    text_documents::on_did_save_text_document,
    text_documents::{
        on_did_change_text_document, on_did_close_text_document, on_did_open_text_document,
    },
};
use common::{PerfLogEvent, PerfLogger};
use crossbeam::{SendError, Sender};
use log::info;
use lsp_server::{ErrorCode, Notification, ResponseError};
use lsp_types::{
    notification::{
        DidChangeTextDocument, DidCloseTextDocument, DidOpenTextDocument, DidSaveTextDocument, Exit,
    },
    request::{Completion, References, Shutdown},
};
use relay_compiler::config::Config;
use std::sync::{Arc, RwLock};
mod lsp_request_dispatch;
use lsp_request_dispatch::LSPRequestDispatch;
mod lsp_notification_dispatch;
use lsp_notification_dispatch::LSPNotificationDispatch;
mod lsp_state;
pub use lsp_state::LSPExtraDataProvider;
pub(crate) use lsp_state::{LSPState, LSPStateError};

/// Initializes an LSP connection, handling the `initialize` message and `initialized` notification
/// handshake.
pub fn initialize(connection: &Connection) -> LSPProcessResult<InitializeParams> {
    let mut server_capabilities = ServerCapabilities::default();
    // Enable text document syncing so we can know when files are opened/changed/saved/closed
    server_capabilities.text_document_sync =
        Some(TextDocumentSyncCapability::Kind(TextDocumentSyncKind::Full));

    server_capabilities.completion_provider = Some(CompletionOptions {
        resolve_provider: Some(true),
        trigger_characters: Some(vec!["(".into(), "{".into(), "\n".into(), ",".into()]),
        work_done_progress_options: WorkDoneProgressOptions {
            work_done_progress: None,
        },
    });

    server_capabilities.hover_provider = Some(true);
    server_capabilities.definition_provider = Some(true);
    server_capabilities.references_provider = Some(true);

    let server_capabilities = serde_json::to_value(&server_capabilities)?;
    let params = connection.initialize(server_capabilities)?;
    let params: InitializeParams = serde_json::from_value(params)?;
    Ok(params)
}

/// Run the main server loop
pub async fn run<TPerfLogger: PerfLogger + 'static>(
    connection: Connection,
    mut config: Config,
    _params: InitializeParams,
    perf_logger: Arc<TPerfLogger>,
    extra_data_provider: Box<dyn LSPExtraDataProvider + Send + Sync>,
) -> LSPProcessResult<()>
where
    TPerfLogger: PerfLogger + 'static,
{
    info!(
        "Running language server with config root {:?}",
        config.root_dir
    );
    set_starting_status(&connection.sender);

    let lsp_state_errors: Arc<RwLock<Vec<LSPStateError>>> = Default::default();

    config.status_reporter = Box::new(LSPStatusReporter::new(
        config.root_dir.clone(),
        connection.sender.clone(),
        Arc::clone(&lsp_state_errors),
    ));

    let mut lsp_state = LSPState::create_state(
        Arc::new(config),
        lsp_state_errors,
        extra_data_provider,
        Arc::clone(&perf_logger),
    )
    .await?;

    // At this point we're ready to provide hover/complete/go_to_definition capabilities
    set_actual_server_status(&connection.sender, &lsp_state.get_errors());

    // And now we can listen for messages and notification
    for msg in connection.receiver {
        info!("LSP message received {:?}", msg);
        match msg {
            Message::Request(req) => {
                handle_request(&mut lsp_state, req, &connection.sender, &perf_logger)
                    .map_err(LSPProcessError::from)?;
            }
            Message::Notification(notification) => {
                handle_notification(&mut lsp_state, notification, &perf_logger);
            }
            _ => {
                // Ignore responses for now
            }
        }
    }

    Ok(())
}

fn handle_request<TPerfLogger: PerfLogger + 'static>(
    lsp_state: &mut LSPState<TPerfLogger>,
    request: lsp_server::Request,
    sender: &Sender<Message>,
    perf_logger: &Arc<TPerfLogger>,
) -> Result<(), SendError<Message>> {
    let get_server_response_bound = |req| dispatch_request(req, lsp_state);
    let get_response = with_request_logging(perf_logger, get_server_response_bound);

    sender.send(Message::Response(get_response(request)))
}

fn dispatch_request<TPerfLogger: PerfLogger + 'static>(
    request: lsp_server::Request,
    lsp_state: &mut LSPState<TPerfLogger>,
) -> ServerResponse {
    let get_response = || -> Result<_, ServerResponse> {
        let request = LSPRequestDispatch::new(request, lsp_state)
            .on_request_sync::<HoverRequest>(on_hover)?
            .on_request_sync::<GotoDefinition>(on_goto_definition)?
            .on_request_sync::<References>(on_references)?
            .on_request_sync::<Completion>(on_completion)?
            .on_request_sync::<Shutdown>(on_shutdown)?
            .request();

        // If we have gotten here, we have not handled the request
        Ok(ServerResponse {
            id: request.id,
            result: None,
            error: Some(ResponseError {
                code: ErrorCode::MethodNotFound as i32,
                data: None,
                message: format!("No handler registered for method '{}'", request.method),
            }),
        })
    };
    get_response().unwrap_or_else(|response| response)
}

fn with_request_logging<'a, TPerfLogger: PerfLogger + 'static>(
    perf_logger: &'a Arc<TPerfLogger>,
    get_response: impl FnOnce(lsp_server::Request) -> ServerResponse + 'a,
) -> impl FnOnce(lsp_server::Request) -> ServerResponse + 'a {
    move |request| {
        let lsp_request_event = perf_logger.create_event("lsp_message");
        lsp_request_event.string("lsp_method", request.method.clone());
        lsp_request_event.string("lsp_type", "request".to_string());
        let lsp_request_processing_time = lsp_request_event.start("lsp_message_processing_time");

        let response = get_response(request);

        if response.result.is_some() {
            lsp_request_event.string("lsp_outcome", "success".to_string());
        } else if let Some(error) = &response.error {
            lsp_request_event.string("lsp_outcome", "error".to_string());
            if let Some(data) = &error.data {
                lsp_request_event.string("lsp_error_data", data.to_string());
            }
        }
        // N.B. we don't handle the case where the ServerResponse has neither a result nor
        // an error, which is an invalid state.

        lsp_request_event.stop(lsp_request_processing_time);
        perf_logger.complete_event(lsp_request_event);

        response
    }
}

fn handle_notification<TPerfLogger: PerfLogger + 'static>(
    lsp_state: &mut LSPState<TPerfLogger>,
    notification: Notification,
    perf_logger: &Arc<TPerfLogger>,
) {
    let lsp_notification_event = perf_logger.create_event("lsp_message");
    lsp_notification_event.string("lsp_method", notification.method.clone());
    lsp_notification_event.string("lsp_type", "notification".to_string());
    let lsp_notification_processing_time =
        lsp_notification_event.start("lsp_message_processing_time");

    let notification_result = dispatch_notification(notification, lsp_state);

    // The only possible error (for now) is not handling the notification.
    // N.B. is_ok is correct here.
    if notification_result.is_ok() {
        lsp_notification_event.string("lsp_outcome", "error".to_string());
    }

    lsp_notification_event.stop(lsp_notification_processing_time);
    perf_logger.complete_event(lsp_notification_event);
}

fn dispatch_notification<TPerfLogger: PerfLogger + 'static>(
    notification: lsp_server::Notification,
    lsp_state: &mut LSPState<TPerfLogger>,
) -> Result<(), ()> {
    let notification = LSPNotificationDispatch::new(notification, lsp_state)
        .on_notification_sync::<DidOpenTextDocument>(on_did_open_text_document)?
        .on_notification_sync::<DidCloseTextDocument>(on_did_close_text_document)?
        .on_notification_sync::<DidChangeTextDocument>(on_did_change_text_document)?
        .on_notification_sync::<DidSaveTextDocument>(on_did_save_text_document)?
        .on_notification_sync::<Exit>(on_exit)?
        .notification();

    // If we have gotten here, we have not handled the notification
    info!(
        "Error: no handler registered for notification '{}'",
        notification.method
    );
    Ok(())
}
<commit_msg>remove unimportant or outdated comments<commit_after>/*
 * Copyright (c) Facebook, Inc. and its affiliates.
 *
 * This source code is licensed under the MIT license found in the
 * LICENSE file in the root directory of this source tree.
 */

use crate::{
    completion::on_completion,
    goto_definition::on_goto_definition,
    hover::on_hover,
    lsp::{
        set_actual_server_status, set_starting_status, CompletionOptions, Connection,
        GotoDefinition, HoverRequest, InitializeParams, Message, ServerCapabilities,
        ServerResponse, TextDocumentSyncCapability, TextDocumentSyncKind, WorkDoneProgressOptions,
    },
    lsp_process_error::{LSPProcessError, LSPProcessResult},
    references::on_references,
    shutdown::{on_exit, on_shutdown},
    status_reporting::LSPStatusReporter,
    text_documents::on_did_save_text_document,
    text_documents::{
        on_did_change_text_document, on_did_close_text_document, on_did_open_text_document,
    },
};
use common::{PerfLogEvent, PerfLogger};
use crossbeam::{SendError, Sender};
use log::info;
use lsp_server::{ErrorCode, Notification, ResponseError};
use lsp_types::{
    notification::{
        DidChangeTextDocument, DidCloseTextDocument, DidOpenTextDocument, DidSaveTextDocument, Exit,
    },
    request::{Completion, References, Shutdown},
};
use relay_compiler::config::Config;
use std::sync::{Arc, RwLock};
mod lsp_request_dispatch;
use lsp_request_dispatch::LSPRequestDispatch;
mod lsp_notification_dispatch;
use lsp_notification_dispatch::LSPNotificationDispatch;
mod lsp_state;
pub use lsp_state::LSPExtraDataProvider;
pub(crate) use lsp_state::{LSPState, LSPStateError};

/// Initializes an LSP connection, handling the `initialize` message and `initialized` notification
/// handshake.
pub fn initialize(connection: &Connection) -> LSPProcessResult<InitializeParams> {
    let mut server_capabilities = ServerCapabilities::default();
    // Enable text document syncing so we can know when files are opened/changed/saved/closed
    server_capabilities.text_document_sync =
        Some(TextDocumentSyncCapability::Kind(TextDocumentSyncKind::Full));

    server_capabilities.completion_provider = Some(CompletionOptions {
        resolve_provider: Some(true),
        trigger_characters: Some(vec!["(".into(), "{".into(), "\n".into(), ",".into()]),
        work_done_progress_options: WorkDoneProgressOptions {
            work_done_progress: None,
        },
    });

    server_capabilities.hover_provider = Some(true);
    server_capabilities.definition_provider = Some(true);
    server_capabilities.references_provider = Some(true);

    let server_capabilities = serde_json::to_value(&server_capabilities)?;
    let params = connection.initialize(server_capabilities)?;
    let params: InitializeParams = serde_json::from_value(params)?;
    Ok(params)
}

/// Run the main server loop
pub async fn run<TPerfLogger: PerfLogger + 'static>(
    connection: Connection,
    mut config: Config,
    _params: InitializeParams,
    perf_logger: Arc<TPerfLogger>,
    extra_data_provider: Box<dyn LSPExtraDataProvider + Send + Sync>,
) -> LSPProcessResult<()>
where
    TPerfLogger: PerfLogger + 'static,
{
    info!(
        "Running language server with config root {:?}",
        config.root_dir
    );
    set_starting_status(&connection.sender);

    let lsp_state_errors: Arc<RwLock<Vec<LSPStateError>>> = Default::default();

    config.status_reporter = Box::new(LSPStatusReporter::new(
        config.root_dir.clone(),
        connection.sender.clone(),
        Arc::clone(&lsp_state_errors),
    ));

    let mut lsp_state = LSPState::create_state(
        Arc::new(config),
        lsp_state_errors,
        extra_data_provider,
        Arc::clone(&perf_logger),
    )
    .await?;

    set_actual_server_status(&connection.sender, &lsp_state.get_errors());

    for msg in connection.receiver {
        info!("LSP message received {:?}", msg);
        match msg {
            Message::Request(req) => {
                handle_request(&mut lsp_state, req, &connection.sender, &perf_logger)
                    .map_err(LSPProcessError::from)?;
            }
            Message::Notification(notification) => {
                handle_notification(&mut lsp_state, notification, &perf_logger);
            }
            _ => {
                // Ignore responses for now
            }
        }
    }

    Ok(())
}

fn handle_request<TPerfLogger: PerfLogger + 'static>(
    lsp_state: &mut LSPState<TPerfLogger>,
    request: lsp_server::Request,
    sender: &Sender<Message>,
    perf_logger: &Arc<TPerfLogger>,
) -> Result<(), SendError<Message>> {
    let get_server_response_bound = |req| dispatch_request(req, lsp_state);
    let get_response = with_request_logging(perf_logger, get_server_response_bound);

    sender.send(Message::Response(get_response(request)))
}

fn dispatch_request<TPerfLogger: PerfLogger + 'static>(
    request: lsp_server::Request,
    lsp_state: &mut LSPState<TPerfLogger>,
) -> ServerResponse {
    let get_response = || -> Result<_, ServerResponse> {
        let request = LSPRequestDispatch::new(request, lsp_state)
            .on_request_sync::<HoverRequest>(on_hover)?
            .on_request_sync::<GotoDefinition>(on_goto_definition)?
            .on_request_sync::<References>(on_references)?
            .on_request_sync::<Completion>(on_completion)?
            .on_request_sync::<Shutdown>(on_shutdown)?
            .request();

        // If we have gotten here, we have not handled the request
        Ok(ServerResponse {
            id: request.id,
            result: None,
            error: Some(ResponseError {
                code: ErrorCode::MethodNotFound as i32,
                data: None,
                message: format!("No handler registered for method '{}'", request.method),
            }),
        })
    };
    get_response().unwrap_or_else(|response| response)
}

fn with_request_logging<'a, TPerfLogger: PerfLogger + 'static>(
    perf_logger: &'a Arc<TPerfLogger>,
    get_response: impl FnOnce(lsp_server::Request) -> ServerResponse + 'a,
) -> impl FnOnce(lsp_server::Request) -> ServerResponse + 'a {
    move |request| {
        let lsp_request_event = perf_logger.create_event("lsp_message");
        lsp_request_event.string("lsp_method", request.method.clone());
        lsp_request_event.string("lsp_type", "request".to_string());
        let lsp_request_processing_time = lsp_request_event.start("lsp_message_processing_time");

        let response = get_response(request);

        if response.result.is_some() {
            lsp_request_event.string("lsp_outcome", "success".to_string());
        } else if let Some(error) = &response.error {
            lsp_request_event.string("lsp_outcome", "error".to_string());
            if let Some(data) = &error.data {
                lsp_request_event.string("lsp_error_data", data.to_string());
            }
        }
        // N.B. we don't handle the case where the ServerResponse has neither a result nor
        // an error, which is an invalid state.

        lsp_request_event.stop(lsp_request_processing_time);
        perf_logger.complete_event(lsp_request_event);

        response
    }
}

fn handle_notification<TPerfLogger: PerfLogger + 'static>(
    lsp_state: &mut LSPState<TPerfLogger>,
    notification: Notification,
    perf_logger: &Arc<TPerfLogger>,
) {
    let lsp_notification_event = perf_logger.create_event("lsp_message");
    lsp_notification_event.string("lsp_method", notification.method.clone());
    lsp_notification_event.string("lsp_type", "notification".to_string());
    let lsp_notification_processing_time =
        lsp_notification_event.start("lsp_message_processing_time");

    let notification_result = dispatch_notification(notification, lsp_state);

    // The only possible error (for now) is not handling the notification.
    // N.B. is_ok is correct here.
    if notification_result.is_ok() {
        lsp_notification_event.string("lsp_outcome", "error".to_string());
    }

    lsp_notification_event.stop(lsp_notification_processing_time);
    perf_logger.complete_event(lsp_notification_event);
}

fn dispatch_notification<TPerfLogger: PerfLogger + 'static>(
    notification: lsp_server::Notification,
    lsp_state: &mut LSPState<TPerfLogger>,
) -> Result<(), ()> {
    let notification = LSPNotificationDispatch::new(notification, lsp_state)
        .on_notification_sync::<DidOpenTextDocument>(on_did_open_text_document)?
        .on_notification_sync::<DidCloseTextDocument>(on_did_close_text_document)?
        .on_notification_sync::<DidChangeTextDocument>(on_did_change_text_document)?
        .on_notification_sync::<DidSaveTextDocument>(on_did_save_text_document)?
        .on_notification_sync::<Exit>(on_exit)?
        .notification();

    // If we have gotten here, we have not handled the notification
    info!(
        "Error: no handler registered for notification '{}'",
        notification.method
    );
    Ok(())
}
<|endoftext|>


<commit_before>// Licensed under the Apache License, Version 2.0 <LICENSE-APACHE or
// http://www.apache.org/licenses/LICENSE-2.0> or the MIT license
// <LICENSE-MIT or http://opensource.org/licenses/MIT>, at your
// option. This file may not be copied, modified, or distributed
// except according to those terms.

extern crate commands;
extern crate readline;

use commands::parser::builder::{Command, CommandTree};
use commands::parser::Parser;
use commands::tokenizer::tokenize;
use readline::readline;

use std::ffi::CString;
use std::io::Write;
use std::io;

// This will be a lot nicer once readline 0.0.12 is published
// to crates.io and I can use it.

fn main() {
    let mut tree = CommandTree::new();
    tree.command(Command::new("show").finalize());
    let root = tree.finalize();

    let prompt = CString::new(">> ").unwrap();
    while let Ok(s) = readline(&prompt) {
        if let Ok(tokens) = tokenize(s.to_str().unwrap()) {
            let mut parser = Parser::new(root.clone());
            match parser.parse(tokens) {
                Ok(_) => {}
                Err(_) => panic!(),
            }
        }
        io::stdout().write_all(b"\n").unwrap();
    }
    io::stdout().write_all(b"\nExiting.\n").unwrap();
}
<commit_msg>readline example now reports ParseErrors.<commit_after>// Licensed under the Apache License, Version 2.0 <LICENSE-APACHE or
// http://www.apache.org/licenses/LICENSE-2.0> or the MIT license
// <LICENSE-MIT or http://opensource.org/licenses/MIT>, at your
// option. This file may not be copied, modified, or distributed
// except according to those terms.

extern crate commands;
extern crate readline;

use commands::parser::builder::{Command, CommandTree};
use commands::parser::nodes::Node;
use commands::parser::{ParseError, Parser};
use commands::tokenizer::tokenize;
use readline::readline;

use std::ffi::CString;
use std::io::Write;
use std::io;

// This will be a lot nicer once readline 0.0.12 is published
// to crates.io and I can use it.

fn main() {
    let mut tree = CommandTree::new();
    tree.command(Command::new("show").finalize());
    let root = tree.finalize();

    let prompt = CString::new(">> ").unwrap();
    while let Ok(s) = readline(&prompt) {
        if let Ok(tokens) = tokenize(s.to_str().unwrap()) {
            let mut parser = Parser::new(root.clone());
            match parser.parse(tokens) {
                Ok(_) => {}
                Err(ParseError::NoMatches(_, acceptable)) => {
                    print!("\nPossible options:\n");
                    for ref option in acceptable {
                        print!("  {} - {}\n", option.help_symbol(), option.help_text());
                    }
                }
                Err(ParseError::AmbiguousMatch(_, matches)) => {
                    print!("\nCan be interpreted as:\n");
                    for ref option in matches {
                        print!("  {} - {}\n", option.help_symbol(), option.help_text());
                    }
                }
            }
        }
        io::stdout().write_all(b"\n").unwrap();
    }
    io::stdout().write_all(b"\nExiting.\n").unwrap();
}
<|endoftext|>