Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Draft: Add a --json flag to output json. #1099

Draft
wants to merge 9 commits into
base: master
Choose a base branch
from
Draft
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
1 change: 1 addition & 0 deletions .envrc
Original file line number Diff line number Diff line change
@@ -0,0 +1 @@
use flake
1 change: 1 addition & 0 deletions .gitignore
Original file line number Diff line number Diff line change
@@ -1,3 +1,4 @@
/target
out.md
**/*.rs.bk
.direnv/
14 changes: 14 additions & 0 deletions Cargo.lock

Some generated files are not rendered by default. Learn more about how customized files appear on GitHub.

3 changes: 2 additions & 1 deletion Cargo.toml
Original file line number Diff line number Diff line change
Expand Up @@ -33,7 +33,7 @@ terminal_size = "0.3"
thiserror = "1.0"
sys-locale = "0.3"
once_cell = "1.17.1"
chrono = { version = "0.4.19", features = ["unstable-locales"] }
chrono = { version = "0.4.19", features = ["serde", "unstable-locales"] }
chrono-humanize = "0.2"
# incompatible with v0.1.11
unicode-width = "0.1.13"
Expand All @@ -46,6 +46,7 @@ serde_yaml = "0.9"
url = "2.1"
vsort = "0.2"
xdg = "2.5"
serde_json = "1.0.128"

[target."cfg(not(all(windows, target_arch = \"x86\", target_env = \"gnu\")))".dependencies]
# if ssl feature is enabled compilation will fail on arm-unknown-linux-gnueabihf and i686-pc-windows-gnu
Expand Down
57 changes: 57 additions & 0 deletions flake.lock

Some generated files are not rendered by default. Learn more about how customized files appear on GitHub.

38 changes: 38 additions & 0 deletions flake.nix
Original file line number Diff line number Diff line change
@@ -0,0 +1,38 @@
{
inputs = {
utils.url = "github:numtide/flake-utils";
};
outputs = { self, nixpkgs, utils }: utils.lib.eachDefaultSystem (system:
let
pkgs = nixpkgs.legacyPackages.${system};
lsd = pkgs.rustPlatform.buildRustPackage {
pname = "lsd";
version = "1.1.5";

src = ./.;

cargoHash = "sha256-chryC4YDvd8c7fIiHMWi+g5JYZJqkLPknSCgzYVKucE=";
doCheck = false;

nativeBuildInputs = [ pkgs.git ];

#meta = with pkgs.lib; {
# description = "A fast line-oriented regex search tool, similar to ag and ack";
# homepage = "https://github.com/BurntSushi/ripgrep";
# license = licenses.unlicense;
# maintainers = [];
#};
};
in {
devShell = pkgs.mkShell {
buildInputs = with pkgs; [
cargo
rustc
rust-analyzer
rustfmt
];
};
packages.default = lsd;
}
);
}
4 changes: 4 additions & 0 deletions src/app.rs
Original file line number Diff line number Diff line change
Expand Up @@ -60,6 +60,10 @@ pub struct Cli {
#[arg(long)]
pub tree: bool,

/// Print the output as json
#[arg(long)]
pub json: bool,

/// Stop recursing into directories after reaching specified depth
#[arg(long, value_name = "NUM")]
pub depth: Option<usize>,
Expand Down
20 changes: 13 additions & 7 deletions src/core.rs
Original file line number Diff line number Diff line change
Expand Up @@ -72,7 +72,7 @@ impl Core {
// or require a raw output (like the `wc` command).
if !tty_available {
// we should not overwrite the tree layout
if flags.layout != Layout::Tree {
if flags.layout != Layout::Tree && flags.layout != Layout::Json {
flags.layout = Layout::OneLine;
}

Expand Down Expand Up @@ -174,22 +174,28 @@ impl Core {
}

fn display(&self, metas: &[Meta]) {
let output = if self.flags.layout == Layout::Tree {
display::tree(
let output = match self.flags.layout {
Layout::Tree => display::tree(
metas,
&self.flags,
&self.colors,
&self.icons,
&self.git_theme,
)
} else {
display::grid(
),
Layout::Json => display::json(
metas,
&self.flags,
&self.colors,
&self.icons,
&self.git_theme,
)
),
_ => display::grid(
metas,
&self.flags,
&self.colors,
&self.icons,
&self.git_theme,
),
};

print_output!("{}", output);
Expand Down
93 changes: 92 additions & 1 deletion src/display.rs
Original file line number Diff line number Diff line change
Expand Up @@ -4,7 +4,9 @@ use crate::flags::{Display, Flags, HyperlinkOption, Layout};
use crate::git_theme::GitTheme;
use crate::icon::Icons;
use crate::meta::name::DisplayOption;
use crate::meta::{FileType, Meta, OwnerCache};
use crate::meta::{Date, FileType, Meta, OwnerCache};
use chrono::{DateTime, Local};
use serde::Serialize;
use std::collections::HashMap;
use term_grid::{Cell, Direction, Filling, Grid, GridOptions};
use terminal_size::terminal_size;
Expand All @@ -15,6 +17,95 @@ const LINE: &str = "\u{2502} "; // "│ "
const CORNER: &str = "\u{2514}\u{2500}\u{2500}"; // "└──"
const BLANK: &str = " ";

#[derive(Serialize)]
struct JsonMeta {
name: String,
display: String,
r#type: String,
extension: Option<String>,
dirlike: bool,
content: Option<Vec<JsonMeta>>,
date: Option<DateTime<Local>>,
icon: String,
permissions: String,
size: Option<u64>,
user: Option<String>,
group: Option<String>,
path: String,
}

impl JsonMeta {
fn from_meta(value: &Meta, icons: &Icons, colors: &Colors, flags: &Flags) -> JsonMeta {
let name = &value.name;
let icon = icons.get(&name);
let display = name.render(
colors,
icons,
&DisplayOption::FileName,
flags.hyperlink,
false,
);
let permissions = value
.permissions_or_attributes
.as_ref()
.unwrap()
.render(colors, flags)
.to_string();
let size = value.size.as_ref().map(|size| size.get_bytes());
let user = value.owner.as_ref().map(|owner| {
owner
.render_user(colors, &OwnerCache::default(), flags)
.to_string()
});
let group = value.owner.as_ref().map(|owner| {
owner
.render_group(colors, &OwnerCache::default(), flags)
.to_string()
});
let path = value.path.to_str().unwrap().to_string();

JsonMeta {
content: value.content.as_ref().map(|content| {
content
.iter()
.map(|meta| JsonMeta::from_meta(meta, icons, colors, flags))
.collect()
}),
name: name.name.clone(),
display: display.to_string(),
r#type: name.file_type().render(colors).to_string(),
extension: name.extension().map(String::from),
date: value.date.as_ref().and_then(|date| match date {
&Date::Date(date) => Some(date.clone()),
&Date::Invalid => None,
}),
dirlike: value.file_type.is_dirlike(),
permissions,
icon,
size,
user,
group,
path,
}
}
}

pub fn json(
metas: &[Meta],
flags: &Flags,
colors: &Colors,
icons: &Icons,
_git_theme: &GitTheme,
) -> String {
serde_json::to_string(
&metas
.into_iter()
.map(|meta| JsonMeta::from_meta(meta, icons, colors, flags))
.collect::<Vec<JsonMeta>>(),
)
.unwrap()
}

pub fn grid(
metas: &[Meta],
flags: &Flags,
Expand Down
5 changes: 4 additions & 1 deletion src/flags/layout.rs
Original file line number Diff line number Diff line change
Expand Up @@ -16,6 +16,7 @@ pub enum Layout {
Grid,
Tree,
OneLine,
Json,
}

impl Configurable<Layout> for Layout {
Expand All @@ -26,7 +27,9 @@ impl Configurable<Layout> for Layout {
/// arguments is greater than 1, this also returns the [OneLine](Layout::OneLine) variant.
/// Finally if neither of them is passed, this returns [None].
fn from_cli(cli: &Cli) -> Option<Self> {
if cli.tree {
if cli.json {
Some(Self::Json)
} else if cli.tree {
Some(Self::Tree)
} else if cli.long || cli.oneline || cli.inode || cli.context || cli.blocks.len() > 1
// TODO: handle this differently
Expand Down
4 changes: 3 additions & 1 deletion src/git.rs
Original file line number Diff line number Diff line change
@@ -1,8 +1,10 @@
use serde::Serialize;

use crate::meta::git_file_status::GitFileStatus;
use std::path::{Path, PathBuf};

#[allow(dead_code)]
#[derive(Copy, Clone, Debug, PartialEq, Eq, Hash, PartialOrd, Ord, Default)]
#[derive(Copy, Clone, Debug, PartialEq, Eq, Hash, PartialOrd, Ord, Default, Serialize)]
pub enum GitStatus {
/// No status info
#[default]
Expand Down
4 changes: 3 additions & 1 deletion src/meta/access_control.rs
Original file line number Diff line number Diff line change
@@ -1,7 +1,9 @@
use serde::Serialize;

use crate::color::{ColoredString, Colors, Elem};
use std::path::Path;

#[derive(Clone, Debug)]
#[derive(Clone, Debug, Serialize)]
pub struct AccessControl {
has_acl: bool,
selinux_context: String,
Expand Down
3 changes: 2 additions & 1 deletion src/meta/date.rs
Original file line number Diff line number Diff line change
Expand Up @@ -3,11 +3,12 @@ use crate::color::{ColoredString, Colors, Elem};
use crate::flags::{DateFlag, Flags};
use chrono::{DateTime, Duration, Local};
use chrono_humanize::HumanTime;
use serde::Serialize;
use std::fs::Metadata;
use std::panic;
use std::time::SystemTime;

#[derive(Clone, Debug, PartialEq, Eq, PartialOrd, Ord)]
#[derive(Clone, Debug, PartialEq, Eq, PartialOrd, Ord, Serialize)]
pub enum Date {
Date(DateTime<Local>),
Invalid,
Expand Down
4 changes: 3 additions & 1 deletion src/meta/filetype.rs
Original file line number Diff line number Diff line change
@@ -1,7 +1,9 @@
use serde::Serialize;

use crate::color::{ColoredString, Colors, Elem};
use std::fs::Metadata;

#[derive(Debug, PartialEq, Eq, Copy, Clone)]
#[derive(Debug, PartialEq, Eq, Copy, Clone, Serialize)]
#[cfg_attr(windows, allow(dead_code))]
pub enum FileType {
BlockDevice,
Expand Down
4 changes: 3 additions & 1 deletion src/meta/git_file_status.rs
Original file line number Diff line number Diff line change
@@ -1,8 +1,10 @@
use serde::Serialize;

use crate::color::{self, ColoredString, Colors};
use crate::git::GitStatus;
use crate::git_theme::GitTheme;

#[derive(Copy, Clone, Debug, PartialEq, Eq, Hash, PartialOrd, Ord)]
#[derive(Copy, Clone, Debug, PartialEq, Eq, Hash, PartialOrd, Ord, Serialize)]
pub struct GitFileStatus {
pub index: GitStatus,
pub workdir: GitStatus,
Expand Down
Loading