1
  2
  3
  4
  5
  6
  7
  8
  9
 10
 11
 12
 13
 14
 15
 16
 17
 18
 19
 20
 21
 22
 23
 24
 25
 26
 27
 28
 29
 30
 31
 32
 33
 34
 35
 36
 37
 38
 39
 40
 41
 42
 43
 44
 45
 46
 47
 48
 49
 50
 51
 52
 53
 54
 55
 56
 57
 58
 59
 60
 61
 62
 63
 64
 65
 66
 67
 68
 69
 70
 71
 72
 73
 74
 75
 76
 77
 78
 79
 80
 81
 82
 83
 84
 85
 86
 87
 88
 89
 90
 91
 92
 93
 94
 95
 96
 97
 98
 99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
use crate::prelude::*;
use nu_engine::evaluate_baseline_expr;
use nu_engine::WholeStreamCommand;
use nu_errors::ShellError;
use nu_protocol::{Signature, SyntaxShape, UntaggedValue};
use nu_source::Tagged;
use std::borrow::Borrow;

pub struct Format;

impl WholeStreamCommand for Format {
    fn name(&self) -> &str {
        "format"
    }

    fn signature(&self) -> Signature {
        Signature::build("format").required(
            "pattern",
            SyntaxShape::String,
            "the pattern to output. Eg) \"{foo}: {bar}\"",
        )
    }

    fn usage(&self) -> &str {
        "Format columns into a string using a simple pattern."
    }

    fn run(&self, args: CommandArgs) -> Result<OutputStream, ShellError> {
        format_command(args)
    }

    fn examples(&self) -> Vec<Example> {
        vec![Example {
            description: "Print filenames with their sizes",
            example: "ls | format '{name}: {size}'",
            result: None,
        }]
    }
}

fn format_command(args: CommandArgs) -> Result<OutputStream, ShellError> {
    let ctx = Arc::new(args.context.clone());
    let pattern: Tagged<String> = args.req(0)?;

    let format_pattern = format(&pattern);
    let commands = Arc::new(format_pattern);

    Ok(args
        .input
        .map(move |value| {
            let mut output = String::new();
            let commands = commands.clone();
            let ctx = ctx.clone();

            for command in &*commands {
                match command {
                    FormatCommand::Text(s) => {
                        output.push_str(s);
                    }
                    FormatCommand::Column(c) => {
                        // FIXME: use the correct spans
                        let full_column_path = nu_parser::parse_full_column_path(
                            &(c.to_string()).spanned(Span::unknown()),
                            &ctx.scope,
                        );

                        ctx.scope.enter_scope();
                        ctx.scope.add_var("$it", value.clone());
                        let result = evaluate_baseline_expr(&full_column_path.0, &*ctx);
                        ctx.scope.exit_scope();

                        if let Ok(c) = result {
                            output.push_str(&value::format_leaf(c.borrow()).plain_string(100_000))
                        } else {
                            // That column doesn't match, so don't emit anything
                        }
                    }
                }
            }

            Ok(UntaggedValue::string(output).into_untagged_value())
        })
        .into_input_stream())
}

#[derive(Debug)]
enum FormatCommand {
    Text(String),
    Column(String),
}

fn format(input: &str) -> Vec<FormatCommand> {
    let mut output = vec![];

    let mut loop_input = input.chars();
    loop {
        let mut before = String::new();

        for c in &mut loop_input {
            if c == '{' {
                break;
            }
            before.push(c);
        }

        if !before.is_empty() {
            output.push(FormatCommand::Text(before.to_string()));
        }
        // Look for column as we're now at one
        let mut column = String::new();

        for c in &mut loop_input {
            if c == '}' {
                break;
            }
            column.push(c);
        }

        if !column.is_empty() {
            output.push(FormatCommand::Column(column.to_string()));
        }

        if before.is_empty() && column.is_empty() {
            break;
        }
    }

    output
}

#[cfg(test)]
mod tests {
    use super::Format;
    use super::ShellError;

    #[test]
    fn examples_work_as_expected() -> Result<(), ShellError> {
        use crate::examples::test as test_examples;

        test_examples(Format {})
    }
}