Przeglądaj źródła

feat(json): starting to parse json as per asked in #768

MartinFillon 2 lat temu
rodzic
commit
18cace298e
6 zmienionych plików z 125 dodań i 1 usunięć
  1. 36 0
      src/main.rs
  2. 3 1
      src/options/flags.rs
  3. 12 0
      src/options/view.rs
  4. 58 0
      src/output/details.rs
  5. 15 0
      src/output/lines.rs
  6. 1 0
      src/output/mod.rs

+ 36 - 0
src/main.rs

@@ -396,6 +396,8 @@ impl<'args> Exa<'args> {
             ..
         } = self.options.view;
 
+        debug!("matching on mode {:?}, {:?}", mode, self.console_width);
+
         match (mode, self.console_width) {
             (Mode::Grid(ref opts), Some(console_width)) => {
                 let filter = &self.options.filter;
@@ -490,6 +492,40 @@ impl<'args> Exa<'args> {
                 };
                 r.render(&mut self.writer)
             }
+
+            (Mode::Json(ref opts), _) => match opts {
+                Some(o) => {
+                    let filter = &self.options.filter;
+                    let recurse = self.options.dir_action.recurse_options();
+                    let git_ignoring = self.options.filter.git_ignore == GitIgnore::CheckAndIgnore;
+                    let git = self.git.as_ref();
+                    let git_repos = self.git_repos;
+
+                    let r = details::Render {
+                        dir,
+                        files,
+                        theme,
+                        file_style,
+                        opts: o,
+                        recurse,
+                        filter,
+                        git_ignoring,
+                        git,
+                        git_repos,
+                    };
+                    r.render_as_json(&mut self.writer)
+                }
+                None => {
+                    let filter = &self.options.filter;
+                    let r = lines::Render {
+                        files,
+                        theme,
+                        file_style,
+                        filter,
+                    };
+                    r.render_as_json(&mut self.writer)
+                }
+            },
         }
     }
 }

+ 3 - 1
src/options/flags.rs

@@ -1,3 +1,4 @@
+#![cfg_attr(rustfmt, rustfmt_skip)]
 use crate::options::parser::{Arg, Args, TakesValue, Values};
 
 // exa options
