Skip to content
Open
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
44 changes: 44 additions & 0 deletions .github/workflows/claude-code-review.yml
Original file line number Diff line number Diff line change
@@ -0,0 +1,44 @@
name: Claude Code Review

on:
pull_request:
types: [opened, synchronize, ready_for_review, reopened]
# Optional: Only run on specific file changes
# paths:
# - "src/**/*.ts"
# - "src/**/*.tsx"
# - "src/**/*.js"
# - "src/**/*.jsx"

jobs:
claude-review:
# Optional: Filter by PR author
# if: |
# github.event.pull_request.user.login == 'external-contributor' ||
# github.event.pull_request.user.login == 'new-developer' ||
# github.event.pull_request.author_association == 'FIRST_TIME_CONTRIBUTOR'

runs-on: ubuntu-latest
permissions:
contents: read
pull-requests: read
issues: read
id-token: write

steps:
- name: Checkout repository
uses: actions/checkout@v4
with:
fetch-depth: 1

- name: Run Claude Code Review
id: claude-review
uses: anthropics/claude-code-action@v1
with:
anthropic_api_key: ${{ secrets.ANTHROPIC_API_KEY }}
plugin_marketplaces: 'https://github.com/anthropics/claude-code.git'
plugins: 'code-review@claude-code-plugins'
prompt: '/code-review:code-review ${{ github.repository }}/pull/${{ github.event.pull_request.number }}'
# See https://github.com/anthropics/claude-code-action/blob/main/docs/usage.md
# or https://code.claude.com/docs/en/cli-reference for available options

50 changes: 50 additions & 0 deletions .github/workflows/claude.yml
Original file line number Diff line number Diff line change
@@ -0,0 +1,50 @@
name: Claude Code

on:
issue_comment:
types: [created]
pull_request_review_comment:
types: [created]
issues:
types: [opened, assigned]
pull_request_review:
types: [submitted]

jobs:
claude:
if: |
(github.event_name == 'issue_comment' && contains(github.event.comment.body, '@claude')) ||
(github.event_name == 'pull_request_review_comment' && contains(github.event.comment.body, '@claude')) ||
(github.event_name == 'pull_request_review' && contains(github.event.review.body, '@claude')) ||
(github.event_name == 'issues' && (contains(github.event.issue.body, '@claude') || contains(github.event.issue.title, '@claude')))
runs-on: ubuntu-latest
permissions:
contents: read
pull-requests: read
issues: read
id-token: write
actions: read # Required for Claude to read CI results on PRs
steps:
- name: Checkout repository
uses: actions/checkout@v4
with:
fetch-depth: 1

- name: Run Claude Code
id: claude
uses: anthropics/claude-code-action@v1
with:
anthropic_api_key: ${{ secrets.ANTHROPIC_API_KEY }}

# This is an optional setting that allows Claude to read CI results on PRs
additional_permissions: |
actions: read

# Optional: Give a custom prompt to Claude. If this is not specified, Claude will perform the instructions specified in the comment that tagged it.
# prompt: 'Update the pull request description to include a summary of changes.'

# Optional: Add claude_args to customize behavior and configuration
# See https://github.com/anthropics/claude-code-action/blob/main/docs/usage.md
# or https://code.claude.com/docs/en/cli-reference for available options
# claude_args: '--allowed-tools Bash(gh pr:*)'

