Skip to content
Merged
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
2,603 changes: 2,285 additions & 318 deletions Cargo.lock

Large diffs are not rendered by default.

1 change: 1 addition & 0 deletions Cargo.toml
Original file line number Diff line number Diff line change
Expand Up @@ -20,6 +20,7 @@ ignore = "0.4"
thiserror = "2"
comfy-table = "7"
csv = "1"
self_update = { version = "0.43", features = ["archive-tar", "archive-zip", "compression-flate2"] }

[dev-dependencies]
assert_cmd = "2"
Expand Down
8 changes: 4 additions & 4 deletions src/analysis.rs
Original file line number Diff line number Diff line change
Expand Up @@ -3,11 +3,11 @@ use std::path::{Path, PathBuf};

use arborist::{AnalysisConfig, FileReport, Language};

use crate::cli::CliArgs;
use crate::cli::AnalyzeArgs;
use crate::error::ArboristError;
use crate::traversal;

pub fn build_config(args: &CliArgs) -> AnalysisConfig {
pub fn build_config(args: &AnalyzeArgs) -> AnalysisConfig {
AnalysisConfig {
cognitive_threshold: args.threshold,
include_methods: !args.no_methods,
Expand Down Expand Up @@ -36,7 +36,7 @@ pub fn analyze_stdin(language: &str, config: &AnalysisConfig) -> Result<FileRepo
pub fn analyze_paths(
paths: &[PathBuf],
config: &AnalysisConfig,
args: &CliArgs,
args: &AnalyzeArgs,
) -> Result<(Vec<FileReport>, Vec<String>), ArboristError> {
let files = traversal::collect_files(paths, args.languages.as_deref(), args.gitignore)?;

Expand All @@ -53,7 +53,7 @@ pub fn analyze_paths(
Ok((reports, errors))
}

pub fn apply_filters(reports: &[FileReport], args: &CliArgs) -> (Vec<FileReport>, bool) {
pub fn apply_filters(reports: &[FileReport], args: &AnalyzeArgs) -> (Vec<FileReport>, bool) {
let mut threshold_exceeded = false;
let mut filtered: Vec<FileReport> = Vec::new();

Expand Down
23 changes: 21 additions & 2 deletions src/cli.rs
Original file line number Diff line number Diff line change
@@ -1,6 +1,6 @@
use std::path::PathBuf;

use clap::{Parser, ValueEnum};
use clap::{Parser, Subcommand, ValueEnum};

#[derive(Debug, Clone, ValueEnum)]
pub enum OutputFormat {
Expand All @@ -23,7 +23,26 @@ pub enum SortMetric {
version,
about = "Code complexity metrics powered by arborist-metrics"
)]
pub struct CliArgs {
pub struct Cli {
#[command(subcommand)]
pub command: Option<Command>,

#[command(flatten)]
pub analyze: AnalyzeArgs,
}

#[derive(Debug, Subcommand)]
pub enum Command {
/// Check for updates and install the latest version
Update {
/// Only check for available updates without installing
#[arg(long)]
check: bool,
},
}

#[derive(Debug, clap::Args)]
pub struct AnalyzeArgs {
/// Files or directories to analyze
#[arg()]
pub paths: Vec<PathBuf>,
Expand Down
5 changes: 3 additions & 2 deletions src/lib.rs
Original file line number Diff line number Diff line change
Expand Up @@ -3,11 +3,12 @@ pub mod cli;
pub mod error;
pub mod output;
pub mod traversal;
pub mod update;

use cli::CliArgs;
use cli::AnalyzeArgs;
use error::{ArboristError, ExitReport};

pub fn run(args: &CliArgs) -> Result<ExitReport, ArboristError> {
pub fn run(args: &AnalyzeArgs) -> Result<ExitReport, ArboristError> {
let is_stdin = args.paths.is_empty() && atty::is(atty::Stream::Stdin).not_tty();

if is_stdin {
Expand Down
19 changes: 11 additions & 8 deletions src/main.rs
Original file line number Diff line number Diff line change
Expand Up @@ -2,16 +2,19 @@ use std::process::ExitCode;

use clap::Parser;

use arborist_cli::cli::CliArgs;
use arborist_cli::cli::{Cli, Command};

fn main() -> ExitCode {
let args = CliArgs::parse();
let cli = Cli::parse();

match arborist_cli::run(&args) {
Ok(report) => report.exit_code(),
Err(err) => {
eprintln!("error: {err}");
ExitCode::from(2)
}
match cli.command {
Some(Command::Update { check }) => arborist_cli::update::run(check),
None => match arborist_cli::run(&cli.analyze) {
Ok(report) => report.exit_code(),
Err(err) => {
eprintln!("error: {err}");
ExitCode::from(2)
}
},
}
}
4 changes: 2 additions & 2 deletions src/output/mod.rs
Original file line number Diff line number Diff line change
Expand Up @@ -5,12 +5,12 @@ pub mod table;
use arborist::FileReport;

use crate::analysis;
use crate::cli::{CliArgs, OutputFormat};
use crate::cli::{AnalyzeArgs, OutputFormat};
use crate::error::ArboristError;

pub fn write_output(
reports: &[FileReport],
args: &CliArgs,
args: &AnalyzeArgs,
_threshold_exceeded: bool,
) -> Result<(), ArboristError> {
let use_flat_mode = args.sort.is_some() || args.top.is_some();
Expand Down
6 changes: 3 additions & 3 deletions src/output/table.rs
Original file line number Diff line number Diff line change
Expand Up @@ -4,10 +4,10 @@ use arborist::FileReport;
use comfy_table::{ContentArrangement, Table};

use crate::analysis::FlatFunction;
use crate::cli::CliArgs;
use crate::cli::AnalyzeArgs;
use crate::error::ArboristError;

pub fn write_reports(reports: &[FileReport], args: &CliArgs) -> Result<(), ArboristError> {
pub fn write_reports(reports: &[FileReport], args: &AnalyzeArgs) -> Result<(), ArboristError> {
let stdout = io::stdout();
let mut out = stdout.lock();
let is_tty = stdout.is_terminal();
Expand Down Expand Up @@ -71,7 +71,7 @@ pub fn write_reports(reports: &[FileReport], args: &CliArgs) -> Result<(), Arbor
Ok(())
}

pub fn write_flat(flat: &[FlatFunction], args: &CliArgs) -> Result<(), ArboristError> {
pub fn write_flat(flat: &[FlatFunction], args: &AnalyzeArgs) -> Result<(), ArboristError> {
let stdout = io::stdout();
let mut out = stdout.lock();
let is_tty = stdout.is_terminal();
Expand Down
105 changes: 105 additions & 0 deletions src/update.rs
Original file line number Diff line number Diff line change
@@ -0,0 +1,105 @@
use std::process::ExitCode;

const CURRENT_VERSION: &str = env!("CARGO_PKG_VERSION");
const REPO_OWNER: &str = "StrangeDaysTech";
const REPO_NAME: &str = "arborist-cli";

pub fn run(check_only: bool) -> ExitCode {
println!("arborist-cli v{CURRENT_VERSION}");

if is_cargo_installed() {
eprintln!(
"hint: this binary appears to be installed via cargo. \
To update, run: cargo install arborist-cli"
);
if check_only {
return check_latest_version();
}
return ExitCode::SUCCESS;
}

if check_only {
return check_latest_version();
}

perform_update()
}

fn check_latest_version() -> ExitCode {
print!("Checking for updates... ");

match self_update::backends::github::Update::configure()
.repo_owner(REPO_OWNER)
.repo_name(REPO_NAME)
.bin_name("arborist-cli")
.current_version(CURRENT_VERSION)
.build()
{
Ok(updater) => match updater.get_latest_release() {
Ok(release) => {
let latest = &release.version;
if latest == CURRENT_VERSION {
println!("already up to date.");
ExitCode::SUCCESS
} else {
println!("v{latest} available (current: v{CURRENT_VERSION})");
println!("Run `arborist update` to install it.");
ExitCode::SUCCESS
}
}
Err(e) => {
eprintln!("failed to check for updates: {e}");
ExitCode::from(2)
}
},
Err(e) => {
eprintln!("failed to configure updater: {e}");
ExitCode::from(2)
}
}
}

fn perform_update() -> ExitCode {
println!("Checking for updates...");

let result = self_update::backends::github::Update::configure()
.repo_owner(REPO_OWNER)
.repo_name(REPO_NAME)
.bin_name("arborist-cli")
.current_version(CURRENT_VERSION)
.show_download_progress(true)
.show_output(false)
.no_confirm(true)
.build();

match result {
Ok(updater) => match updater.update() {
Ok(status) => {
if status.updated() {
println!("Updated to v{}.", status.version());
} else {
println!("Already up to date (v{CURRENT_VERSION}).");
}
ExitCode::SUCCESS
}
Err(e) => {
eprintln!("error: update failed: {e}");
ExitCode::from(2)
}
},
Err(e) => {
eprintln!("error: failed to configure updater: {e}");
ExitCode::from(2)
}
}
}

/// Heuristic: if the binary is inside a cargo bin directory, it was likely
/// installed via `cargo install`.
fn is_cargo_installed() -> bool {
let Ok(exe) = std::env::current_exe() else {
return false;
};
let path = exe.to_string_lossy();
path.contains(".cargo/bin") || path.contains(".cargo\\bin")
}
Loading