diff --git a/Cargo.lock b/Cargo.lock index fd715053bb7c..28bd065d45fb 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -629,6 +629,43 @@ dependencies = [ "prql-compiler", ] +[[package]] +name = "concolor" +version = "0.1.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "0b946244a988c390a94667ae0e3958411fa40cc46ea496a929b263d883f5f9c3" +dependencies = [ + "bitflags", + "concolor-override", + "concolor-query", + "is-terminal", +] + +[[package]] +name = "concolor-clap" +version = "0.1.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "435ff0007a3bb04099fe1beedc6b76e7dd5340c90b168008ac0d7e87441de1bf" +dependencies = [ + "clap 4.1.10", + "concolor", +] + +[[package]] +name = "concolor-override" +version = "1.0.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "a855d4a1978dc52fb0536a04d384c2c0c1aa273597f08b77c8c4d3b2eec6037f" + +[[package]] +name = "concolor-query" +version = "0.3.3" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "88d11d52c3d7ca2e6d0040212be9e4dbbcd78b6447f535b6b561f449427944cf" +dependencies = [ + "windows-sys 0.45.0", +] + [[package]] name = "console" version = "0.15.5" @@ -2159,6 +2196,8 @@ dependencies = [ "clap 4.1.10", "clio", "color-eyre", + "concolor", + "concolor-clap", "env_logger", "insta", "itertools", diff --git a/prql-compiler/prqlc/Cargo.toml b/prql-compiler/prqlc/Cargo.toml index 8365cac5d3d8..64a10631512d 100644 --- a/prql-compiler/prqlc/Cargo.toml +++ b/prql-compiler/prqlc/Cargo.toml @@ -15,11 +15,13 @@ atty = "0.2.14" clap = {version = "4.1.1", features = ["derive"]} clio = {version = "0.2.4", features = ['clap-parse']} color-eyre = "0.6.1" +concolor = "0.1.0" +concolor-clap = {version = "0.1.0", features = ["api"]} env_logger = {version = "0.10.0", features = ["color"]} itertools = "0.10.3" minijinja = {version = "0.30.4", features = ["unstable_machinery"]} notify = "^5.1.0" -prql-compiler = {path = '..', version = "0.6.1" } +prql-compiler = {path = '..', version = "0.6.1"} regex = {version = "1.7.1", features = ["std", "unicode"]} serde = "^1" serde_json = "1.0.81" diff --git a/prql-compiler/prqlc/src/cli.rs b/prql-compiler/prqlc/src/cli.rs index 2480901d1e04..f1571e80e9cc 100644 --- a/prql-compiler/prqlc/src/cli.rs +++ b/prql-compiler/prqlc/src/cli.rs @@ -19,6 +19,7 @@ pub fn main() -> color_eyre::eyre::Result<()> { env_logger::builder().format_timestamp(None).init(); color_eyre::install()?; let mut cli = Cli::parse(); + cli.color.apply(); if let Err(error) = cli.command.run() { eprintln!("{error}"); @@ -29,9 +30,12 @@ pub fn main() -> color_eyre::eyre::Result<()> { } #[derive(Parser, Debug, Clone)] +#[clap(color = concolor_clap::color_choice())] struct Cli { #[command(subcommand)] command: Command, + #[command(flatten)] + color: concolor_clap::Color, } #[derive(Subcommand, Debug, Clone)] @@ -39,10 +43,8 @@ struct Cli { enum Command { /// Parse into PL AST Parse { - #[clap(value_parser, default_value = "-")] - input: Input, - #[clap(value_parser, default_value = "-")] - output: Output, + #[clap(flatten)] + io_args: IoArgs, #[arg(value_enum, long, default_value = "yaml")] format: Format, }, @@ -59,16 +61,19 @@ enum Command { /// Parse, resolve & lower into RQ Resolve { - #[clap(value_parser, default_value = "-")] - input: Input, - #[clap(value_parser, default_value = "-")] - output: Output, + #[clap(flatten)] + io_args: IoArgs, #[arg(value_enum, long, default_value = "yaml")] format: Format, }, /// Parse, resolve, lower into RQ & compile to SQL - Compile(IoArgs), + Compile { + #[clap(flatten)] + io_args: IoArgs, + #[arg(long, default_value = "true")] + include_signature_comment: bool, + }, /// Watch a directory and compile .prql files to .sql files Watch(watch::WatchArgs), @@ -96,11 +101,10 @@ fn is_stdin(input: &Input) -> bool { impl Command { /// Entrypoint called by [`main`] pub fn run(&mut self) -> Result<()> { - if let Command::Watch(command) = self { - return watch::run(command); - }; - - self.run_io_command() + match self { + Command::Watch(command) => watch::run(command), + _ => self.run_io_command(), + } } fn run_io_command(&mut self) -> std::result::Result<(), anyhow::Error> { @@ -113,7 +117,15 @@ impl Command { self.write_output(&buf)?; } Err(e) => { - print!("{:}", downcast(e).composed(&source_id, &source, true)); + print!( + "{:}", + // TODO: we're repeating this for `Compile`; can we consolidate? + downcast(e).composed( + &source_id, + &source, + concolor::get(concolor::Stream::Stdout).ansi_color() + ) + ); std::process::exit(1) } } @@ -167,33 +179,46 @@ impl Command { Format::Yaml => serde_yaml::to_string(&ir)?.into_bytes(), } } - // TODO: Allow passing the `Options` to the CLI; map those through. - // We already do this in Watch. - Command::Compile(_) => compile(source, &Options::default())?.as_bytes().to_vec(), + Command::Compile { + include_signature_comment, + .. + } => compile( + source, + // I'm guessing it's too "clever" to use `Options` directly in + // the Compile enum variant, and avoid this boilerplate? Would + // reduce this code somewhat. + &Options::default() + .with_color(concolor::get(concolor::Stream::Stdout).ansi_color()) + .with_signature_comment(*include_signature_comment), + )? + .as_bytes() + .to_vec(), Command::Watch(_) => unreachable!(), }) } fn read_input(&mut self) -> Result<(String, String)> { - // TODO: possibly this should be called by the relevant subcommands - // passing in `input`, rather than matching on them and grabbing `input` - // from `self`. + // Possibly this should be called by the relevant subcommands passing in + // `input`, rather than matching on them and grabbing `input` from + // `self`? But possibly if everything moves to `io_args`, then this is + // quite reasonable? use Command::*; let mut input = match self { - Parse { input, .. } | Resolve { input, .. } => input.clone(), - Format(io) | Debug(io) | Annotate(io) | Compile(io) => io.input.clone(), + Parse { io_args, .. } | Resolve { io_args, .. } | Compile { io_args, .. } => { + io_args.input.clone() + } + Format(io) | Debug(io) | Annotate(io) => io.input.clone(), Watch(_) => unreachable!(), }; // Don't wait without a prompt when running `prqlc compile` — // it's confusing whether it's waiting for input or not. This // offers the prompt. if is_stdin(&input) && atty::is(atty::Stream::Stdin) { - println!("Enter PRQL, then ctrl-d:"); - println!(); + println!("Enter PRQL, then ctrl-d:\n"); } let mut source = String::new(); - (input).read_to_string(&mut source)?; + input.read_to_string(&mut source)?; let source_id = (input.path()).to_str().unwrap().to_string(); Ok((source, source_id)) } @@ -201,8 +226,10 @@ impl Command { fn write_output(&mut self, data: &[u8]) -> std::io::Result<()> { use Command::*; let mut output = match self { - Parse { output, .. } | Resolve { output, .. } => output.to_owned(), - Format(io) | Debug(io) | Annotate(io) | Compile(io) => io.output.to_owned(), + Parse { io_args, .. } | Resolve { io_args, .. } | Compile { io_args, .. } => { + io_args.output.to_owned() + } + Format(io) | Debug(io) | Annotate(io) => io.output.to_owned(), Watch(_) => unreachable!(), }; output.write_all(data) @@ -321,7 +348,13 @@ group a_column (take 10 | sort b_column | derive [the_number = rank, last = lag fn compile() { // Check we get an error on a bad input let input = "asdf"; - let result = Command::execute(&Command::Compile(IoArgs::default()), input); + let result = Command::execute( + &Command::Compile { + io_args: IoArgs::default(), + include_signature_comment: true, + }, + input, + ); assert_display_snapshot!(result.unwrap_err(), @r###" Error: ╭─[:1:1] @@ -337,8 +370,7 @@ group a_column (take 10 | sort b_column | derive [the_number = rank, last = lag fn parse() { let output = Command::execute( &Command::Parse { - input: IoArgs::default().input, - output: IoArgs::default().output, + io_args: IoArgs::default(), format: Format::Yaml, }, "from x | select y", @@ -369,8 +401,7 @@ group a_column (take 10 | sort b_column | derive [the_number = rank, last = lag fn resolve() { let output = Command::execute( &Command::Resolve { - input: IoArgs::default().input, - output: IoArgs::default().output, + io_args: IoArgs::default(), format: Format::Yaml, }, "from x | select y", diff --git a/prql-compiler/src/lib.rs b/prql-compiler/src/lib.rs index 7eabba01d4d5..2c922e87119d 100644 --- a/prql-compiler/src/lib.rs +++ b/prql-compiler/src/lib.rs @@ -211,6 +211,11 @@ impl Options { self } + pub fn with_signature_comment(mut self, signature_comment: bool) -> Self { + self.signature_comment = signature_comment; + self + } + pub fn no_signature(mut self) -> Self { self.signature_comment = false; self