Skip to content

Commit

Permalink
Printable Recipe Datatables (openrewrite#4087)
Browse files Browse the repository at this point in the history
* Added method to export datatable information as csv to RecipeRun class

* Wrapped column value in quotes to escape commas in value

* Refactored streams to iterative loops. Log exceptions through Execution Context error handler

* Added formatCsv method. Added logic to ensure directories exist before creating files

* Refactored export logic to be more testable. Added test for csv printing logic
  • Loading branch information
ryan-hudson authored and U808771 committed Mar 14, 2024
1 parent 1970a34 commit aff81a8
Show file tree
Hide file tree
Showing 2 changed files with 107 additions and 0 deletions.
75 changes: 75 additions & 0 deletions rewrite-core/src/main/java/org/openrewrite/RecipeRun.java
Original file line number Diff line number Diff line change
Expand Up @@ -17,12 +17,25 @@

import lombok.Value;
import lombok.With;
import org.openrewrite.config.ColumnDescriptor;
import org.openrewrite.config.DataTableDescriptor;
import org.openrewrite.internal.lang.Nullable;

import java.io.File;
import java.io.FileNotFoundException;
import java.io.FileOutputStream;
import java.io.IOException;
import java.io.PrintWriter;
import java.lang.reflect.Field;
import java.nio.file.Files;
import java.nio.file.Path;
import java.util.ArrayList;
import java.util.List;
import java.util.Map;
import java.util.function.Consumer;

import static java.util.Collections.emptyList;
import static org.openrewrite.internal.RecipeIntrospectionUtils.dataTableDescriptorFromDataTable;

@Value
public class RecipeRun {
Expand Down Expand Up @@ -53,4 +66,66 @@ public <E> List<E> getDataTableRows(String name) {
}
return emptyList();
}

public void exportDatatablesToCsv(Path filePath, ExecutionContext ctx) {
try {
Files.createDirectories(filePath);
} catch (IOException e) {
ctx.getOnError().accept(e);
}
for (Map.Entry<DataTable<?>, List<?>> entry : dataTables.entrySet()) {
DataTable<?> dataTable = entry.getKey();
List<?> rows = entry.getValue();
File csv = filePath.resolve(dataTable.getName() + ".csv").toFile();
try (PrintWriter printWriter = new PrintWriter(new FileOutputStream(csv, false))) {
exportCsv(ctx, dataTable, printWriter::println, rows);
} catch (FileNotFoundException e) {
ctx.getOnError().accept(e);
}
}
}

public static void exportCsv(final ExecutionContext ctx, final DataTable<?> dataTable, final Consumer<String> output,
final List<?> rows) {
DataTableDescriptor descriptor = dataTableDescriptorFromDataTable(dataTable);
List<String> fieldNames = new ArrayList<>();
List<String> fieldTitles = new ArrayList<>();
List<String> fieldDescriptions = new ArrayList<>();

for (ColumnDescriptor columnDescriptor : descriptor.getColumns()) {
fieldNames.add(columnDescriptor.getName());
fieldTitles.add(formatForCsv(columnDescriptor.getDisplayName()));
fieldDescriptions.add(formatForCsv(columnDescriptor.getDescription()));
}

output.accept(String.join(",", fieldTitles));
output.accept(String.join(",", fieldDescriptions));
exportRowData(output, rows, fieldNames, ctx);
}

private static void exportRowData(Consumer<String> output, List<?> rows, List<String> fieldNames,
ExecutionContext ctx) {
for (Object row : rows) {
List<String> rowValues = new ArrayList<>();
for (String fieldName : fieldNames) {
try {
Field field = row.getClass().getDeclaredField(fieldName);
field.setAccessible(true);
//Assume every column value is printable with toString
rowValues.add(formatForCsv(field.get(row).toString()));
} catch (NoSuchFieldException | IllegalAccessException e) {
ctx.getOnError().accept(e);
}
}
output.accept(String.join(",", rowValues));
}
}

private static String formatForCsv(@Nullable String data) {
if (data != null) {
return String.format("\"%s\"", data.replace("\"", "\"\""));
} else {
return "\"\"";
}
}
}
32 changes: 32 additions & 0 deletions rewrite-core/src/test/java/org/openrewrite/RecipeRunTest.java
Original file line number Diff line number Diff line change
@@ -0,0 +1,32 @@
package org.openrewrite;

import org.junit.jupiter.api.Test;
import org.openrewrite.table.SourcesFileResults;
import org.openrewrite.test.RewriteTest;
import org.openrewrite.text.FindAndReplace;

import static org.assertj.core.api.Assertions.assertThat;
import static org.openrewrite.test.SourceSpecs.text;

public class RecipeRunTest implements RewriteTest {
@Test
void printDatatable() {
rewriteRun(
recipeSpec -> recipeSpec.recipe(new FindAndReplace("replace_me", "replacement", null, null, null, null, null))
.afterRecipe(recipeRun -> {
StringBuilder output = new StringBuilder();
final String dataTableName = SourcesFileResults.class.getName();
RecipeRun.exportCsv(new InMemoryExecutionContext(), recipeRun.getDataTable(dataTableName),
s -> output.append(s).append("\n"), recipeRun.getDataTableRows(dataTableName));
assertThat(output.toString()).isEqualTo("""
"Source path before the run","Source path after the run","Parent of the recipe that made changes","Recipe that made changes","Estimated time saving","Cycle"
"The source path of the file before the run.","A recipe may modify the source path. This is the path after the run.","In a hierarchical recipe, the parent of the recipe that made a change. Empty if this is the root of a hierarchy or if the recipe is not hierarchical at all.","The specific recipe that made a change.","An estimated effort that a developer to fix manually instead of using this recipe, in unit of seconds.","The recipe cycle in which the change was made."
"file.txt","file.txt","","org.openrewrite.text.FindAndReplace","300","1"
""");
}), text("""
replace_me
""", """
replacement
"""));
}
}

0 comments on commit aff81a8

Please sign in to comment.