1 change: 1 addition & 0 deletions build.gradle
Original file line number Diff line number Diff line change
Expand Up @@ -30,6 +30,7 @@ distributions {
dependencies {
implementation("org.apache.poi:poi-ooxml:3.12")
implementation("org.apache.odftoolkit:simple-odf:0.8.2-incubating")
implementation("org.apache.commons:commons-io:1.3.2")
implementation("com.google.code.findbugs:jsr305:3.0.2")
}

Expand Down
10 changes: 7 additions & 3 deletions src/main/java/com/ka/spreadsheet/diff/Flags.java
Original file line number Diff line number Diff line change
Expand Up @@ -15,14 +15,17 @@ public class Flags {
public static Double DIFF_NUMERIC_PRECISION;
public static boolean DIFF_IGNORE_FORMULAS;
public static DiffFormatter DIFF_FORMAT;
public static String LOG_FILENAME;
public static File WORKBOOK1;
public static File WORKBOOK2;
public static WorkbookIgnores WORKBOOK_IGNORES1;
public static WorkbookIgnores WORKBOOK_IGNORES2;

public enum DiffFormatter {
EXCEL_CMP,
UNIFIED
UNIFIED,
LOGGER,
LOGGER_UNIFIED
}

public static boolean parseFlags(String[] args) {
Expand Down Expand Up @@ -58,8 +61,9 @@ public static boolean parseFlags(String[] args) {
System.out.println(usage());
return false;
}
WORKBOOK1 = new File(args[0]);
WORKBOOK2 = new File(args[1]);
LOG_FILENAME = args[0];
WORKBOOK1 = new File(args[1]);
WORKBOOK2 = new File(args[2]);
WORKBOOK_IGNORES1 = WorkbookIgnores.parseWorkbookIgnores(args, "--ignore1");
WORKBOOK_IGNORES2 = WorkbookIgnores.parseWorkbookIgnores(args, "--ignore2");
return true;
Expand Down
Original file line number Diff line number Diff line change
@@ -0,0 +1,97 @@
package com.ka.spreadsheet.diff;


import org.apache.log4j.LogManager;
import org.apache.log4j.Logger;

import java.util.LinkedHashSet;
import java.util.Set;

public class LoggerSpreadSheetDiffCallback extends SpreadSheetDiffCallbackBase {
private static final Logger cleanLogger = LogManager.getLogger("DisplayLogger");
private final Set<Object> sheets = new LinkedHashSet<Object>();
private final Set<Object> rows = new LinkedHashSet<Object>();
private final Set<Object> cols = new LinkedHashSet<Object>();
private final Set<Object> macros = new LinkedHashSet<Object>();

private final Set<Object> sheets1 = new LinkedHashSet<Object>();
private final Set<Object> rows1 = new LinkedHashSet<Object>();
private final Set<Object> cols1 = new LinkedHashSet<Object>();

private final Set<Object> sheets2 = new LinkedHashSet<Object>();
private final Set<Object> rows2 = new LinkedHashSet<Object>();
private final Set<Object> cols2 = new LinkedHashSet<Object>();

private final Set<Object> macros1 = new LinkedHashSet<Object>();
private final Set<Object> macros2 = new LinkedHashSet<Object>();

private String file1;
private String file2;

@Override
public void init(String file1, String file2) {
super.init(file1, file2);
this.file1 = file1;
this.file2 = file2;
}

@Override
public void reportWorkbooksDiffer(boolean differ) {
super.reportWorkbooksDiffer(differ);
reportSummary("DIFF", sheets, rows, cols, macros);
reportSummary("EXTRA WB1", sheets1, rows1, cols1, macros1);
reportSummary("EXTRA WB2", sheets2, rows2, cols2, macros2);
cleanLogger.info("-----------------------------------------");
cleanLogger.info("Excel files " + file1 + " and " + file2 + " "
+ (differ ? "differ" : "match"));
}

@Override
public void reportMacroOnlyIn(boolean inFirstSpreadSheet) {
super.reportMacroOnlyIn(inFirstSpreadSheet);
String name = "unknown";
(inFirstSpreadSheet ? macros1 : macros2).add(name);
cleanLogger.info("EXTRA macro name: " + name + " found only in " + wb(inFirstSpreadSheet));
}

@Override
public void reportExtraCell(boolean inFirstSpreadSheet, CellPos c) {
super.reportExtraCell(inFirstSpreadSheet, c);
if (inFirstSpreadSheet) {
sheets1.add(c.getSheetName());
rows1.add(c.getRow());
cols1.add(c.getColumn());
} else {
sheets2.add(c.getSheetName());
rows2.add(c.getRow());
cols2.add(c.getColumn());
}
cleanLogger.info("EXTRA Cell in " + wb(inFirstSpreadSheet) + " " + c.getCellPosition()
+ " => '" + c.getCellValue() + "'");
}

@Override
public void reportDiffCell(CellPos c1, CellPos c2) {
super.reportDiffCell(c1, c2);
sheets.add(c1.getSheetName());
rows.add(c1.getRow());
cols.add(c1.getColumn());
cleanLogger.info("DIFF Cell at " + c1.getCellPosition() + " => '" + c1.getCellValue()
+ "' v/s '" + c2.getCellValue() + "'");
}

private void reportSummary(String what, Set<Object> sheets, Set<Object> rows, Set<Object> cols,
Set<Object> macros) {
cleanLogger.info("----------------- " + what + " -------------------");
cleanLogger.info("Sheets: " + sheets);
cleanLogger.info("Rows: " + rows);
cleanLogger.info("Cols: " + cols);
if (!macros.isEmpty()) {
cleanLogger.info("Macros: " + macros);
}
}

private String wb(boolean inFirstSpreadSheet) {
return inFirstSpreadSheet ? "WB1" : "WB2";
}
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,156 @@
package com.ka.spreadsheet.diff;


import org.apache.log4j.LogManager;
import org.apache.log4j.Logger;

import java.util.ArrayList;
import java.util.List;

import static com.ka.spreadsheet.diff.SpreadSheetUtils.CELL_INTERNAL_TO_USER;

// Output format, inspired by the traditional Unified Diff format (but using cell references instead of line numbers):
// +--- column 1
// V
// --- file_1_name!sheet_1_name
// +++ file_2_name!sheet_1_name
// @@ cell_1_ref,cell_n_ref cell_1_ref,cell_n_ref @@
// -old_cell_1_data
// -...
// -old_cell_n_data
// +new_cell_1_data
// +...
// +new_cell_n_data
// @@@ ...
// ...
// --- file_1_name!sheet_2_name
// +++ file_2_name!sheet_2_name
// ...
// Each cell data line is always present, even if the data is empty (thus just "-" or "+).
public class LoggerUnifiedDiffSpreadSheetDiffCallback extends SpreadSheetDiffCallbackBase {
private static final Logger cleanLogger = LogManager.getLogger("DisplayLogger");
private final String lineSeparator = System.getProperty("line.separator");
private String file1;
private String file2;
private DiffCell prevDiffCell = new DiffCell("", -2, -2, null, null);
private List<DiffCell> currentCellBlock = new ArrayList<DiffCell>();

@Override
public void init(String file1, String file2) {
super.init(file1, file2);
this.file1 = file1;
this.file2 = file2;
}

@Override
public void finish() {
super.finish();
printAndEmptyCellBlock();
}

@Override
public void reportMacroOnlyIn(boolean inFirstSpreadSheet) {
super.reportMacroOnlyIn(inFirstSpreadSheet);
cleanLogger.info("Unified diff format does not support macros, however WB" + (inFirstSpreadSheet ? "1" : "2") + " contains at least one macro that is not in the other workbook.");
}

@Override
public void reportExtraCell(boolean inFirstSpreadSheet, CellPos c) {
super.reportExtraCell(inFirstSpreadSheet, c);
accumulateAndMaybePrint(new DiffCell(
c.getSheetName(),
c.getRowIndex(),
c.getColumnIndex(),
(inFirstSpreadSheet ? c.getCellValue() : null),
(!inFirstSpreadSheet ? c.getCellValue() : null)
));
}

@Override
public void reportDiffCell(CellPos c1, CellPos c2) {
super.reportDiffCell(c1, c2);
accumulateAndMaybePrint(new DiffCell(
c1.getSheetName(),
c1.getRowIndex(),
c1.getColumnIndex(),
c1.getCellValue(),
c2.getCellValue()
));
}

private void accumulateAndMaybePrint(DiffCell diffCell) {
if (!isSameSheet(prevDiffCell, diffCell)) {
printAndEmptyCellBlock();
cleanLogger.info("--- " + file1 + "!" + diffCell.sheetName);
cleanLogger.info("+++ " + file2 + "!" + diffCell.sheetName);
}
if (!isSameRow(prevDiffCell, diffCell)) {
printAndEmptyCellBlock();
}
if (!isSameCellBlock(prevDiffCell, diffCell)) {
printAndEmptyCellBlock();
}
currentCellBlock.add(diffCell);
prevDiffCell = diffCell;
}

private boolean isSameSheet(DiffCell c1, DiffCell c2) {
return c1.sheetName.equals(c2.sheetName);
}

private boolean isSameRow(DiffCell c1, DiffCell c2) {
return c1.rowIndex == c2.rowIndex;
}

private boolean isSameCellBlock(DiffCell c1, DiffCell c2) {
return c1.colIndex == (c2.colIndex - 1);
}

// TODO: Make this handle multiple rows, maybe. What would that output look like? This might be a bad idea.
private void printAndEmptyCellBlock() {
if (currentCellBlock.size() > 0) {
StringBuilder sheet1Lines = new StringBuilder();
StringBuilder sheet2Lines = new StringBuilder();
String cellRange = CELL_INTERNAL_TO_USER(currentCellBlock.get(0).rowIndex, currentCellBlock.get(0).colIndex);
if (currentCellBlock.size() > 1) {
cellRange = cellRange + "," +
CELL_INTERNAL_TO_USER(currentCellBlock.get(0).rowIndex, currentCellBlock.get(currentCellBlock.size()-1).colIndex);
}
cleanLogger.info("@@ -" + cellRange + " +" + cellRange + " @@");
int prevCol = -1;
for (DiffCell rowCell : currentCellBlock) {
assert currentCellBlock.get(0).rowIndex == rowCell.rowIndex : "printAndEmptyCellBlock() only supports one row at a time.";
assert prevCol == -1 || prevCol == (rowCell.colIndex -1 ) : "printAndEmptyCellBlock() only supports a contiguous range of cells at a time.";
sheet1Lines.append("-");
sheet2Lines.append("+");
if (rowCell.c1Value != null) {
sheet1Lines.append(rowCell.c1Value);
}
if (rowCell.c2Value != null) {
sheet2Lines.append(rowCell.c2Value);
}
sheet1Lines.append(lineSeparator);
sheet2Lines.append(lineSeparator);
}
cleanLogger.info(sheet1Lines.toString());
cleanLogger.info(sheet2Lines.toString());
}
currentCellBlock = new ArrayList<DiffCell>();
}

private class DiffCell {
String sheetName;
int rowIndex;
int colIndex;
CellValue c1Value;
CellValue c2Value;

public DiffCell(String _sheetName, int _rowIndex, int _colIndex, CellValue _c1Value, CellValue _c2Value) {
sheetName = _sheetName;
rowIndex = _rowIndex;
colIndex = _colIndex;
c1Value = _c1Value;
c2Value = _c2Value;
}
}
}
Loading