@@ -6,6 +7,7 @@ pub static HELP:    Arg = Arg { short: Some(b'?'), long: "help",     takes_value
 
 // display options
 pub static ONE_LINE:    Arg = Arg { short: Some(b'1'), long: "oneline",     takes_value: TakesValue::Forbidden };
+pub static JSON:        Arg = Arg { short: None,       long: "json",        takes_value: TakesValue::Forbidden };
 pub static LONG:        Arg = Arg { short: Some(b'l'), long: "long",        takes_value: TakesValue::Forbidden };
 pub static GRID:        Arg = Arg { short: Some(b'G'), long: "grid",        takes_value: TakesValue::Forbidden };
 pub static ACROSS:      Arg = Arg { short: Some(b'x'), long: "across",      takes_value: TakesValue::Forbidden };
@@ -88,7 +90,7 @@ pub static FILE_FLAGS:        Arg = Arg { short: Some(b'O'), long: "flags",
 pub static ALL_ARGS: Args = Args(&[
     &VERSION, &HELP,
 
-    &ONE_LINE, &LONG, &GRID, &ACROSS, &RECURSE, &TREE, &CLASSIFY, &DEREF_LINKS,
+    &ONE_LINE, &JSON, &LONG, &GRID, &ACROSS, &RECURSE, &TREE, &CLASSIFY, &DEREF_LINKS,
     &COLOR, &COLOUR, &COLOR_SCALE, &COLOUR_SCALE, &COLOR_SCALE_MODE, &COLOUR_SCALE_MODE,
     &WIDTH, &NO_QUOTES, &ABSOLUTE,
 

+ 12 - 0
src/options/view.rs

@@ -44,6 +44,7 @@ impl Mode {
                 || f.matches(&flags::ONE_LINE)
                 || f.matches(&flags::GRID)
                 || f.matches(&flags::TREE)
+                || f.matches(&flags::JSON)
         });
 
         let Some(flag) = flag else {
@@ -89,6 +90,17 @@ impl Mode {
             return Ok(Self::Lines);
         }
 
+        if flag.matches(&flags::JSON) && !flag.matches(&flags::ONE_LINE) {
+            let _ = matches.has(&flags::JSON)?;
+            let details = details::Options::deduce_long(matches, vars)?;
+            return Ok(Self::Json(Some(details)));
+        }
+
+        if flag.matches(&flags::JSON) && flag.matches(&flags::ONE_LINE) {
+            let _ = matches.has(&flags::JSON)?;
+            return Ok(Self::Json(None));
+        }
+
         let grid = grid::Options::deduce(matches)?;
         Ok(Self::Grid(grid))
     }

+ 58 - 0
src/output/details.rs

@@ -222,6 +222,64 @@ impl<'a> Render<'a> {
         Ok(())
     }
 
+    pub fn render_as_json<W: Write>(mut self, w: &mut W) -> io::Result<()> {
+        let n_cpus = match num_cpus::get() as u32 {
+            0 => 1,
+            n => n,
+        };
+        let mut pool = Pool::new(n_cpus);
+        let mut rows = Vec::new();
+
+        if let Some(ref options) = self.opts.table {
+            let mut table = Table::new(options, self.git, self.theme, self.git_repos);
+
+            if self.opts.header {
+                let header = table.header_row();
+                table.add_widths(&header);
+            }
+
+            // This is weird, but I can’t find a way around it:
+            // https://internals.rust-lang.org/t/should-option-mut-t-implement-copy/3715/6
+            let mut table = Some(table);
+            self.add_files_to_table(
+                &mut pool,
+                &mut table,
+                &mut rows,
+                &self.files,
+                TreeDepth::root(),
+                None,
+            );
+
+            write!(w, "{}", "{\"files\":[")?;
+            for (i, row) in self.iterate_with_table(table.unwrap(), rows).enumerate() {
+                write!(w, "\"{}\"", row.strings())?;
+                if (i + 1) < self.files.len() {
+                    write!(w, ", ")?;
+                }
+            }
+            write!(w, "{}", "]}}\n")?;
+        } else {
+            self.add_files_to_table(
+                &mut pool,
+                &mut None,
+                &mut rows,
+                &self.files,
+                TreeDepth::root(),
+                None,
+            );
+
+            write!(w, "{}", "{\"files\":[")?;
+            for (i, row) in self.iterate(rows).enumerate() {
+                write!(w, "\"{}\"", row.strings())?;
+                if (i + 1) < self.files.len() {
+                    write!(w, ", ")?;
+                }
+            }
+            write!(w, "{}", "]}}\n")?;
+        }
+        Ok(())
+    }
+
     /// Whether to show the extended attribute hint
     pub fn show_xattr_hint(&self, file: &File<'_>) -> bool {
         // Do not show the hint '@' if the only extended attribute is the security

+ 15 - 0
src/output/lines.rs

@@ -34,4 +34,19 @@ impl<'a> Render<'a> {
             .with_mount_details(false)
             .paint()
     }
+
+    pub fn render_as_json<W: Write>(mut self, w: &mut W) -> io::Result<()> {
+        self.filter.sort_files(&mut self.files);
+        write!(w, "{}", "{\"files\":[")?;
+        for (i, file) in self.files.iter().enumerate() {
+            let name_cell = self.render_file(file);
+            write!(w, "\"{}\"", ANSIStrings(&name_cell))?;
+            if (i + 1) < self.files.len() {
+                write!(w, "{}", ",")?;
+            }
+        }
+        write!(w, "{}", "]}\n")?;
+
+        Ok(())
+    }
 }

+ 1 - 0
src/output/mod.rs

@@ -35,6 +35,7 @@ pub enum Mode {
     Details(details::Options),
     GridDetails(grid_details::Options),
     Lines,
+    Json(Option<details::Options>),
 }
 
 /// The width of the terminal requested by the user.