1 use ag::pipeline::{OutputMode, Pipeline, QueryContainer, TermErrorReporter};
2 use human_panic::setup_panic;
3 use quicli::prelude::*;
4
5 #[cfg(feature = "self_update")]
6 use self_update;
7 use std::fs::File;
8 use std::io;
9 use std::io::{stdout, BufReader};
10 use structopt::StructOpt;
11
12 #[global_allocator]
13 static GLOBAL: jemallocator::Jemalloc = jemallocator::Jemalloc;
14
15 use crate::InvalidArgs::{CantSupplyBoth, InvalidFormatString, InvalidOutputMode};
16 use structopt::clap::ArgGroup;
17
18 // Needed to require either "--self-update" or a query
main_arg_group() -> ArgGroup<'static>19 fn main_arg_group() -> ArgGroup<'static> {
20 ArgGroup::with_name("main").required(true)
21 }
22
23 #[derive(Debug, StructOpt)]
24 #[structopt(
25 after_help = "For more details + docs, see https://github.com/rcoh/angle-grinder",
26 raw(group = "main_arg_group()")
27 )]
28 struct Cli {
29 /// The query
30 #[structopt(group = "main")]
31 query: Option<String>,
32
33 #[cfg(feature = "self_update")]
34 /// Update agrind to the latest published version Github (https://github.com/rcoh/angle-grinder)
35 #[structopt(long = "self-update", group = "main")]
36 update: bool,
37
38 /// Optionally reads from a file instead of Stdin
39 #[structopt(long = "file", short = "f")]
40 file: Option<String>,
41
42 /// DEPRECATED. Use -o format=... instead. Provide a Rust std::fmt string to format output
43 #[structopt(long = "format", short = "m")]
44 format: Option<String>,
45
46 /// Set output format. One of (json|legacy|format=<rust fmt str>|logfmt)
47 #[structopt(
48 long = "output",
49 short = "o",
50 long_help = "Set output format. Options: \n\
51 - `json`,\n\
52 - `logfmt`\n\
53 - `format=<rust format string>` (eg. -o format='{src} => {dst}'\n\
54 - `legacy` The original output format, auto aligning [k=v]"
55 )]
56 output: Option<String>,
57
58 #[structopt(flatten)]
59 verbosity: Verbosity,
60 }
61
62 #[derive(Debug, Fail)]
63 pub enum InvalidArgs {
64 #[fail(display = "Query was missing. Usage: `agrind 'query'`")]
65 MissingQuery,
66
67 #[fail(display = "Invalid output mode {}. Valid choices: {}", choice, choices)]
68 InvalidOutputMode { choice: String, choices: String },
69
70 #[fail(
71 display = "Invalid format string. Expected something like `-o format='{{src}} => {{dst}}'`"
72 )]
73 InvalidFormatString,
74
75 #[fail(display = "Can't supply a format string and an output mode")]
76 CantSupplyBoth,
77 }
78
main() -> CliResult79 fn main() -> CliResult {
80 setup_panic!();
81 let args = Cli::from_args();
82 #[cfg(feature = "self_update")]
83 if args.update {
84 return update();
85 }
86 let query = QueryContainer::new(
87 args.query.ok_or(InvalidArgs::MissingQuery)?,
88 Box::new(TermErrorReporter {}),
89 );
90 args.verbosity.setup_env_logger("agrind")?;
91 let output_mode = match (args.output, args.format) {
92 (Some(_output), Some(_format)) => Err(CantSupplyBoth),
93 (Some(output), None) => parse_output(&output),
94 (None, Some(format)) => Ok(OutputMode::Format(format)),
95 (None, None) => parse_output("legacy"),
96 }?;
97 let pipeline = Pipeline::new(&query, stdout(), output_mode)?;
98 match args.file {
99 Some(file_name) => {
100 let f = File::open(file_name)?;
101 pipeline.process(BufReader::new(f))
102 }
103 None => {
104 let stdin = io::stdin();
105 let locked = stdin.lock();
106 pipeline.process(locked)
107 }
108 };
109 Ok(())
110 }
111
parse_output(output_param: &str) -> Result<OutputMode, InvalidArgs>112 fn parse_output(output_param: &str) -> Result<OutputMode, InvalidArgs> {
113 // for some args, we split on `=` first
114 let (arg, val) = match output_param.find('=') {
115 None => (output_param, "="),
116 Some(idx) => output_param.split_at(idx),
117 };
118 let val = &val[1..];
119
120 match (arg, val) {
121 ("legacy", "") => Ok(OutputMode::Legacy),
122 ("json", "") => Ok(OutputMode::Json),
123 ("logfmt", "") => Ok(OutputMode::Logfmt),
124 ("format", v) if !v.is_empty() => Ok(OutputMode::Format(v.to_owned())),
125 ("format", "") => Err(InvalidFormatString),
126 (other, _v) => Err(InvalidOutputMode {
127 choice: other.to_owned(),
128 choices: "legacy, json, logfmt, format".to_owned(),
129 }),
130 }
131 }
132
133 #[cfg(feature = "self_update")]
update() -> CliResult134 fn update() -> CliResult {
135 let crate_version = self_update::cargo_crate_version!();
136 let status = self_update::backends::github::Update::configure()
137 .repo_owner("rcoh")
138 .repo_name("angle-grinder")
139 .bin_name("agrind")
140 .show_download_progress(true)
141 .current_version(crate_version)
142 .build()?
143 .update()?;
144
145 if crate_version == status.version() {
146 println!(
147 "Currently running the latest version publicly available ({}). No changes",
148 status.version()
149 );
150 } else {
151 println!("Updated to version: {}", status.version());
152 }
153 Ok(())
154 }
155