diff --git a/.github/workflows/ci.yml b/.github/workflows/ci.yml new file mode 100644 index 0000000..115a1d6 --- /dev/null +++ b/.github/workflows/ci.yml @@ -0,0 +1,46 @@ +name: CI + +on: + push: + branches: [main] + pull_request: + branches: [main] + +env: + CARGO_TERM_COLOR: always + +jobs: + check: + name: Check (${{ matrix.os }}) + runs-on: ${{ matrix.os }} + strategy: + fail-fast: false + matrix: + os: [ubuntu-latest, macos-latest, windows-latest] + + steps: + - uses: actions/checkout@v4 + + - name: Install Rust toolchain + uses: dtolnay/rust-toolchain@stable + with: + components: rustfmt, clippy + + - name: Cache cargo registry and build + uses: actions/cache@v4 + with: + path: | + ~/.cargo/registry + ~/.cargo/git + target + key: ${{ runner.os }}-cargo-${{ hashFiles('**/Cargo.lock') }} + restore-keys: ${{ runner.os }}-cargo- + + - name: Check formatting + run: cargo fmt --check + + - name: Clippy + run: cargo clippy -- -D warnings + + - name: Run tests + run: cargo test diff --git a/Cargo.toml b/Cargo.toml index 1da5e0d..c225f68 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -4,6 +4,12 @@ version = "0.1.0" edition = "2024" description = "CLI for arborist-metrics: cognitive/cyclomatic complexity and SLOC metrics" license = "MIT OR Apache-2.0" +repository = "https://github.com/StrangeDaysTech/arborist-cli" +homepage = "https://github.com/StrangeDaysTech/arborist-cli" +readme = "README.md" +keywords = ["complexity", "metrics", "cognitive", "cyclomatic", "cli"] +categories = ["command-line-utilities", "development-tools"] +exclude = [".devtrail/", ".github/", ".claude/", ".specify/", "specs/", "tests/fixtures/"] [dependencies] arborist-metrics = { version = "0.1", features = ["all"] } diff --git a/tests/cli/directory.rs b/tests/cli/directory.rs index 40910ef..42cada1 100644 --- a/tests/cli/directory.rs +++ b/tests/cli/directory.rs @@ -29,8 +29,14 @@ fn directory_gitignore_excludes() { assert!(output.status.success()); let stdout = String::from_utf8(output.stdout).unwrap(); - assert!(!stdout.contains("generated"), "gitignored file should be excluded"); - assert!(stdout.contains("compute"), "non-ignored files should be included"); + assert!( + !stdout.contains("generated"), + "gitignored file should be excluded" + ); + assert!( + stdout.contains("compute"), + "non-ignored files should be included" + ); } /// T029: verify `--languages rust` analyzes only .rs files, skips .py @@ -44,7 +50,10 @@ fn directory_language_filter() { assert!(output.status.success()); let stdout = String::from_utf8(output.stdout).unwrap(); assert!(stdout.contains("Rust"), "should include Rust files"); - assert!(!stdout.contains("Python"), "should not include Python files"); + assert!( + !stdout.contains("Python"), + "should not include Python files" + ); } /// T030: verify directory with no recognized files prints info and exits 0 @@ -56,7 +65,11 @@ fn directory_no_recognized_files() { // Let's just test that empty results are handled gracefully. // We'll use a path filter that excludes everything. cmd() - .args(["tests/fixtures/nested_project/", "--languages", "javascript"]) + .args([ + "tests/fixtures/nested_project/", + "--languages", + "javascript", + ]) .assert() .success(); } diff --git a/tests/cli/filtering.rs b/tests/cli/filtering.rs index a4f9bc4..8bea90e 100644 --- a/tests/cli/filtering.rs +++ b/tests/cli/filtering.rs @@ -31,8 +31,14 @@ fn threshold_exceeds_only() { .unwrap(); let stdout = String::from_utf8(output.stdout).unwrap(); - assert!(stdout.contains("complex_function"), "exceeding function should appear"); - assert!(!stdout.contains("simple_function"), "non-exceeding function should be filtered"); + assert!( + stdout.contains("complex_function"), + "exceeding function should appear" + ); + assert!( + !stdout.contains("simple_function"), + "non-exceeding function should be filtered" + ); assert!( !stdout.contains("moderate_function"), "non-exceeding function should be filtered" @@ -68,11 +74,7 @@ fn no_methods_flag() { #[test] fn sort_cognitive_descending() { let output = cmd() - .args([ - "tests/fixtures/nested_project/src/", - "--sort", - "cognitive", - ]) + .args(["tests/fixtures/nested_project/src/", "--sort", "cognitive"]) .output() .unwrap(); @@ -101,7 +103,11 @@ fn sort_name_ascending() { assert!(output.status.success()); let stdout = String::from_utf8(output.stdout).unwrap(); let compute_pos = stdout.find("compute").unwrap(); - let main_pos = stdout.find(" main").unwrap_or_else(|| stdout.find("\nmain").unwrap_or_else(|| stdout.find("main").unwrap())); + let main_pos = stdout.find(" main").unwrap_or_else(|| { + stdout + .find("\nmain") + .unwrap_or_else(|| stdout.find("main").unwrap()) + }); let transform_pos = stdout.find("transform").unwrap(); assert!( compute_pos < main_pos && main_pos < transform_pos, diff --git a/tests/cli/output_formats.rs b/tests/cli/output_formats.rs index 953913d..30227c8 100644 --- a/tests/cli/output_formats.rs +++ b/tests/cli/output_formats.rs @@ -73,7 +73,10 @@ fn csv_directory_multiple_rows() { let stdout = String::from_utf8(output.stdout).unwrap(); let lines: Vec<&str> = stdout.lines().collect(); // header + at least 3 functions (compute, transform, main) - assert!(lines.len() >= 4, "expected header + 3+ data rows, got {lines:?}"); + assert!( + lines.len() >= 4, + "expected header + 3+ data rows, got {lines:?}" + ); } /// T058: verify CSV with no-function file produces header only