├── settings.gradle ├── src ├── main │ ├── groovy │ │ └── fuzzycsv │ │ │ ├── lombok.config │ │ │ ├── GridOptions.java │ │ │ ├── rdbms │ │ │ ├── stmt │ │ │ │ ├── SqlDialect.groovy │ │ │ │ ├── SqlRenderer.groovy │ │ │ │ ├── DumbH2Renderer.groovy │ │ │ │ ├── MySqlRenderer.groovy │ │ │ │ └── DefaultSqlRenderer.groovy │ │ │ ├── DbExportFlags.groovy │ │ │ ├── ExportParams.groovy │ │ │ ├── DDLUtils.java │ │ │ ├── DbColumnSync.groovy │ │ │ ├── FuzzyCsvDbInserter.groovy │ │ │ └── FuzzyCSVDbExporter.groovy │ │ │ ├── javaly │ │ │ ├── Fx1.java │ │ │ ├── Fx2.java │ │ │ ├── Fx3.java │ │ │ ├── VoidFx1.java │ │ │ ├── Dynamic.java │ │ │ ├── FxUtils.java │ │ │ └── Numb.java │ │ │ ├── Aggregator.groovy │ │ │ ├── ResolutionStrategy.groovy │ │ │ ├── ConcatMethod.java │ │ │ ├── DataAction.groovy │ │ │ ├── SpreadConfig.java │ │ │ ├── FuzzyCsvException.java │ │ │ ├── Reducer.groovy │ │ │ ├── nav │ │ │ ├── MutableNav.groovy │ │ │ ├── ExIterator.groovy │ │ │ └── Navigator.groovy │ │ │ ├── DataActionStep.java │ │ │ ├── Sum.groovy │ │ │ ├── AbstractAggregator.groovy │ │ │ ├── TableIterator.groovy │ │ │ ├── IgnoreNewLineCSVWriter.groovy │ │ │ ├── Fuzzy.groovy │ │ │ ├── FuzzyCsvExtensionMethods.java │ │ │ ├── Count.groovy │ │ │ ├── FuzzyStaticApi.groovy │ │ │ ├── Sort.java │ │ │ ├── CSVToExcel.groovy │ │ │ ├── RecordFx.groovy │ │ │ ├── FastIndexOfList.groovy │ │ │ ├── Excel2Csv.groovy │ │ │ ├── FuzzyCSVUtils.java │ │ │ ├── Converter.java │ │ │ ├── Importer.java │ │ │ └── Exporter.java │ └── resources │ │ └── META-INF │ │ └── services │ │ └── org.codehaus.groovy.runtime.ExtensionModule └── test │ ├── resources │ ├── sample_file.xlsx │ └── log4j.xml │ └── groovy │ └── fuzzycsv │ ├── javaly │ ├── JFixtures.groovy │ ├── DynamicTest.java │ ├── TestUtils.java │ └── NumbTest.java │ ├── IgnoreNewLineCSVWriterTest.groovy │ ├── FuzzyCSVUtilsTest.groovy │ ├── FuzzyTableImportTest.groovy │ ├── Excel2CsvTest.groovy │ ├── CountTest.groovy │ ├── Data.groovy │ ├── H2DbHelper.groovy │ ├── SumTest.groovy │ ├── FuzzyCsvExtensionMethodsTest.groovy │ ├── rdbms │ ├── FuzzyCsvInserterTest.groovy │ └── DbColumnSyncTest.groovy │ ├── RecordTest.groovy │ ├── ArrayListTest.groovy │ ├── NavigatorTest.groovy │ ├── FxExtensionsTest.groovy │ └── ConverterTest.java ├── gradle └── wrapper │ ├── gradle-wrapper.jar │ └── gradle-wrapper.properties ├── .sdkmanrc ├── .gitignore ├── circle.yml ├── .github └── workflows │ └── gradle.yml ├── Makefile ├── Releasing.txt ├── gradle.properties ├── gradlew.bat ├── release.sh ├── gradlew └── License /settings.gradle: -------------------------------------------------------------------------------- 1 | rootProject.name = 'fuzzycsv' 2 | -------------------------------------------------------------------------------- /src/main/groovy/fuzzycsv/lombok.config: -------------------------------------------------------------------------------- 1 | lombok.accessors.chain=true 2 | lombok.accessors.fluent=false 3 | -------------------------------------------------------------------------------- /gradle/wrapper/gradle-wrapper.jar: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/kayr/fuzzy-csv/HEAD/gradle/wrapper/gradle-wrapper.jar -------------------------------------------------------------------------------- /src/test/resources/sample_file.xlsx: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/kayr/fuzzy-csv/HEAD/src/test/resources/sample_file.xlsx -------------------------------------------------------------------------------- /.sdkmanrc: -------------------------------------------------------------------------------- 1 | # Enable auto-env through the sdkman_auto_env config 2 | # Add key=value pairs of SDKs to use below 3 | java=11.0.20-tem 4 | -------------------------------------------------------------------------------- /src/main/groovy/fuzzycsv/GridOptions.java: -------------------------------------------------------------------------------- 1 | package fuzzycsv; 2 | 3 | public enum GridOptions { 4 | LIST_AS_TABLE, SHALLOW_MODE 5 | } 6 | -------------------------------------------------------------------------------- /src/main/groovy/fuzzycsv/rdbms/stmt/SqlDialect.groovy: -------------------------------------------------------------------------------- 1 | package fuzzycsv.rdbms.stmt 2 | 3 | enum SqlDialect { 4 | MYSQL, H2, DEFAULT 5 | } -------------------------------------------------------------------------------- /src/main/groovy/fuzzycsv/javaly/Fx1.java: -------------------------------------------------------------------------------- 1 | package fuzzycsv.javaly; 2 | 3 | public interface Fx1 { 4 | R call(T arg) throws Exception; 5 | } 6 | -------------------------------------------------------------------------------- /src/main/groovy/fuzzycsv/javaly/Fx2.java: -------------------------------------------------------------------------------- 1 | package fuzzycsv.javaly; 2 | 3 | public interface Fx2 { 4 | R call(T1 arg1, T2 arg2) throws Exception; 5 | } 6 | -------------------------------------------------------------------------------- /src/main/groovy/fuzzycsv/javaly/Fx3.java: -------------------------------------------------------------------------------- 1 | package fuzzycsv.javaly; 2 | 3 | public interface Fx3 { 4 | R call(T1 arg1, T2 arg2, T3 arg3) throws Exception; 5 | } 6 | -------------------------------------------------------------------------------- /src/main/resources/META-INF/services/org.codehaus.groovy.runtime.ExtensionModule: -------------------------------------------------------------------------------- 1 | moduleName=FuzzyCsvExtensions 2 | moduleVersion=1.0.0 3 | extensionClasses=fuzzycsv.FuzzyCsvExtensionMethods -------------------------------------------------------------------------------- /src/main/groovy/fuzzycsv/javaly/VoidFx1.java: -------------------------------------------------------------------------------- 1 | package fuzzycsv.javaly; 2 | 3 | import fuzzycsv.nav.Navigator; 4 | 5 | public interface VoidFx1 { 6 | void call(R f) throws Exception; 7 | } 8 | -------------------------------------------------------------------------------- /gradle/wrapper/gradle-wrapper.properties: -------------------------------------------------------------------------------- 1 | distributionBase=GRADLE_USER_HOME 2 | distributionPath=wrapper/dists 3 | distributionUrl=https\://services.gradle.org/distributions/gradle-7.3-bin.zip 4 | zipStoreBase=GRADLE_USER_HOME 5 | zipStorePath=wrapper/dists 6 | -------------------------------------------------------------------------------- /src/main/groovy/fuzzycsv/Aggregator.groovy: -------------------------------------------------------------------------------- 1 | package fuzzycsv 2 | 3 | 4 | interface Aggregator { 5 | 6 | void setData(List data) 7 | 8 | T getValue() 9 | 10 | String getColumnName() 11 | 12 | void setColumnName(String name) 13 | 14 | Aggregator az(String name) 15 | 16 | } 17 | -------------------------------------------------------------------------------- /src/main/groovy/fuzzycsv/ResolutionStrategy.groovy: -------------------------------------------------------------------------------- 1 | package fuzzycsv 2 | 3 | enum ResolutionStrategy { 4 | LEFT_FIRST, RIGHT_FIRST, FINAL_FIRST 5 | } 6 | 7 | enum Mode { 8 | STRICT, RELAXED 9 | 10 | boolean isRelaxed() { this == RELAXED } 11 | 12 | boolean isStrict() { this == STRICT } 13 | } -------------------------------------------------------------------------------- /.gitignore: -------------------------------------------------------------------------------- 1 | .classpath 2 | .project 3 | bin/ 4 | target/ 5 | .settings 6 | .DS_Store 7 | .groovy 8 | .idea/ 9 | **/*.iml 10 | *.iml 11 | .gradle/ 12 | build 13 | scratches/ 14 | src/main/groovy/fuzzycsv/JJ.groovy 15 | src/test/resources/dsl 16 | out/ 17 | .attach_* 18 | src/main/playground/ 19 | /src/test/groovy/compat/fuzzycsv/ 20 | -------------------------------------------------------------------------------- /circle.yml: -------------------------------------------------------------------------------- 1 | run: 2 | name: chmod permissions 3 | command: chmod +x ./gradlew 4 | 5 | machine: 6 | java: 7 | version: 8 | oraclejdk8 9 | 10 | 11 | general: 12 | branches: 13 | only: 14 | - master # only build/deploy from master 15 | 16 | test: 17 | override: 18 | - ./gradlew test -is 19 | -------------------------------------------------------------------------------- /src/main/groovy/fuzzycsv/ConcatMethod.java: -------------------------------------------------------------------------------- 1 | package fuzzycsv; 2 | 3 | public interface ConcatMethod { 4 | 5 | enum Row implements ConcatMethod { 6 | LEFT, RIGHT, COMMON, ALL 7 | 8 | } 9 | 10 | enum Column implements ConcatMethod { 11 | STACK, ALL //LEFT, RIGHT, COMMON 12 | } 13 | 14 | 15 | } 16 | -------------------------------------------------------------------------------- /src/test/groovy/fuzzycsv/javaly/JFixtures.groovy: -------------------------------------------------------------------------------- 1 | package fuzzycsv.javaly 2 | 3 | class JFixtures { 4 | 5 | 6 | static String input = """ 7 | name,sex 8 | John Doe, male 9 | Ronald Koh, female 10 | Betty Kai,female 11 | Eliza H, female 12 | """ 13 | 14 | 15 | } 16 | -------------------------------------------------------------------------------- /.github/workflows/gradle.yml: -------------------------------------------------------------------------------- 1 | name: Java CI 2 | 3 | on: [push] 4 | 5 | jobs: 6 | build: 7 | 8 | runs-on: ubuntu-latest 9 | 10 | steps: 11 | - uses: actions/checkout@v1 12 | - name: Set up JDK 1.11 13 | uses: actions/setup-java@v1 14 | with: 15 | distribution: 'zulu' 16 | java-version: '11' 17 | - name: Build with Gradle 18 | run: ./gradlew build 19 | -------------------------------------------------------------------------------- /src/main/groovy/fuzzycsv/rdbms/stmt/SqlRenderer.groovy: -------------------------------------------------------------------------------- 1 | package fuzzycsv.rdbms.stmt 2 | 3 | import fuzzycsv.rdbms.FuzzyCSVDbExporter 4 | 5 | interface SqlRenderer { 6 | 7 | String modifyColumn(String tableName, FuzzyCSVDbExporter.Column column) 8 | 9 | String addColumn(String tableName, FuzzyCSVDbExporter.Column column) 10 | 11 | String createTable(String tableName, List columns) 12 | 13 | String quoteName(String name) 14 | 15 | } -------------------------------------------------------------------------------- /Makefile: -------------------------------------------------------------------------------- 1 | 2 | 3 | clean: 4 | ./gradlew clean 5 | 6 | test: 7 | make clean test-g3 8 | make clean test-g4 9 | 10 | test-g4: 11 | ./gradlew test -Pvariant=4 12 | 13 | test-g3: 14 | ./gradlew test -Pvariant=3 15 | 16 | build: 17 | ./gradlew build 18 | 19 | publish-groovy3: 20 | ./gradlew build publish -Pvariant=3 --no-daemon 21 | 22 | publish-groovy4: 23 | ./gradlew build publish -Pvariant=4 --no-daemon 24 | 25 | close-release: 26 | #./gradlew closeAndReleaseRepository 27 | echo "Close and release repository manually" 28 | -------------------------------------------------------------------------------- /src/main/groovy/fuzzycsv/DataAction.groovy: -------------------------------------------------------------------------------- 1 | package fuzzycsv 2 | 3 | import groovy.transform.stc.ClosureParams 4 | import groovy.transform.stc.SimpleType 5 | 6 | class DataAction { 7 | FuzzyCSVTable table 8 | Closure action 9 | Closure filter = { true } 10 | 11 | def set(@ClosureParams(value = SimpleType.class, options = "fuzzycsv.Record") 12 | Closure action) { 13 | this.action = action 14 | 15 | } 16 | 17 | def where(@ClosureParams(value = SimpleType.class, options = "fuzzycsv.Record") Closure filter) { 18 | this.filter = filter 19 | } 20 | 21 | } -------------------------------------------------------------------------------- /Releasing.txt: -------------------------------------------------------------------------------- 1 | Previously using the research gate gradle release plugin..since we now support variants and no time to play around with scripts we will just release manually 2 | 3 | gradle release -Prelease.useAutomaticVersion=true -Prelease.releaseVersion=1.0.0 -Prelease.newVersion=1.1.0-SNAPSHOT 4 | 5 | 6 | RELEASE MANUALLY. 7 | 1. Set the version name in the gradle.properties file 8 | 9 | 2. > gradle clean build publish -Pvariant=4 --no-daemon 10 | 3. > gradle closeAndReleaseRepository 11 | 12 | 2. > gradle clean build publish -Pvariant=3 --no-daemon 13 | 3. > gradle closeAndReleaseRepository 14 | 15 | 16 | gradle clean build publish -Pvariant=3 --no-daemon -------------------------------------------------------------------------------- /src/test/resources/log4j.xml: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 6 | 7 | 8 | 9 | 10 | 11 | 12 | 13 | 14 | 15 | 16 | 17 | 18 | -------------------------------------------------------------------------------- /src/main/groovy/fuzzycsv/SpreadConfig.java: -------------------------------------------------------------------------------- 1 | package fuzzycsv; 2 | 3 | import fuzzycsv.javaly.Fx2; 4 | import lombok.AccessLevel; 5 | import lombok.SneakyThrows; 6 | 7 | import java.util.Objects; 8 | 9 | @lombok.With 10 | @lombok.AllArgsConstructor(access = AccessLevel.PRIVATE) 11 | @lombok.Getter 12 | public class SpreadConfig { 13 | 14 | private Object col; 15 | private Fx2 nameGenFn; 16 | 17 | public SpreadConfig() { 18 | nameGenFn = (Object col, Object value) -> RecordFx.resolveName(col) + "_" + value; 19 | } 20 | 21 | @SneakyThrows 22 | public String createName(Object key) { 23 | Object call = nameGenFn.call(col, key); 24 | return Objects.toString(call); 25 | } 26 | } 27 | -------------------------------------------------------------------------------- /src/test/groovy/fuzzycsv/IgnoreNewLineCSVWriterTest.groovy: -------------------------------------------------------------------------------- 1 | package fuzzycsv 2 | 3 | import junit.framework.Assert 4 | import org.junit.Test 5 | 6 | /** 7 | * Created by kay on 11/30/2016. 8 | */ 9 | class IgnoreNewLineCSVWriterTest { 10 | 11 | @Test 12 | void writeTo() throws Exception { 13 | def csv = [ 14 | ['asas', 'sasas'], 15 | ['kasj \n ', '''""asas"\\" ' \r '''] 16 | ] 17 | 18 | def w = new StringWriter() 19 | 20 | IgnoreNewLineCSVWriter.writeTo(w, csv) 21 | Assert.assertEquals w.toString(), '''"asas","sasas" 22 | |"kasj ","""""asas""\\"" ' " 23 | |'''.stripMargin() 24 | 25 | } 26 | 27 | } -------------------------------------------------------------------------------- /src/main/groovy/fuzzycsv/rdbms/DbExportFlags.groovy: -------------------------------------------------------------------------------- 1 | package fuzzycsv.rdbms 2 | 3 | import groovy.transform.CompileStatic 4 | 5 | @CompileStatic 6 | enum DbExportFlags { 7 | CREATE, 8 | CREATE_IF_NOT_EXISTS, 9 | INSERT, 10 | RESTRUCTURE, 11 | USE_DECIMAL_FOR_INTS 12 | 13 | 14 | static Set of(DbExportFlags flag, DbExportFlags... others) { 15 | def flags = EnumSet.of(flag) 16 | if (others) flags.addAll(others) 17 | return flags 18 | } 19 | 20 | static Set withRestructure() { 21 | return of(CREATE_IF_NOT_EXISTS, INSERT, RESTRUCTURE) 22 | } 23 | 24 | static Set createAndInsert() { 25 | return of(CREATE_IF_NOT_EXISTS,INSERT) 26 | } 27 | 28 | 29 | } 30 | -------------------------------------------------------------------------------- /src/test/groovy/fuzzycsv/FuzzyCSVUtilsTest.groovy: -------------------------------------------------------------------------------- 1 | package fuzzycsv 2 | 3 | import org.junit.Test 4 | 5 | import static fuzzycsv.FuzzyCSVUtils.move 6 | import static fuzzycsv.FuzzyCSVUtils.moveElems 7 | 8 | class FuzzyCSVUtilsTest { 9 | @Test 10 | void testMove() { 11 | 12 | 13 | assert move(createList(), 5, 0) == ['5S', '0S', '1S', '2S', '3S', '4S'] 14 | assert move(createList(), 0, 5) == ['1S', '2S', '3S', '4S', '5S', '0S'] 15 | 16 | assert moveElems(createList(), '5S', '0S') == ['5S', '0S', '1S', '2S', '3S', '4S'] 17 | assert moveElems(createList(), '5S', '0S') == ['5S', '0S', '1S', '2S', '3S', '4S'] 18 | 19 | 20 | } 21 | 22 | static List createList() { 23 | ['0S', '1S', '2S', '3S', '4S', '5S'] 24 | } 25 | } 26 | -------------------------------------------------------------------------------- /src/main/groovy/fuzzycsv/FuzzyCsvException.java: -------------------------------------------------------------------------------- 1 | package fuzzycsv; 2 | 3 | import java.util.concurrent.Callable; 4 | 5 | public class FuzzyCsvException extends RuntimeException { 6 | 7 | public FuzzyCsvException(String message) { 8 | super(message); 9 | } 10 | 11 | public FuzzyCsvException(String message, Throwable cause) { 12 | super(message, cause); 13 | } 14 | 15 | 16 | public static T wrap(Callable callable) { 17 | try { 18 | return callable.call(); 19 | } catch (Exception e) { 20 | throw new FuzzyCsvException(e.getMessage(), e); 21 | } 22 | } 23 | 24 | public static FuzzyCsvException wrap(Exception e) { 25 | return new FuzzyCsvException(e.getMessage(), e); 26 | } 27 | 28 | } 29 | -------------------------------------------------------------------------------- /src/main/groovy/fuzzycsv/Reducer.groovy: -------------------------------------------------------------------------------- 1 | package fuzzycsv 2 | /** 3 | * Created by kay on 9/9/2016. 4 | */ 5 | class Reducer extends AbstractAggregator { 6 | 7 | Closure reducer 8 | private passRecord 9 | 10 | Reducer(Closure reducer) { 11 | this.reducer = reducer 12 | this.passRecord = reducer.maximumNumberOfParameters > 1 13 | } 14 | 15 | 16 | @Override 17 | Object getValue() { 18 | return reducer.call(data) 19 | } 20 | 21 | Object getValue(Record fx) { 22 | 23 | if (passRecord) { 24 | reducer.call(data, fx) 25 | } else { 26 | return reducer.call(data) 27 | } 28 | } 29 | 30 | static reduce(Closure fx) { 31 | return new Reducer(fx) 32 | } 33 | 34 | static reduce(String column, Closure fx) { 35 | return new Reducer(fx).az(column) 36 | } 37 | } 38 | -------------------------------------------------------------------------------- /src/main/groovy/fuzzycsv/nav/MutableNav.groovy: -------------------------------------------------------------------------------- 1 | package fuzzycsv.nav 2 | 3 | import groovy.transform.CompileStatic 4 | 5 | @CompileStatic 6 | class MutableNav { 7 | 8 | 9 | Navigator curr 10 | 11 | MutableNav(Navigator curr) { 12 | this.curr = curr 13 | } 14 | 15 | def value() { 16 | curr.get() 17 | } 18 | 19 | MutableNav up() { 20 | curr = curr.up() 21 | return this 22 | } 23 | 24 | MutableNav down() { 25 | curr = curr.down() 26 | return this 27 | } 28 | 29 | MutableNav left() { 30 | curr = curr.left() 31 | return this 32 | 33 | } 34 | 35 | MutableNav right() { 36 | curr = curr.right() 37 | return this 38 | 39 | } 40 | 41 | boolean canGoUp() { 42 | curr.canGoUp() 43 | 44 | } 45 | 46 | @Override 47 | String toString() { 48 | return curr.toString() 49 | } 50 | } 51 | -------------------------------------------------------------------------------- /src/main/groovy/fuzzycsv/DataActionStep.java: -------------------------------------------------------------------------------- 1 | package fuzzycsv; 2 | 3 | import fuzzycsv.javaly.Fx1; 4 | import fuzzycsv.javaly.FxUtils; 5 | import lombok.AccessLevel; 6 | 7 | public class DataActionStep { 8 | 9 | private static final Fx1 TRUE = r -> true; 10 | 11 | @lombok.Setter(AccessLevel.PACKAGE) 12 | private Fx1 filter = TRUE; 13 | @lombok.Setter(AccessLevel.PACKAGE) 14 | 15 | private Fx1 action; 16 | @lombok.Setter(AccessLevel.PACKAGE) 17 | 18 | private FuzzyCSVTable fuzzyCSVTable; 19 | 20 | public FuzzyCSVTable where(Fx1 filter) { 21 | this.filter = filter; 22 | return update(); 23 | } 24 | 25 | /** 26 | * Update for no condition 27 | */ 28 | public FuzzyCSVTable all() { 29 | return where(TRUE); 30 | } 31 | public FuzzyCSVTable update() { 32 | 33 | return FuzzyCSVTable.tbl(FuzzyCSV.modify(fuzzyCSVTable.getCsv(), FxUtils.recordFx(action), FxUtils.recordFx(filter))); 34 | } 35 | 36 | } 37 | -------------------------------------------------------------------------------- /src/main/groovy/fuzzycsv/Sum.groovy: -------------------------------------------------------------------------------- 1 | package fuzzycsv 2 | 3 | 4 | class Sum extends AbstractAggregator { 5 | 6 | /** 7 | * A list of either Record functions or Column Names 8 | */ 9 | List columns 10 | 11 | Sum() {} 12 | 13 | 14 | Sum(List columns) { 15 | this.columns = columns 16 | } 17 | 18 | Sum(List columns, String columnName) { 19 | this.columnName = columnName 20 | this.columns = columns 21 | } 22 | 23 | @Override 24 | Number getValue() { 25 | List data = getData(columns) 26 | def value = data.sum { row -> 27 | return FuzzyCSVUtils.toNumbers(row).sum() 28 | } 29 | return value 30 | } 31 | 32 | 33 | static Sum sum(Object[] aggregateColumns) { 34 | return new Sum(aggregateColumns as List) 35 | } 36 | 37 | @Override 38 | String getColumnName() { 39 | if (!super.columnName) { 40 | return "sum(${columns*.toString().join(',')})" 41 | } else { 42 | return super.getColumnName() 43 | } 44 | } 45 | } 46 | -------------------------------------------------------------------------------- /src/main/groovy/fuzzycsv/AbstractAggregator.groovy: -------------------------------------------------------------------------------- 1 | package fuzzycsv 2 | 3 | import groovy.transform.CompileStatic 4 | 5 | import static fuzzycsv.FuzzyCSVTable.tbl 6 | 7 | 8 | @CompileStatic 9 | abstract class AbstractAggregator implements Aggregator { 10 | 11 | FuzzyCSVTable data 12 | protected String columnName 13 | 14 | 15 | List getData(List columns) { 16 | 17 | def requiredData 18 | if (columns == null || columns.isEmpty()) { 19 | requiredData = data.csv 20 | } else { 21 | requiredData = FuzzyCSV.select(columns, data.csv, Mode.STRICT) 22 | } 23 | def newData = requiredData[1..-1] 24 | return newData 25 | } 26 | 27 | void setData(List data) { 28 | this.data = tbl(data) 29 | } 30 | 31 | 32 | @Override 33 | Aggregator az(String name) { 34 | columnName = name; this 35 | } 36 | 37 | String getColumnName() { 38 | return columnName 39 | } 40 | 41 | void setColumnName(String columnName) { 42 | this.columnName = columnName 43 | } 44 | } 45 | -------------------------------------------------------------------------------- /src/main/groovy/fuzzycsv/TableIterator.groovy: -------------------------------------------------------------------------------- 1 | package fuzzycsv 2 | 3 | import groovy.transform.CompileStatic 4 | 5 | /** 6 | * Created by kay on 7/25/2016. 7 | */ 8 | @CompileStatic 9 | class TableIterator implements Iterator { 10 | 11 | private List header 12 | private List csv 13 | private Iterator csvIterator 14 | private int counter = 1 15 | 16 | private TableIterator() {} 17 | 18 | TableIterator(FuzzyCSVTable tbl) { 19 | csv = tbl.csv 20 | csvIterator = csv.iterator() 21 | //get the header 22 | header = csvIterator.next() 23 | } 24 | 25 | @Override 26 | boolean hasNext() { 27 | return csvIterator.hasNext() 28 | } 29 | 30 | @Override 31 | Record next() { 32 | def recData = csvIterator.next() 33 | def record = Record.getRecord(header, recData, csv, counter) 34 | counter++ 35 | return record 36 | } 37 | 38 | @Override 39 | void remove() { 40 | throw new UnsupportedOperationException("Remove is not supported by TableIterator") 41 | } 42 | } 43 | -------------------------------------------------------------------------------- /src/test/groovy/fuzzycsv/FuzzyTableImportTest.groovy: -------------------------------------------------------------------------------- 1 | package fuzzycsv 2 | 3 | import org.junit.Before 4 | import org.junit.Test 5 | 6 | class FuzzyTableImportTest { 7 | 8 | def csv = [ 9 | ['name', 'sex', 'age'], 10 | ['name1', 'sex1', 22], 11 | ['name3', 'sex2', 23], 12 | ] 13 | 14 | String json 15 | 16 | @Before 17 | void setup() { 18 | json = FuzzyCSVTable.tbl(csv).toJsonText() 19 | } 20 | 21 | @Test 22 | void testFromList() { 23 | def table = FuzzyCSVTable.tbl(csv).copy() 24 | assert FuzzyCSVTable.fromListList(csv).csv == table.csv 25 | 26 | assert FuzzyCSVTable.fromJsonText(json).csv == table.csv 27 | 28 | assert FuzzyCSVTable.fromInspection(csv).csv == table.csv 29 | 30 | assert FuzzyCSVTable.fromRecordList(FuzzyCSVTable.tbl(csv).copy().collect()).csv == table.csv 31 | 32 | assert FuzzyCSVTable.fromMap([a: 1, b: 2]).csv == [['key', 'value'], ['a', 1], ['b', 2]] 33 | assert FuzzyCSVTable.tbl([a: 1, b: 2]).csv == [['key', 'value'], ['a', 1], ['b', 2]] 34 | 35 | 36 | } 37 | } 38 | -------------------------------------------------------------------------------- /gradle.properties: -------------------------------------------------------------------------------- 1 | #Mon, 12 Sep 2016 15:56:06 +0300 2 | 3 | 4 | #gpg --list-keys --keyid-format short 5 | signing.keyId=keyid 6 | signing.password=password 7 | #gpg --export-secret-keys -o secring.gpg 8 | signing.secretKeyRingFile=pathtokeyring 9 | 10 | ossrhUsername=ossrhun 11 | ossrhPassword=password 12 | 13 | 14 | # Release Plugin Settings 15 | SONATYPE_HOST=S01 16 | 17 | RELEASE_SIGNING_ENABLED=true 18 | 19 | 20 | POM_ARTIFACT_ID=fuzzy-csv 21 | VERSION_NAME=1.9.1 22 | 23 | POM_NAME=Fuzzy CSV 24 | POM_DESCRIPTION=A groovy/java tabular Data (from CSV,SQL,JSON) processing library that supports fuzzy column matching,tranformations/merging/querying etc 25 | #POM_INCEPTION_YEAR=2020 26 | POM_URL=https://github.com/kayr/fuzzy-csv/ 27 | 28 | POM_LICENSE_NAME=The Apache Software License, Version 2.0 29 | POM_LICENSE_URL=https://www.apache.org/licenses/LICENSE-2.0.txt 30 | POM_LICENSE_DIST=repo 31 | 32 | POM_SCM_URL=scm:git:https://github.com/kayr/fuzzy-csv.git 33 | POM_SCM_CONNECTION=scm:git:https://github.com/kayr/fuzzy-csv.git 34 | POM_SCM_DEV_CONNECTION=scm:git:https://github.com/kayr/fuzzy-csv.git 35 | 36 | POM_DEVELOPER_ID=kayr 37 | POM_DEVELOPER_NAME=Ronald Kayondo 38 | POM_DEVELOPER_URL=https://github.com/kayr/ -------------------------------------------------------------------------------- /src/test/groovy/fuzzycsv/Excel2CsvTest.groovy: -------------------------------------------------------------------------------- 1 | package fuzzycsv 2 | 3 | 4 | import org.apache.poi.xssf.usermodel.XSSFWorkbook 5 | import org.junit.Test 6 | 7 | import java.text.DateFormat 8 | 9 | class Excel2CsvTest { 10 | 11 | @Test 12 | void testConvertingToCSV() { 13 | def path = '/sample_file.xlsx' 14 | 15 | def workbook = new XSSFWorkbook(getClass().getResourceAsStream(path)) 16 | 17 | 18 | def sheets = Excel2Csv.allSheetsToCsv(workbook) 19 | 20 | def table = sheets['client_transactions'] 21 | 22 | def modify = table.modify { 23 | set { 24 | // it.date = (it.date as Date).format("dd-MM-yyyy") 25 | it.set("date", (it.date as Date).format("dd-MM-yyyy")) 26 | } 27 | } 28 | 29 | 30 | assert modify.csv == [['date', 'batch_number', 'client_number', 'agent_number', 'transaction_type', 'clearing_status', 'amount', 'transaction_id'], 31 | ['07-09-2017', null, '701-721572311', null, 'SUBSCRIPTION', 'Cleared', 2.0E7, null], 32 | ['07-09-2017', null, '701-995855822', null, 'SUBSCRIPTION', 'Pending', 2.0E8, null], 33 | ['11-09-2017', null, '701-1916955102', null, 'SUBSCRIPTION', 'Transferred', 800000.0, null]] 34 | } 35 | 36 | } 37 | -------------------------------------------------------------------------------- /src/test/groovy/fuzzycsv/CountTest.groovy: -------------------------------------------------------------------------------- 1 | package fuzzycsv 2 | 3 | import groovy.test.GroovyAssert 4 | import org.junit.Test 5 | 6 | import static groovy.test.GroovyAssert.shouldFail 7 | 8 | 9 | class CountTest { 10 | 11 | @Test 12 | void testGetValue() { 13 | 14 | Count count = new Count(['sub_county', 'ps_total_score'], Data.csv) 15 | assert count.value == 5 16 | 17 | count = new Count(['ps_total_score'], Data.csv) 18 | assert count.value == 2 19 | 20 | count = new Count(['ps_total_score', 'pipes_total_score'], Data.csv) 21 | assert count.value == 3 22 | 23 | count = new Count(null, Data.csv) 24 | assert count.value == 5 25 | 26 | shouldFail (IllegalArgumentException) { 27 | new Count(['dsd'], Data.csv).value 28 | } 29 | 30 | 31 | } 32 | 33 | @Test 34 | void testName(){ 35 | def count = new Count(['a'], null) 36 | assert count.getColumnName() == 'count(a)' 37 | 38 | count = new Count(['a','b'], null) 39 | assert count.getColumnName() == 'count(a,b)' 40 | 41 | count = new Count(['a','b'], null).unique() 42 | assert count.getColumnName() == 'countunique(a,b)' 43 | 44 | count = new Count(['a','b'], null).unique().az('xxx') 45 | assert count.getColumnName() == 'xxx' 46 | } 47 | } 48 | -------------------------------------------------------------------------------- /src/main/groovy/fuzzycsv/IgnoreNewLineCSVWriter.groovy: -------------------------------------------------------------------------------- 1 | package fuzzycsv 2 | 3 | import com.opencsv.CSVWriter 4 | 5 | /** 6 | * Created by kay on 11/30/2016. 7 | */ 8 | @Deprecated 9 | class IgnoreNewLineCSVWriter extends CSVWriter { 10 | 11 | public static final char LINE_END = '\n' 12 | public static final char LINE_RETURN = '\r' 13 | private boolean applyQuotesToAll = true 14 | 15 | 16 | /** 17 | * Constructs CSVWriter using a comma for the separator. 18 | * 19 | * @param writer 20 | * the writer to an underlying CSV source. 21 | */ 22 | IgnoreNewLineCSVWriter(Writer writer) { 23 | super(writer) 24 | } 25 | 26 | 27 | @Override 28 | protected void processCharacter(Appendable appendable, char nextChar) throws IOException { 29 | if (nextChar == LINE_END || nextChar == LINE_RETURN) return 30 | super.processCharacter(appendable, nextChar) 31 | } 32 | 33 | @Override 34 | void writeAll(List allLines) { 35 | 36 | for (Iterator iter = allLines.iterator(); iter.hasNext();) { 37 | String[] line = iter.next() as String[] 38 | writeNext(line, applyQuotesToAll) 39 | } 40 | writer.flush() 41 | } 42 | 43 | static writeTo(Writer w, List csv) { 44 | new IgnoreNewLineCSVWriter(w).writeAll(csv) 45 | } 46 | } 47 | -------------------------------------------------------------------------------- /src/main/groovy/fuzzycsv/rdbms/stmt/DumbH2Renderer.groovy: -------------------------------------------------------------------------------- 1 | package fuzzycsv.rdbms.stmt 2 | 3 | import fuzzycsv.rdbms.FuzzyCSVDbExporter 4 | import groovy.transform.CompileStatic; 5 | 6 | @CompileStatic 7 | class DumbH2Renderer extends DefaultSqlRenderer { 8 | 9 | private static DumbH2Renderer instance; 10 | 11 | static DumbH2Renderer getInstance() { 12 | if (instance == null)// don't care about synchronization 13 | instance = new DumbH2Renderer(); 14 | return instance; 15 | } 16 | 17 | @Override 18 | String quoteName(String name) { 19 | return "\"" + name + "\""; 20 | } 21 | 22 | @Override 23 | String toDataString(FuzzyCSVDbExporter.Column column) { 24 | def primaryKeyStr = column.isPrimaryKey ? 'primary key' : '' 25 | 26 | if (column.autoIncrement) { 27 | primaryKeyStr = "$primaryKeyStr AUTO_INCREMENT" 28 | } 29 | 30 | if(column.type == 'bigint') 31 | return "${quoteName(column.name)} $column.type ${primaryKeyStr}" 32 | 33 | if (column.decimals > 0) 34 | return "${quoteName(column.name)} $column.type($column.size, $column.decimals) ${primaryKeyStr}" 35 | 36 | if (column.size > 0) 37 | return "${quoteName(column.name)} $column.type($column.size) ${primaryKeyStr}" 38 | 39 | 40 | return "${quoteName(column.name)} $column.type ${primaryKeyStr}" 41 | } 42 | } 43 | -------------------------------------------------------------------------------- /src/main/groovy/fuzzycsv/rdbms/stmt/MySqlRenderer.groovy: -------------------------------------------------------------------------------- 1 | package fuzzycsv.rdbms.stmt 2 | 3 | import fuzzycsv.rdbms.FuzzyCSVDbExporter.Column 4 | import org.codehaus.groovy.runtime.InvokerHelper 5 | 6 | import static fuzzycsv.rdbms.FuzzyCsvDbInserter.inTicks 7 | 8 | class MySqlRenderer extends DefaultSqlRenderer { 9 | 10 | 11 | private static SqlRenderer instance 12 | 13 | static SqlRenderer getInstance() { 14 | if (instance == null)// don't care about synchronization 15 | instance = new MySqlRenderer() 16 | return instance 17 | } 18 | 19 | @Override 20 | String modifyColumn(String tableName, Column column) { 21 | def copy = mayBeModifyType(column) 22 | "ALTER TABLE ${quoteName(tableName)} MODIFY COLUMN ${toDataString(copy)};" 23 | } 24 | 25 | @Override 26 | String addColumn(String tableName, Column column) { 27 | def copy = mayBeModifyType(column) 28 | "ALTER TABLE ${quoteName(tableName)} ADD ${toDataString(copy)}" 29 | } 30 | 31 | Column mayBeModifyType(Column column) { 32 | Column copy = column 33 | if (column.type == 'text') { 34 | copy = new Column() 35 | InvokerHelper.setProperties(copy, column.properties) 36 | copy.type = 'longtext' 37 | } 38 | return copy 39 | } 40 | 41 | 42 | @Override 43 | String quoteName(String name) { 44 | inTicks(name) 45 | } 46 | 47 | } 48 | -------------------------------------------------------------------------------- /src/test/groovy/fuzzycsv/javaly/DynamicTest.java: -------------------------------------------------------------------------------- 1 | package fuzzycsv.javaly; 2 | 3 | import org.junit.jupiter.api.Nested; 4 | import org.junit.jupiter.api.Test; 5 | 6 | import java.math.BigDecimal; 7 | 8 | import static org.junit.jupiter.api.Assertions.assertFalse; 9 | import static org.junit.jupiter.api.Assertions.assertTrue; 10 | 11 | class DynamicTest { 12 | 13 | @Nested 14 | class Eq { 15 | 16 | @Test 17 | void eq() { 18 | assertTrue(Dynamic.of(1).eq(1)); 19 | assertTrue(Dynamic.of(1).eq(1.0)); 20 | assertTrue(Dynamic.of(1).eq(1L)); 21 | assertTrue(Dynamic.of(1).eq(1.0f)); 22 | assertTrue(Dynamic.of(1).eq(1.0d)); 23 | assertTrue(Dynamic.of(1).eq(Dynamic.of(1))); 24 | assertTrue(Dynamic.of(1).eq(Dynamic.of(1.0))); 25 | assertTrue(Dynamic.of(1).eq(Dynamic.of(1L))); 26 | assertTrue(Dynamic.of(1).eq(Dynamic.of(1.0f))); 27 | assertTrue(Dynamic.of(1).eq(Dynamic.of(1.0d))); 28 | assertTrue(Dynamic.of(1).eq(Dynamic.of(new BigDecimal("1")))); 29 | assertTrue(Dynamic.of(1).eq(Dynamic.of(Numb.of("1")))); 30 | assertTrue(Dynamic.of(1).eq(Numb.of("1"))); 31 | 32 | assertTrue(Dynamic.of(1.0).eq(1)); 33 | assertTrue(Dynamic.of("this is a string").eq("this is a string")); 34 | assertTrue(Dynamic.of("this is a string").eq(Dynamic.of("this is a string"))); 35 | 36 | } 37 | 38 | @Test 39 | void eqShouldNotCoerce() { 40 | assertFalse(Dynamic.of(1).eq("1")); 41 | } 42 | } 43 | 44 | } -------------------------------------------------------------------------------- /src/main/groovy/fuzzycsv/rdbms/ExportParams.groovy: -------------------------------------------------------------------------------- 1 | package fuzzycsv.rdbms 2 | 3 | import fuzzycsv.rdbms.stmt.SqlDialect 4 | import fuzzycsv.rdbms.stmt.SqlRenderer 5 | import groovy.transform.builder.Builder 6 | import groovy.transform.builder.SimpleStrategy 7 | 8 | @Builder(builderStrategy = SimpleStrategy, prefix = 'with') 9 | class ExportParams { 10 | int pageSize = 10 11 | List primaryKeys = [] 12 | List autoIncrement = [] 13 | Set exportFlags = Collections.emptySet() 14 | SqlRenderer sqlRenderer 15 | SqlDialect dialect = SqlDialect.DEFAULT 16 | 17 | 18 | private ExportParams() {} 19 | 20 | ExportParams autoIncrement(String key, String... otherKeys) { 21 | if (autoIncrement == null) autoIncrement = [] 22 | 23 | autoIncrement.add(key) 24 | 25 | if (otherKeys) autoIncrement.addAll(otherKeys) 26 | 27 | return this 28 | } 29 | 30 | ExportParams withPrimaryKeys(String key, String... otherKeys) { 31 | if (primaryKeys == null) primaryKeys = [] 32 | 33 | primaryKeys.add(key) 34 | 35 | if (otherKeys) primaryKeys.addAll(otherKeys) 36 | 37 | return this 38 | } 39 | 40 | static ExportParams of(DbExportFlags flag, DbExportFlags... flags) { 41 | def exportFlags = DbExportFlags.of(flag, flags) 42 | 43 | return new ExportParams().withExportFlags(exportFlags) 44 | 45 | } 46 | 47 | static ExportParams of(Set flags) { 48 | return new ExportParams().withExportFlags(flags) 49 | } 50 | 51 | 52 | static ExportParams defaultParams() { 53 | return new ExportParams() 54 | } 55 | 56 | } 57 | -------------------------------------------------------------------------------- /src/main/groovy/fuzzycsv/Fuzzy.groovy: -------------------------------------------------------------------------------- 1 | package fuzzycsv 2 | 3 | import com.github.kayr.phrasematcher.PhraseMatcher 4 | import groovy.transform.CompileStatic 5 | import org.codehaus.groovy.runtime.DefaultGroovyMethods 6 | import org.slf4j.Logger 7 | import org.slf4j.LoggerFactory 8 | 9 | @CompileStatic 10 | class Fuzzy { 11 | private static Logger log = LoggerFactory.getLogger(Fuzzy) 12 | 13 | static int findBestPosition(def phrases, String header, double minScore) { 14 | 15 | phrases = DefaultGroovyMethods.asType(phrases, List) // groovy 4 no longer supports phrases as List 16 | def csvColIdx = findPosition(phrases, header) 17 | if (csvColIdx == -1 && minScore < 1.0) { 18 | csvColIdx = findClosestPosition(phrases, header, minScore) 19 | } 20 | csvColIdx 21 | } 22 | 23 | static int findClosestPosition(def phrases, String phrase, double minScore) { 24 | phrases = DefaultGroovyMethods.asType(phrases, List) 25 | def ph = PhraseMatcher.train(phrases as List) 26 | def newName = ph.bestHit(phrase, minScore) 27 | 28 | if (newName.isInvalid()) { 29 | if (log.isDebugEnabled()) 30 | log.debug "getColumnPositionUsingHeuristic(): warning: no column match found: [$phrase] = [$newName]" 31 | return -1 32 | } 33 | if (log.isDebugEnabled()) 34 | log.debug "getColumnPositionUsingHeuristic(): heuristic: [$phrase] = [$newName]" 35 | return findPosition(phrases, newName.phrase) 36 | } 37 | 38 | static int findPosition(def phrases, String name) { 39 | phrases.findIndexOf { value -> value.toString().toLowerCase().trim().equalsIgnoreCase(name.trim().toLowerCase()) } 40 | } 41 | } 42 | -------------------------------------------------------------------------------- /src/test/groovy/fuzzycsv/javaly/TestUtils.java: -------------------------------------------------------------------------------- 1 | package fuzzycsv.javaly; 2 | 3 | import fuzzycsv.FuzzyCSVTable; 4 | import fuzzycsv.Sort; 5 | import org.junit.jupiter.api.Test; 6 | 7 | import java.util.AbstractMap; 8 | import java.util.Arrays; 9 | import java.util.Map; 10 | 11 | import static fuzzycsv.javaly.FxUtils.recordFx; 12 | 13 | public class TestUtils { 14 | 15 | private TestUtils() { 16 | } 17 | 18 | 19 | public static Map mapOf(Map.Entry... entries) { 20 | Map map = new java.util.LinkedHashMap<>(); 21 | for (Map.Entry entry : entries) { 22 | map.put(entry.getKey(), entry.getValue()); 23 | } 24 | return map; 25 | } 26 | 27 | public static Map.Entry kv(K k1, V v1) { 28 | return new AbstractMap.SimpleEntry<>(k1, v1); 29 | } 30 | 31 | @Test 32 | void fooBar() { 33 | FuzzyCSVTable t = FuzzyCSVTable.fromRows( 34 | Arrays.asList("a,b,c,d".split(",")), 35 | Arrays.asList("a,1,10,2".split(",")), 36 | Arrays.asList("b,3,11,4".split(",")), 37 | Arrays.asList("a,5,12,6".split(",")) 38 | ).copy(); 39 | 40 | 41 | t.addColumn( 42 | recordFx("n", 43 | arg -> mapOf( 44 | kv("c", arg.get("c")), 45 | kv("d", arg.get("d")) 46 | ))) 47 | .deleteColumns("c", "d") 48 | .unwind("n") 49 | .spread(recordFx(r -> { 50 | Map.Entry n1 = r.d("n").cast(); 51 | return mapOf(kv("n", n1.getKey()), kv("v", n1.getValue())); 52 | }).az("x")) 53 | .deleteColumns("n") 54 | .sortBy(Sort.byColumns("x_n")) 55 | .printTable(); 56 | 57 | } 58 | } 59 | -------------------------------------------------------------------------------- /src/main/groovy/fuzzycsv/FuzzyCsvExtensionMethods.java: -------------------------------------------------------------------------------- 1 | package fuzzycsv; 2 | 3 | import groovy.lang.Closure; 4 | import groovy.lang.IntRange; 5 | 6 | import java.util.List; 7 | 8 | public class FuzzyCsvExtensionMethods { 9 | 10 | private FuzzyCsvExtensionMethods() { 11 | } 12 | 13 | public static List getAt(FuzzyCSVTable self, String column) { 14 | return self.getColumn(column); 15 | } 16 | 17 | public static Record getAt(FuzzyCSVTable self, int index) { 18 | return self.row(index); 19 | } 20 | 21 | public static FuzzyCSVTable getAt(FuzzyCSVTable self, IntRange range) { 22 | return self.doSlice(range); 23 | } 24 | 25 | 26 | public static FuzzyCSVTable leftShift(FuzzyCSVTable self, FuzzyCSVTable right) { 27 | return self.concatColumns(right); 28 | } 29 | 30 | public static FuzzyCSVTable plus(FuzzyCSVTable self, FuzzyCSVTable right) { 31 | return self.union(right); 32 | } 33 | 34 | 35 | public static Object getAt(Record self, int idx) { 36 | return self.get(idx); 37 | } 38 | 39 | public static Object getAt(Record self, CharSequence column) { 40 | return self.get(column.toString()); 41 | } 42 | 43 | public static Object getAt(Record self, String column) { 44 | return self.get(column); 45 | } 46 | 47 | public static Record putAt(Record self, String column, Object value) { 48 | self.set(column, value); 49 | return self; 50 | } 51 | 52 | public static Record setAt(Record self, String column, Object value) { 53 | self.set(column, value); 54 | return self; 55 | } 56 | 57 | public static Sort asc(String name) { 58 | return Sort.byColumn(name).asc(); 59 | } 60 | 61 | public static Sort desc(String name) { 62 | return Sort.byColumn(name).desc(); 63 | } 64 | 65 | 66 | } 67 | -------------------------------------------------------------------------------- /src/main/groovy/fuzzycsv/Count.groovy: -------------------------------------------------------------------------------- 1 | package fuzzycsv 2 | 3 | import groovy.transform.CompileStatic 4 | 5 | @CompileStatic 6 | class Count extends AbstractAggregator { 7 | 8 | List columns 9 | private boolean unique = false 10 | 11 | Count() {} 12 | 13 | Count(List columns, List data) { 14 | this.setData(data) 15 | this.columns = columns 16 | } 17 | 18 | @Override 19 | Object getValue() { 20 | def data = getData(columns) 21 | def unique = unique ? data.unique() : data 22 | if (columns) 23 | return unique.count { List r -> r.any { c -> c != null } } 24 | return unique.size() 25 | } 26 | 27 | Count unique() { 28 | unique = true 29 | return this 30 | } 31 | 32 | Count all() { 33 | unique = false 34 | return this 35 | } 36 | 37 | 38 | static Count count() { 39 | return new Count(columnName: "count()") 40 | } 41 | 42 | static Count plnCount(String name) { 43 | return new Count(columnName: name) 44 | } 45 | 46 | static Count count(String name, Object... columnsForCounting) { 47 | return new Count(columnName: name, columns: columnsForCounting as List) 48 | } 49 | 50 | static Count countUnique() { 51 | return new Count(columnName: "count()").unique() 52 | } 53 | 54 | static Count plnCountUnique(String name) { 55 | return new Count(columnName: name).unique() 56 | } 57 | 58 | static Count countUnique(String name, Object... columnsForCounting) { 59 | return new Count(columnName: name, columns: columnsForCounting as List).unique() 60 | } 61 | 62 | @Override 63 | String getColumnName() { 64 | if (!super.@columnName) { 65 | def columnNames = columns.collect { it.toString() }.join(',') 66 | if (unique) 67 | return "countunique(${columnNames})" 68 | else 69 | return "count(${columnNames})" 70 | } else { 71 | return super.@columnName 72 | } 73 | } 74 | 75 | } 76 | -------------------------------------------------------------------------------- /src/main/groovy/fuzzycsv/javaly/Dynamic.java: -------------------------------------------------------------------------------- 1 | package fuzzycsv.javaly; 2 | 3 | import java.util.Map; 4 | import java.util.Objects; 5 | 6 | public class Dynamic { 7 | 8 | private final Object obj; 9 | 10 | public Dynamic(Object obj) { 11 | this.obj = obj; 12 | } 13 | 14 | public static Dynamic of(Object obj) { 15 | return new Dynamic(obj); 16 | } 17 | 18 | 19 | Numb numb() { 20 | return Numb.of(obj); 21 | } 22 | 23 | Map asMap() { 24 | return (Map) obj; 25 | } 26 | 27 | /** 28 | * Utility method to cast the object to the given type. 29 | */ 30 | public T cast() { 31 | return (T) obj; 32 | } 33 | 34 | /** 35 | * Utility method to cast the object to the given type. 36 | */ 37 | public T cast(Class type) { 38 | return (T) obj; 39 | } 40 | 41 | String str() { 42 | return obj.toString(); 43 | } 44 | 45 | 46 | public boolean isNumber() { 47 | return obj instanceof Number; 48 | } 49 | 50 | 51 | public Object get() { 52 | return obj; 53 | } 54 | 55 | public Boolean eq(Object other) { 56 | 57 | Object unwrapped = other instanceof Dynamic ? ((Dynamic) other).get() : other; 58 | 59 | if (obj == null && unwrapped == null) 60 | return true; 61 | 62 | if (obj == null || unwrapped == null) 63 | return false; 64 | 65 | if (Numb.isNumber(obj) && Numb.isNumber(unwrapped)) 66 | return Numb.of(obj).eq(Numb.of(unwrapped)); 67 | 68 | return obj.equals(unwrapped); 69 | } 70 | 71 | 72 | @Override 73 | public String toString() { 74 | return String.valueOf(obj); 75 | } 76 | 77 | @Override 78 | public boolean equals(Object o) { 79 | if (this == o) return true; 80 | if (o == null || getClass() != o.getClass()) return false; 81 | Dynamic dynamic = (Dynamic) o; 82 | return Objects.equals(obj, dynamic.obj); 83 | } 84 | 85 | public int hashCde() { 86 | return Objects.hash(obj); 87 | } 88 | 89 | 90 | } 91 | 92 | -------------------------------------------------------------------------------- /src/main/groovy/fuzzycsv/rdbms/stmt/DefaultSqlRenderer.groovy: -------------------------------------------------------------------------------- 1 | package fuzzycsv.rdbms.stmt 2 | 3 | import fuzzycsv.rdbms.FuzzyCSVDbExporter 4 | 5 | 6 | class DefaultSqlRenderer implements SqlRenderer { 7 | 8 | private static SqlRenderer instance 9 | 10 | static SqlRenderer getInstance() { 11 | if (instance == null)// don't care about synchronization 12 | instance = new DefaultSqlRenderer() 13 | return instance 14 | } 15 | 16 | @Override 17 | String modifyColumn(String tableName, FuzzyCSVDbExporter.Column column) { 18 | "ALTER TABLE ${quoteName(tableName)} ALTER COLUMN ${toDataString(column)};" 19 | } 20 | 21 | @Override 22 | String addColumn(String tableName, FuzzyCSVDbExporter.Column column) { 23 | "ALTER TABLE ${quoteName(tableName)} ADD ${toDataString(column)}" 24 | 25 | } 26 | 27 | @Override 28 | String createTable(String tableName, List columns) { 29 | def columnString = columns 30 | .collect { toDataString(it) } 31 | .join(',') 32 | 33 | return "create table ${quoteName(tableName)}($columnString); " 34 | } 35 | 36 | @Override 37 | String quoteName(String name) { 38 | def quote = '`' 39 | if (name.contains(quote as CharSequence)) { 40 | throw new IllegalArgumentException("Header cannot contain $quote") 41 | } 42 | return quote + name + quote 43 | 44 | } 45 | 46 | String toDataString(FuzzyCSVDbExporter.Column column) { 47 | def primaryKeyStr = column.isPrimaryKey ? 'primary key' : '' 48 | 49 | if (column.autoIncrement) { 50 | primaryKeyStr = "$primaryKeyStr AUTO_INCREMENT" 51 | } 52 | 53 | if (column.decimals > 0) 54 | return "${quoteName(column.name)} $column.type($column.size, $column.decimals) ${primaryKeyStr}" 55 | 56 | if (column.size > 0) 57 | return "${quoteName(column.name)} $column.type($column.size) ${primaryKeyStr}" 58 | 59 | return "${quoteName(column.name)} $column.type ${primaryKeyStr}" 60 | 61 | } 62 | 63 | } 64 | -------------------------------------------------------------------------------- /src/test/groovy/fuzzycsv/Data.groovy: -------------------------------------------------------------------------------- 1 | package fuzzycsv 2 | 3 | class Data { 4 | 5 | static def getCsv() { 6 | FuzzyCSV.toUnModifiableCSV([ 7 | ['sub_county', 'ps_total_score', 'pipes_total_score', 'tap_total_score'], 8 | ['Hakibale', 18.1, null, null], 9 | ['Kabonero', 1, null, null], 10 | ['Kisomoro', null, 1, 10], 11 | ['Bunyangabu', null, null, '1'], 12 | ['Noon', null, null, 0] 13 | ]) 14 | } 15 | 16 | static def groupingData = FuzzyCSV.toUnModifiableCSV([ 17 | ['sub_county', 'ps_total_score', 'pipes_total_score', 'tap_total_score'], 18 | ['Hakibale', 18.1, null, null], 19 | ['Hakibale', 19, null, null], 20 | ['Hakibale', 2, null, null], 21 | ['Kabonero', 1, null, null], 22 | ['Kabonero', 1, null, null], 23 | ['Kabonero', 1, null, null], 24 | ['Kisomoro', null, 1, 10], 25 | ['Kisomoro', null, 1, 10], 26 | ['Kisomoro', null, 1, 10], 27 | ['Bunyangabu', null, null, '1'], 28 | ['Bunyangabu', null, null, '1'], 29 | ['Noon', null, null, 0] 30 | ]) 31 | 32 | static def getCsvs() { 33 | [ 34 | 'csv1.csv' : 'Name,Sex,Age,Location\n' + 35 | 'Ronald,Male,3,Bweyos\n' + 36 | 'Sara,Female,4,Muyenga', 37 | 'csv1_4.csv' : 'Name,Sex,Age2,Hobby\n' + 38 | 'Ronald,Male,3,Dogs\n' + 39 | 'Sara,Female,4,Cat\n' + 40 | 'Ronald,Femal,3,Monkey', 41 | 'csv1csv2.csv': 'Name,Sex,Age,Location,Subject,Mark\n' + 42 | 'Ronald,Male,3,Bweyos,Math,50\n' + 43 | 'Sara,Female,4,Muyenga,,\n' + 44 | 'Betty,,,,Biology,80', 45 | 'csv2.csv' : 'Name,Subject,Mark\n' + 46 | 'Ronald,Math,50\n' + 47 | 'Ronald,English,50\n' + 48 | 'Betty,Biology,80' 49 | ] 50 | } 51 | } 52 | -------------------------------------------------------------------------------- /src/test/groovy/fuzzycsv/H2DbHelper.groovy: -------------------------------------------------------------------------------- 1 | package fuzzycsv 2 | 3 | import fuzzycsv.rdbms.DDLUtils 4 | import groovy.sql.Sql 5 | import org.h2.jdbcx.JdbcConnectionPool 6 | 7 | import java.sql.Connection 8 | import java.util.concurrent.locks.ReentrantLock 9 | 10 | class H2DbHelper { 11 | 12 | static Sql getConnection() { 13 | Sql.newInstance(getDataSource().connection) 14 | } 15 | 16 | static Sql getMySqlConnection() { 17 | 18 | def dbURL = 'jdbc:mysql://127.0.0.1:3306/playground' 19 | def dbUserName = 'root' 20 | def dbPassword = 'pass' 21 | def dbDriver = 'com.mysql.cj.jdbc.Driver' 22 | return Sql.newInstance(dbURL, dbUserName, dbPassword, dbDriver) 23 | } 24 | 25 | static ReentrantLock lock = new ReentrantLock() 26 | static JdbcConnectionPool ds 27 | 28 | static JdbcConnectionPool getDataSource() { 29 | if (ds != null) { 30 | return ds 31 | } 32 | lock.lock() 33 | try { 34 | if (ds == null) { 35 | ds = JdbcConnectionPool.create('jdbc:h2:mem:test;DB_CLOSE_DELAY=-1;DB_CLOSE_ON_EXIT=false', 'sa', '') 36 | } 37 | } finally { 38 | lock.unlock() 39 | } 40 | return ds 41 | } 42 | 43 | static void dropAllAndDispose() { 44 | if(ds == null) { 45 | return 46 | } 47 | dropAllAndDispose(ds) 48 | } 49 | static void dropAllAndDispose(JdbcConnectionPool ds) { 50 | ds.connection.withCloseable { conn -> 51 | dropAllTables(conn) 52 | } 53 | } 54 | 55 | static void dropAllTables(Connection connection,String catalog=null) { 56 | Sql gsql = new Sql(connection) 57 | DDLUtils.allTables(connection, catalog) 58 | .printTable() 59 | .filter { it.TABLE_TYPE == 'TABLE' || (it.TABLE_TYPE == 'BASE TABLE' && it.TABLE_SCHEM == 'PUBLIC') } 60 | .each { 61 | println("Dropping******** $it.TABLE_NAME") 62 | gsql.execute("drop table if exists \"$it.TABLE_NAME\"" as String) 63 | } 64 | 65 | } 66 | } -------------------------------------------------------------------------------- /src/main/groovy/fuzzycsv/rdbms/DDLUtils.java: -------------------------------------------------------------------------------- 1 | package fuzzycsv.rdbms; 2 | 3 | import fuzzycsv.FuzzyCSVTable; 4 | import fuzzycsv.FuzzyCSVUtils; 5 | 6 | import java.io.Reader; 7 | import java.sql.Clob; 8 | import java.sql.Connection; 9 | import java.sql.ResultSet; 10 | import java.sql.SQLException; 11 | 12 | public class DDLUtils { 13 | public static FuzzyCSVTable allTables(Connection connection, String catalog, String schemaPattern) throws SQLException { 14 | ResultSet tables = connection.getMetaData().getTables(catalog, schemaPattern, null, new String[]{"TABLE"}); 15 | return toTable(tables); 16 | 17 | } 18 | 19 | public static FuzzyCSVTable allTables(Connection connection, String catalog) throws SQLException { 20 | return DDLUtils.allTables(connection, catalog, null); 21 | } 22 | 23 | public static FuzzyCSVTable allTables(Connection connection) throws SQLException { 24 | return DDLUtils.allTables(connection, null, null); 25 | } 26 | 27 | public static boolean tableExists(Connection connection, String tableName) throws SQLException { 28 | ResultSet tables = connection.getMetaData().getTables(null, null, tableName, new String[]{"TABLE"}); 29 | 30 | return !toTable(tables).isEmpty(); 31 | } 32 | 33 | public static FuzzyCSVTable allColumns(Connection connection, String tableName) throws SQLException { 34 | ResultSet columns = connection.getMetaData().getColumns(null, null, tableName, null); 35 | return toTable(columns); 36 | } 37 | 38 | private static FuzzyCSVTable toTable(ResultSet resultSet) { 39 | try { 40 | return FuzzyCSVTable.fromResultSet(resultSet); 41 | } finally { 42 | FuzzyCSVUtils.closeQuietly(resultSet); 43 | } 44 | 45 | } 46 | 47 | public static String clobToString(Clob object) throws SQLException { 48 | try { 49 | return object.getSubString(1, (int) object.length()); 50 | } catch (Exception ignore) { 51 | Reader stream = object.getCharacterStream(0l, object.length()); 52 | if (stream != null) { 53 | return FuzzyCSVUtils.toString(stream); 54 | } 55 | 56 | } 57 | return null; 58 | } 59 | 60 | } 61 | -------------------------------------------------------------------------------- /src/main/groovy/fuzzycsv/FuzzyStaticApi.groovy: -------------------------------------------------------------------------------- 1 | package fuzzycsv 2 | 3 | import fuzzycsv.javaly.Fx1 4 | import fuzzycsv.javaly.Fx2 5 | import groovy.transform.CompileStatic 6 | import groovy.transform.stc.ClosureParams 7 | import groovy.transform.stc.SimpleType 8 | 9 | /** 10 | * helper class consolidating all commonly used methods 11 | */ 12 | @CompileStatic 13 | class FuzzyStaticApi { 14 | 15 | static Count count(Object... columnsForCounting) { 16 | return new Count(columns: columnsForCounting as List) 17 | } 18 | 19 | static Count countUnique(Object... columnsForCounting) { 20 | return new Count(unique: true, columns: columnsForCounting as List) 21 | } 22 | 23 | static Sum sum(Object... aggregateColumns) { 24 | Sum.sum(aggregateColumns) 25 | } 26 | 27 | static Reducer reduce(Closure fx) { 28 | return new Reducer(fx) 29 | } 30 | 31 | static Aggregator reduce(String column, Closure fx) { 32 | return reduce(fx).az(column) 33 | } 34 | 35 | static Number num(def obj) { 36 | return FuzzyCSVUtils.coerceToNumber(obj) 37 | } 38 | 39 | static List nums(List list) { 40 | return FuzzyCSVUtils.toNumbers(list) 41 | } 42 | 43 | /** 44 | Record function with coercion ON -> SLOWER 45 | * @param function 46 | */ 47 | static RecordFx fn(@ClosureParams(value = SimpleType.class, options = "fuzzycsv.Record") Closure function) { 48 | return RecordFx.fn(function) 49 | } 50 | 51 | static RecordFx fn(String name, @ClosureParams(value = SimpleType.class, options = "fuzzycsv.Record") Closure function) { 52 | return RecordFx.fn(name, function) 53 | } 54 | 55 | /** 56 | * Record function with coercion OFF -> FASTER 57 | * @param function 58 | */ 59 | static RecordFx fx(Fx1 function) { 60 | return RecordFx.fx(function) 61 | } 62 | 63 | static RecordFx fx(String name, Fx1 function) { 64 | return RecordFx.fx(name, function) 65 | } 66 | 67 | static FuzzyCSVTable tbl(List csv = [[]]) { 68 | return FuzzyCSVTable.tbl(csv) 69 | } 70 | 71 | static SpreadConfig spreader(Object col, Fx2 nameGen) { 72 | new SpreadConfig().withCol(col).withNameGenFn(nameGen) 73 | } 74 | 75 | 76 | 77 | 78 | } 79 | -------------------------------------------------------------------------------- /src/main/groovy/fuzzycsv/Sort.java: -------------------------------------------------------------------------------- 1 | package fuzzycsv; 2 | 3 | import fuzzycsv.javaly.Fx1; 4 | import fuzzycsv.javaly.Fx2; 5 | import lombok.AllArgsConstructor; 6 | import lombok.With; 7 | import org.codehaus.groovy.runtime.NumberAwareComparator; 8 | 9 | import java.util.Arrays; 10 | import java.util.Comparator; 11 | 12 | 13 | @SuppressWarnings({"unchecked", "rawtypes"}) 14 | @AllArgsConstructor 15 | public class Sort { 16 | 17 | 18 | private static final NumberAwareComparator numberAwareComparator = new NumberAwareComparator(); 19 | private Comparator comparator; 20 | @With 21 | private boolean ascending; 22 | 23 | 24 | public static Sort[] byColumns(String... names) { 25 | return Arrays.stream(names).map(Sort::byColumn).toArray(Sort[]::new); 26 | } 27 | 28 | public static Sort[] byColumns(int... colIdx) { 29 | return Arrays.stream(colIdx).mapToObj(Sort::byColumn).toArray(Sort[]::new); 30 | 31 | } 32 | 33 | public static Sort byColumn(String name) { 34 | return byFx(r -> r.get(name)); 35 | } 36 | 37 | 38 | public static Sort byColumn(int colIdx) { 39 | return byFx(r -> r.get(colIdx)); 40 | } 41 | 42 | public static Sort byFx(Fx1 fx) { 43 | Comparator comparator1 = (Record a, Record b) -> { 44 | try { 45 | Object v1 = fx.call(a); 46 | Object v2 = fx.call(b); 47 | return numberAwareComparator.compare(v1, v2); 48 | } catch (Exception e) { 49 | throw FuzzyCsvException.wrap(e); 50 | } 51 | }; 52 | return new Sort(comparator1, true); 53 | } 54 | 55 | public static Sort byComparing(Fx2 comparator) { 56 | Comparator cmp = (Record a, Record b) -> { 57 | try { 58 | return comparator.call(a, b); 59 | } catch (Exception e) { 60 | throw FuzzyCsvException.wrap(e); 61 | } 62 | }; 63 | return new Sort(cmp, true); 64 | } 65 | 66 | 67 | public Sort asc() { 68 | return withAscending(true); 69 | } 70 | 71 | public Sort desc() { 72 | return withAscending(false); 73 | } 74 | 75 | Comparator toComparator() { 76 | return ascending ? comparator : comparator.reversed(); 77 | } 78 | 79 | 80 | } 81 | -------------------------------------------------------------------------------- /src/main/groovy/fuzzycsv/javaly/FxUtils.java: -------------------------------------------------------------------------------- 1 | package fuzzycsv.javaly; 2 | 3 | import fuzzycsv.Record; 4 | import fuzzycsv.RecordFx; 5 | import groovy.lang.Closure; 6 | 7 | public class FxUtils { 8 | 9 | private FxUtils() { 10 | } 11 | 12 | public static Closure toCls(Fx1 fx) { 13 | return new Closure(null) { 14 | @SuppressWarnings("unchecked") 15 | public R call(Object... args) { 16 | try { 17 | return fx.call((T) args[0]); 18 | } catch (Exception e) { 19 | return sneakyThrow(e); 20 | } 21 | } 22 | 23 | @Override 24 | public int getMaximumNumberOfParameters() { 25 | return 1; 26 | } 27 | }; 28 | } 29 | 30 | public static Closure toCls(Fx2 fx) { 31 | return new Closure(null) { 32 | @SuppressWarnings("unchecked") 33 | public R call(Object... args) { 34 | try { 35 | return fx.call((T1) args[0], (T2) args[1]); 36 | } catch (Exception e) { 37 | return sneakyThrow(e); 38 | } 39 | } 40 | 41 | @Override 42 | public int getMaximumNumberOfParameters() { 43 | return 2; 44 | } 45 | }; 46 | } 47 | 48 | public static Closure toCls(Fx3 fx) { 49 | return new Closure(null) { 50 | @SuppressWarnings("unchecked") 51 | public R call(Object... args) { 52 | try { 53 | return fx.call((T1) args[0], (T2) args[1], (T3) args[2]); 54 | } catch (Exception e) { 55 | return sneakyThrow(e); 56 | } 57 | } 58 | 59 | @Override 60 | public int getMaximumNumberOfParameters() { 61 | return 3; 62 | } 63 | }; 64 | } 65 | 66 | public static RecordFx recordFx(String name, Fx1 fx) { 67 | return RecordFx.fx(name, fx); 68 | } 69 | 70 | public static RecordFx recordFx(Fx1 fx) { 71 | return recordFx(null, fx); 72 | } 73 | 74 | 75 | static Any sneakyThrow(Throwable t) throws T { 76 | throw (T) t; 77 | } 78 | 79 | static void rethrow(Throwable t) { 80 | throw new RuntimeException(t); 81 | } 82 | 83 | public T cast(Object obj) { 84 | return (T) obj; 85 | } 86 | 87 | public static T cast(Object obj, Class type) { 88 | return (T) obj; 89 | } 90 | } 91 | -------------------------------------------------------------------------------- /src/test/groovy/fuzzycsv/SumTest.groovy: -------------------------------------------------------------------------------- 1 | package fuzzycsv 2 | 3 | import org.junit.Test 4 | 5 | import static fuzzycsv.RecordFx.fn 6 | import static fuzzycsv.Sum.sum 7 | 8 | class SumTest { 9 | 10 | @Test 11 | void testSumWithMultiColumns() { 12 | def sumFx = new Sum(columns: ['ps_total_score', 'pipes_total_score', 'tap_total_score'], columnName: 'sum', data: Data.csv) 13 | assert sumFx.value == 31.1 14 | assert sumFx.columnName == 'sum' 15 | 16 | //test fluent 17 | sumFx = sum('ps_total_score', 'pipes_total_score', 'tap_total_score').az('sum') 18 | sumFx.data = Data.csv 19 | 20 | assert sumFx.value == 31.1 21 | assert sumFx.columnName == 'sum' 22 | } 23 | 24 | @Test 25 | void testSumFunctionsColumns() { 26 | FxExtensions.treatNullAsZero() 27 | use(FxExtensions) { 28 | def sumFx = new Sum(columns: [fn { 29 | it.'ps_total_score' + it.'pipes_total_score' + it.'tap_total_score' 30 | }], columnName: 'sum', data: Data.csv) 31 | assert sumFx.value == 31.1 32 | assert sumFx.columnName == 'sum' 33 | 34 | //test fluent 35 | sumFx = sum('ps_total_score', 'pipes_total_score', 'tap_total_score').az('sum') 36 | sumFx.data = Data.csv 37 | 38 | assert sumFx.value == 31.1 39 | assert sumFx.columnName == 'sum' 40 | } 41 | } 42 | 43 | @Test 44 | void testSumFunctionsColumnsWithStaticApi() { 45 | FxExtensions.treatNullAsZero() 46 | def sumFx = new Sum(columns: [fn { 47 | it.'ps_total_score' + it.'pipes_total_score' + it.'tap_total_score' 48 | }], columnName: 'sum', data: Data.csv) 49 | 50 | def tb = FuzzyCSVTable.tbl(Data.csv) 51 | 52 | use(FxExtensions) { 53 | assert tb.aggregate( 54 | RecordFx.fx { 'sas' }.az('asas'), 55 | FuzzyStaticApi.sum(fn { 56 | it.'ps_total_score' + it.'pipes_total_score' + it.'tap_total_score' 57 | }))[1][1] == 31.1 58 | assert sumFx.value == 31.1 59 | assert sumFx.columnName == 'sum' 60 | } 61 | 62 | //test fluent 63 | sumFx = sum('ps_total_score', 'pipes_total_score', 'tap_total_score').az('sum') 64 | sumFx.data = Data.csv 65 | 66 | assert sumFx.value == 31.1 67 | assert sumFx.columnName == 'sum' 68 | } 69 | 70 | @Test 71 | void testName() { 72 | def count = new Sum(['a'], null) 73 | assert count.getColumnName() == 'sum(a)' 74 | 75 | count = new Sum(['a', 'b'], null) 76 | assert count.getColumnName() == 'sum(a,b)' 77 | 78 | 79 | count = new Sum(['a', 'b'], null).az('xxx') 80 | assert count.getColumnName() == 'xxx' 81 | } 82 | } 83 | -------------------------------------------------------------------------------- /src/test/groovy/fuzzycsv/FuzzyCsvExtensionMethodsTest.groovy: -------------------------------------------------------------------------------- 1 | package fuzzycsv 2 | 3 | import org.junit.jupiter.api.Test 4 | 5 | class FuzzyCsvExtensionMethodsTest { 6 | 7 | 8 | def data = FuzzyCSVTable.tbl([ 9 | ['name', 'age'], 10 | ['kay', null], 11 | ['rok', 5], 12 | ]) 13 | 14 | 15 | @Test 16 | void testGetAt() { 17 | assert data[1].name == 'kay' 18 | assert data[2].name == 'rok' 19 | } 20 | 21 | static class Record22 { 22 | String name 23 | Integer age 24 | 25 | public Record22 set(String name, Object value) { 26 | println "set($name, $value)" 27 | return this 28 | } 29 | 30 | public Record22 get(String name) { 31 | println "get($name)" 32 | return this 33 | } 34 | } 35 | @Test 36 | void testUpdating() { 37 | assert data[1].age == null 38 | data[1].age = 23 39 | assert data[1].age == 23 40 | } 41 | 42 | @Test 43 | void testUpdatingWithAtNotation() { 44 | assert data[1]['age'] == null 45 | data[1]['age'] = 23 46 | assert data[1]['age'] == 23 47 | } 48 | 49 | @Test 50 | void testLeftShift() { 51 | def data2 = FuzzyCSVTable.tbl([ 52 | ['gender'], 53 | ['m'], 54 | ['f'], 55 | ]) 56 | 57 | def result = data << data2 58 | 59 | def expected = FuzzyCSVTable.tbl([ 60 | ['name', 'age', 'gender'], 61 | ['kay', null, null], 62 | ['rok', 5, null], 63 | [null, null, 'm'], 64 | [null, null, 'f'], 65 | ]) 66 | 67 | assert result == expected 68 | } 69 | 70 | @Test 71 | void testPlus() { 72 | def data2 = FuzzyCSVTable.tbl([ 73 | ['a', 'b', 'c'], 74 | [1, 2, 3], 75 | [4, 5, 6], 76 | [7, 8, 9] 77 | ]) 78 | 79 | def result = data + data2 80 | 81 | def expected = FuzzyCSVTable.tbl([ 82 | ['name', 'age'], 83 | ['kay', null], 84 | ['rok', 5], 85 | [1, 2, 3], 86 | [4, 5, 6], 87 | [7, 8, 9] 88 | ]) 89 | 90 | assert result == expected 91 | } 92 | 93 | @Test 94 | void testGetAtRange(){ 95 | def data2 = FuzzyCSVTable.tbl([ 96 | ['a', 'b', 'c'], 97 | [1, 2, 3], 98 | [4, 5, 6], 99 | [7, 8, 9] 100 | ]) 101 | 102 | def result = data2[1..-2] 103 | 104 | def expected = FuzzyCSVTable.tbl([ 105 | ['a', 'b', 'c'], 106 | [1, 2, 3], 107 | [4, 5, 6] 108 | ]) 109 | 110 | assert result == expected 111 | } 112 | } 113 | -------------------------------------------------------------------------------- /gradlew.bat: -------------------------------------------------------------------------------- 1 | @rem 2 | @rem Copyright 2015 the original author or authors. 3 | @rem 4 | @rem Licensed under the Apache License, Version 2.0 (the "License"); 5 | @rem you may not use this file except in compliance with the License. 6 | @rem You may obtain a copy of the License at 7 | @rem 8 | @rem https://www.apache.org/licenses/LICENSE-2.0 9 | @rem 10 | @rem Unless required by applicable law or agreed to in writing, software 11 | @rem distributed under the License is distributed on an "AS IS" BASIS, 12 | @rem WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 13 | @rem See the License for the specific language governing permissions and 14 | @rem limitations under the License. 15 | @rem 16 | 17 | @if "%DEBUG%" == "" @echo off 18 | @rem ########################################################################## 19 | @rem 20 | @rem Gradle startup script for Windows 21 | @rem 22 | @rem ########################################################################## 23 | 24 | @rem Set local scope for the variables with windows NT shell 25 | if "%OS%"=="Windows_NT" setlocal 26 | 27 | set DIRNAME=%~dp0 28 | if "%DIRNAME%" == "" set DIRNAME=. 29 | set APP_BASE_NAME=%~n0 30 | set APP_HOME=%DIRNAME% 31 | 32 | @rem Resolve any "." and ".." in APP_HOME to make it shorter. 33 | for %%i in ("%APP_HOME%") do set APP_HOME=%%~fi 34 | 35 | @rem Add default JVM options here. You can also use JAVA_OPTS and GRADLE_OPTS to pass JVM options to this script. 36 | set DEFAULT_JVM_OPTS="-Xmx64m" "-Xms64m" 37 | 38 | @rem Find java.exe 39 | if defined JAVA_HOME goto findJavaFromJavaHome 40 | 41 | set JAVA_EXE=java.exe 42 | %JAVA_EXE% -version >NUL 2>&1 43 | if "%ERRORLEVEL%" == "0" goto execute 44 | 45 | echo. 46 | echo ERROR: JAVA_HOME is not set and no 'java' command could be found in your PATH. 47 | echo. 48 | echo Please set the JAVA_HOME variable in your environment to match the 49 | echo location of your Java installation. 50 | 51 | goto fail 52 | 53 | :findJavaFromJavaHome 54 | set JAVA_HOME=%JAVA_HOME:"=% 55 | set JAVA_EXE=%JAVA_HOME%/bin/java.exe 56 | 57 | if exist "%JAVA_EXE%" goto execute 58 | 59 | echo. 60 | echo ERROR: JAVA_HOME is set to an invalid directory: %JAVA_HOME% 61 | echo. 62 | echo Please set the JAVA_HOME variable in your environment to match the 63 | echo location of your Java installation. 64 | 65 | goto fail 66 | 67 | :execute 68 | @rem Setup the command line 69 | 70 | set CLASSPATH=%APP_HOME%\gradle\wrapper\gradle-wrapper.jar 71 | 72 | 73 | @rem Execute Gradle 74 | "%JAVA_EXE%" %DEFAULT_JVM_OPTS% %JAVA_OPTS% %GRADLE_OPTS% "-Dorg.gradle.appname=%APP_BASE_NAME%" -classpath "%CLASSPATH%" org.gradle.wrapper.GradleWrapperMain %* 75 | 76 | :end 77 | @rem End local scope for the variables with windows NT shell 78 | if "%ERRORLEVEL%"=="0" goto mainEnd 79 | 80 | :fail 81 | rem Set variable GRADLE_EXIT_CONSOLE if you need the _script_ return code instead of 82 | rem the _cmd.exe /c_ return code! 83 | if not "" == "%GRADLE_EXIT_CONSOLE%" exit 1 84 | exit /b 1 85 | 86 | :mainEnd 87 | if "%OS%"=="Windows_NT" endlocal 88 | 89 | :omega 90 | -------------------------------------------------------------------------------- /src/test/groovy/fuzzycsv/rdbms/FuzzyCsvInserterTest.groovy: -------------------------------------------------------------------------------- 1 | package fuzzycsv.rdbms 2 | 3 | import fuzzycsv.FuzzyCSVTable 4 | import fuzzycsv.rdbms.stmt.DefaultSqlRenderer 5 | import org.codehaus.groovy.runtime.DefaultGroovyMethods 6 | import org.junit.Assert 7 | import org.junit.Test 8 | 9 | class FuzzyCsvInserterTest { 10 | 11 | @Test 12 | void testGenerateInsert() { 13 | 14 | FuzzyCSVTable table = FuzzyCSVTable.withHeader("h1", "h2", "h3") 15 | .addRow(1, "2", "P") 16 | .addRow(1.2, "2.2", "P.2") 17 | .addRow(1.3, "2.3", "P.3") 18 | 19 | def result = FuzzyCsvDbInserter.generateInsert(DefaultSqlRenderer.getInstance(),table, "my_table") 20 | 21 | Assert.assertEquals("INSERT INTO `my_table`\n" + 22 | " (`h1`, `h2`, `h3`) \n" + 23 | "VALUES\n" + 24 | "(?, ?, ?),\n" + 25 | "(?, ?, ?),\n" + 26 | "(?, ?, ?)", result.getKey()) 27 | 28 | Assert.assertEquals("[1, '2', 'P', 1.2, '2.2', 'P.2', 1.3, '2.3', 'P.3']", DefaultGroovyMethods.inspect(result.getValue())) 29 | 30 | } 31 | 32 | @Test 33 | void testGenerateUpdate() { 34 | 35 | FuzzyCSVTable table = FuzzyCSVTable.withHeader("h1", "h2", "h3") 36 | .addRow(1, "2", "P") 37 | .addRow(1.2, "2.2", "P.2") 38 | .addRow(1.3, "2.3", "P.3") 39 | 40 | def pairs = FuzzyCsvDbInserter.generateUpdate(DefaultSqlRenderer.instance,table, "my_table", "h1") 41 | 42 | 43 | pairs.each { p -> 44 | Assert.assertEquals("UPDATE `my_table`\n" + 45 | "SET\n" + 46 | " `h2` = ?,\n" + 47 | " `h3` = ?\n" + 48 | " WHERE `h1` = ?", p.getKey()) 49 | } 50 | 51 | 52 | int i = 0 53 | Assert.assertEquals("['2', 'P', 1]", DefaultGroovyMethods.inspect(pairs.get(i++).getValue())) 54 | Assert.assertEquals("['2.2', 'P.2', 1.2]", DefaultGroovyMethods.inspect(pairs.get(i++).getValue())) 55 | Assert.assertEquals("['2.3', 'P.3', 1.3]", DefaultGroovyMethods.inspect(pairs.get(i).getValue())) 56 | 57 | } 58 | 59 | @Test 60 | void testGenerateUpdate2() { 61 | 62 | FuzzyCSVTable table = FuzzyCSVTable.withHeader("h1", "h2", "h3") 63 | .addRow(1, "2", "P") 64 | .addRow(1.2, "2.2", "P.2") 65 | .addRow(1.3, "2.3", "P.3") 66 | 67 | def pairs = FuzzyCsvDbInserter.generateUpdate(DefaultSqlRenderer.instance,table, "my_table", "h1", "h3") 68 | 69 | 70 | pairs.each { p -> 71 | Assert.assertEquals("UPDATE `my_table`\n" + 72 | "SET\n" + 73 | " `h2` = ?\n" + 74 | " WHERE `h1` = ? AND `h3` = ?", p.getKey()) 75 | } 76 | 77 | 78 | int i = 0 79 | Assert.assertEquals("['2', 1, 'P']", DefaultGroovyMethods.inspect(pairs.get(i++).getValue())) 80 | Assert.assertEquals("['2.2', 1.2, 'P.2']", DefaultGroovyMethods.inspect(pairs.get(i++).getValue())) 81 | Assert.assertEquals("['2.3', 1.3, 'P.3']", DefaultGroovyMethods.inspect(pairs.get(i++).getValue())) 82 | 83 | } 84 | } -------------------------------------------------------------------------------- /src/test/groovy/fuzzycsv/RecordTest.groovy: -------------------------------------------------------------------------------- 1 | package fuzzycsv 2 | 3 | 4 | import org.junit.Before 5 | import org.junit.Ignore 6 | import org.junit.Test 7 | 8 | import static groovy.test.GroovyAssert.shouldFail 9 | 10 | class RecordTest { 11 | 12 | def sourceHeader = ['name', 'sex'] 13 | def sourceRecord = ['kay', 'male'] 14 | 15 | def derivedHeader = ['name', 'age'] 16 | def derivedRecord = ['ron', 12] 17 | 18 | @Before 19 | void tearDown() { 20 | FuzzyCSV.THROW_EXCEPTION_ON_ABSENT_COLUMN.set(true) 21 | } 22 | 23 | @Test 24 | void testPropertyMissing() { 25 | Record record = new Record(derivedHeader, derivedRecord) 26 | record.leftHeaders = sourceHeader 27 | record.leftRecord = sourceRecord 28 | 29 | assert record.name == 'ron' 30 | assert record.left('name') == 'kay' 31 | // assert record.'@name' == 'kay' 32 | assert record.age == 12 33 | assert record.sex == 'male' 34 | assert record.left('sex') == 'male' 35 | 36 | shouldFail(IllegalArgumentException) { 37 | record.blah 38 | } 39 | 40 | } 41 | 42 | @Test 43 | void testValue() { 44 | Record record = new Record(derivedHeader, ['ron', null]) 45 | record.leftHeaders = sourceHeader 46 | record.leftRecord = sourceRecord 47 | 48 | shouldFail(IllegalArgumentException) { record.require('blah') } 49 | assert record.require('name') == 'ron' 50 | assert new Record(derivedHeader, ['ron', 10]).require('age') == 10 51 | shouldFail(IllegalStateException) { record.require('age') == null } 52 | assert record.require('age', 10) == 10 53 | } 54 | 55 | @Test 56 | @Ignore("no longer support global leniency") 57 | void testAbsentColumn() { 58 | Record record = new Record(derivedHeader, ['ron', null]) 59 | 60 | shouldFail(IllegalArgumentException) { record.require('blah') } 61 | 62 | //SILENT MODE ON RECORD 63 | assert record.withSilentMode { val("blah") } == null 64 | assert record.silentVal('blah') == null 65 | 66 | //GENERAL SILENT MODE 67 | FuzzyCSV.THROW_EXCEPTION_ON_ABSENT_COLUMN.set(false) 68 | record.silentModeDefault() 69 | assert record.get('blah') == null 70 | assert record.silentVal('blah') == null 71 | 72 | //OVERRIDING SILENT MODE 73 | shouldFail(IllegalArgumentException) { 74 | record.silentModeOff() 75 | record.eval('blah') == null 76 | } 77 | 78 | } 79 | 80 | @Test 81 | void test_aZeroShouldNotBeResolvedToFalse() { 82 | Record r = new Record(['a', 'b'], [1, 2]) 83 | r.rightHeaders = ['d', 'e'] 84 | r.rightRecord = [0, 0] 85 | 86 | assert r.d == 0 87 | // rt r.xxxx == 0 88 | } 89 | 90 | @Test 91 | void test_toMap() { 92 | Record r = new Record(['a', 'b', 'c'], [1, 2, 3]) 93 | assert [a: 1, b: 2, c: 3] == r.toMap() 94 | assert [a: 1, b: 2] == r.toMap('a', 'b') 95 | assert [a: 1] == r.toMap('a') 96 | 97 | } 98 | 99 | 100 | } 101 | -------------------------------------------------------------------------------- /src/test/groovy/fuzzycsv/rdbms/DbColumnSyncTest.groovy: -------------------------------------------------------------------------------- 1 | package fuzzycsv.rdbms 2 | 3 | import fuzzycsv.FuzzyCSV 4 | import fuzzycsv.FuzzyCSVTable 5 | import fuzzycsv.H2DbHelper 6 | import fuzzycsv.Record 7 | import fuzzycsv.rdbms.stmt.DefaultSqlRenderer 8 | import groovy.sql.Sql 9 | 10 | import java.sql.SQLException 11 | 12 | class DbColumnSyncTest { 13 | { 14 | FuzzyCSV.ACCURACY_THRESHOLD.set(1) 15 | } 16 | 17 | List lastSql = [] 18 | def sql = new Sql(H2DbHelper.connection.connection) { 19 | @Override 20 | boolean execute(String sql) throws SQLException { 21 | lastSql.add(sql) 22 | return false 23 | } 24 | } 25 | 26 | DbColumnSync d = new DbColumnSync(gSql: sql, tableName: 'mytable',sqlRenderer: DefaultSqlRenderer.instance) 27 | 28 | //region VARCHAR TESTS 29 | void testAdjustVarChar() { 30 | 31 | def table = FuzzyCSVTable.tbl([['col1'], ['XXX']]) 32 | 33 | d.table = table 34 | 35 | def record = new Record( 36 | ['COLUMN_NAME', 'TYPE_NAME', 'COLUMN_SIZE', 'DECIMAL_SIZE'], 37 | ['col1', 'varchar', 255, 0]) 38 | 39 | d.adjust(record) 40 | assert lastSql.isEmpty() 41 | } 42 | 43 | void testVarcharNull() { 44 | def table = FuzzyCSVTable.tbl([['col1'], [null]]) 45 | 46 | d.table = table 47 | 48 | def record = new Record( 49 | ['COLUMN_NAME', 'TYPE_NAME', 'COLUMN_SIZE', 'DECIMAL_SIZE'], 50 | ['col1', 'varchar', 255, 0]) 51 | 52 | d.adjust(record) 53 | assert lastSql.isEmpty() 54 | } 55 | 56 | void testVarchar987() { 57 | def table = FuzzyCSVTable.tbl([['col1'], ['xx'], ['X' * 987]]) 58 | 59 | d.table = table 60 | 61 | def record = new Record( 62 | ['COLUMN_NAME', 'TYPE_NAME', 'COLUMN_SIZE', 'DECIMAL_SIZE'], 63 | ['col1', 'varchar', 255, 0]) 64 | 65 | d.adjust(record) 66 | assert lastSql.contains('ALTER TABLE `mytable` ALTER COLUMN `col1` varchar(987) ;') 67 | } 68 | 69 | void testVarcharToText() { 70 | def table = FuzzyCSVTable.tbl([['col1'], ['xx'], ['X' * 10_001]]) 71 | 72 | d.table = table 73 | 74 | def record = new Record( 75 | ['COLUMN_NAME', 'TYPE_NAME', 'COLUMN_SIZE', 'DECIMAL_SIZE'], 76 | ['col1', 'varchar', 255, 0]) 77 | 78 | d.adjust(record) 79 | assert lastSql.contains('ALTER TABLE `mytable` ALTER COLUMN `col1` text ;') 80 | } 81 | //endregion 82 | 83 | //region DECIMAL TESTS 84 | void testAdjustDecimal() { 85 | def table = FuzzyCSVTable.tbl([['col1'], [5.0], [700.00]]) 86 | 87 | d.table = table 88 | 89 | def record = new Record( 90 | ['COLUMN_NAME', 'TYPE_NAME', 'COLUMN_SIZE', 'DECIMAL_DIGITS'], 91 | ['col1', 'decimal', 1, 2]) 92 | 93 | d.adjust(record) 94 | assert lastSql.contains('ALTER TABLE `mytable` ALTER COLUMN `col1` decimal(5, 2) ;') 95 | } 96 | 97 | void testDecimalNull() { 98 | def table = FuzzyCSVTable.tbl([['col1'], [null]]) 99 | 100 | d.table = table 101 | 102 | def record = new Record( 103 | ['COLUMN_NAME', 'TYPE_NAME', 'COLUMN_SIZE', 'DECIMAL_SIZE'], 104 | ['col1', 'decimal', 255, 0]) 105 | 106 | d.adjust(record) 107 | assert lastSql.isEmpty() 108 | } 109 | 110 | //endregion 111 | } 112 | -------------------------------------------------------------------------------- /src/main/groovy/fuzzycsv/CSVToExcel.groovy: -------------------------------------------------------------------------------- 1 | package fuzzycsv 2 | 3 | import groovy.transform.CompileStatic 4 | import org.apache.poi.ss.usermodel.* 5 | import org.apache.poi.xssf.streaming.SXSSFWorkbook 6 | import org.slf4j.Logger 7 | import org.slf4j.LoggerFactory 8 | 9 | @CompileStatic 10 | class CSVToExcel { 11 | 12 | private static Logger log = LoggerFactory.getLogger(CSVToExcel) 13 | 14 | static void exportToExcelFile(Map csvMap, String filePath) { 15 | def file = new File(filePath) 16 | def excel = exportToExcel(csvMap) 17 | file.withOutputStream { OutputStream o -> excel.write(o) } 18 | 19 | } 20 | 21 | static Workbook exportToExcel(Map csvMap) { 22 | 23 | def workbook = new SXSSFWorkbook() 24 | 25 | def ec = new XlsExportContext(workbook) 26 | 27 | csvMap.forEach { String name, FuzzyCSVTable csv -> 28 | Sheet sheet = workbook.createSheet(name) 29 | writeExcel(csv, sheet, ec) 30 | } 31 | 32 | return workbook 33 | } 34 | 35 | private static void writeExcel(FuzzyCSVTable csv, Sheet sheet, XlsExportContext ec) { 36 | csv.csv.eachWithIndex { r, i -> 37 | Row row = sheet.createRow(i) 38 | def size = r.size() 39 | for (int j = 0; j < size; j++) { 40 | def value = r.get(j) 41 | def cell = row.createCell(j) 42 | setCellData(value, cell, ec) 43 | } 44 | 45 | } 46 | } 47 | 48 | 49 | private static class XlsExportContext { 50 | SXSSFWorkbook wb 51 | CellStyle dateStyle 52 | 53 | XlsExportContext(SXSSFWorkbook wb) { 54 | this.wb = wb 55 | init() 56 | } 57 | 58 | private def init() { 59 | def createHelper = wb.getCreationHelper() 60 | dateStyle = wb.createCellStyle() 61 | dateStyle.setDataFormat( 62 | //createHelper.createDataFormat().getFormat("MMMM dd, yyyy")) 63 | createHelper.createDataFormat().getFormat("yy-mmm-d h:mm")) 64 | return this 65 | } 66 | 67 | } 68 | 69 | private static void setCellData(Object dataValue, Cell cell, XlsExportContext ec) { 70 | try { 71 | 72 | if (dataValue == null) { 73 | cell.setBlank(); 74 | return 75 | } 76 | 77 | switch (dataValue) { 78 | case Number: 79 | def data = ((Number) dataValue).toDouble() 80 | cell.setCellValue(data) 81 | break 82 | case Calendar: 83 | case Date: 84 | cell.setCellStyle(ec.dateStyle) 85 | cell.setCellValue((Date) dataValue) 86 | break 87 | case String: 88 | cell.setCellValue((String) dataValue) 89 | break 90 | case Boolean: 91 | cell.setCellValue((boolean) dataValue) 92 | break 93 | case byte[]: 94 | cell.setCellValue('[BINARY_DATA]') 95 | break 96 | default: 97 | cell.setCellValue(dataValue?.toString()) 98 | } 99 | 100 | } catch (Exception ex) { 101 | log.error("Could not set cell data [$ex.message]") 102 | cell.setCellErrorValue(FormulaError.NA.getCode()); 103 | } 104 | } 105 | 106 | 107 | } 108 | -------------------------------------------------------------------------------- /src/main/groovy/fuzzycsv/nav/ExIterator.groovy: -------------------------------------------------------------------------------- 1 | package fuzzycsv.nav 2 | 3 | import fuzzycsv.FuzzyCSVTable 4 | import fuzzycsv.javaly.Fx1 5 | import fuzzycsv.javaly.VoidFx1 6 | import groovy.transform.CompileStatic 7 | import groovy.transform.PackageScope 8 | import groovy.transform.stc.ClosureParams 9 | import groovy.transform.stc.FirstParam 10 | import groovy.transform.stc.SimpleType 11 | 12 | import java.util.concurrent.atomic.AtomicInteger 13 | import java.util.stream.Stream 14 | import java.util.stream.StreamSupport 15 | 16 | @CompileStatic 17 | class IterHelper { 18 | static T last(Iterator iter) { 19 | T tmp = null 20 | while (iter.hasNext()) tmp = iter.next() 21 | return tmp 22 | } 23 | 24 | } 25 | 26 | @CompileStatic 27 | abstract class ExIterator implements Iterator { 28 | E last() { 29 | return IterHelper.last(this) 30 | } 31 | 32 | SELF skip() { 33 | return skip(1) 34 | } 35 | 36 | SELF skip(int steps) { 37 | 38 | if (!hasNext()) { 39 | throw new NoSuchElementException("no element to skip") 40 | } 41 | 42 | def j = this 43 | steps.times { Integer i -> 44 | j.next() 45 | } 46 | 47 | return this as SELF 48 | } 49 | 50 | Stream stream() { 51 | if (hasNext()) { 52 | return StreamSupport.stream(Spliterators.spliteratorUnknownSize(this, Spliterator.ORDERED) as Spliterator, false) 53 | } 54 | return Stream.empty() 55 | } 56 | } 57 | 58 | 59 | @CompileStatic 60 | class NavIterator extends ExIterator { 61 | private static AtomicInteger i = new AtomicInteger() 62 | 63 | Navigator curr 64 | FuzzyCSVTable table 65 | FuzzyCSVTable pathTrack 66 | private boolean selfFinished = false 67 | private boolean markPath = false 68 | private Closure stopper 69 | private Closure next 70 | 71 | //internal use 72 | private Integer id = i.incrementAndGet() 73 | private int steps = 0 74 | 75 | static NavIterator from(Navigator curr, FuzzyCSVTable table = curr.table) { 76 | return new NavIterator(curr: curr.table(table), table: table) 77 | } 78 | 79 | @PackageScope 80 | NavIterator withStopper(@ClosureParams(value = SimpleType, options = ["fuzzycsv.FuzzyCSVTable", "fuzzycsv.nav.Navigator"]) Closure stopper) { 81 | this.stopper = stopper 82 | return this 83 | } 84 | 85 | @PackageScope 86 | NavIterator withStepper(@ClosureParams(FirstParam.FirstGenericType) Closure next) { 87 | this.next = next 88 | return this 89 | } 90 | 91 | NavIterator markPath(FuzzyCSVTable t = table.copy()) { 92 | markPath = true 93 | pathTrack = t 94 | return this 95 | } 96 | 97 | @Override 98 | boolean hasNext() { 99 | if (!selfFinished && curr.inBounds(table)) return true 100 | return stopper(table, curr) 101 | } 102 | 103 | @Override 104 | Navigator next() { 105 | 106 | if (!selfFinished) { 107 | selfFinished = true 108 | } else { 109 | curr = next(curr) 110 | } 111 | 112 | if (markPath) curr?.mark("$id-${steps++}|", pathTrack) 113 | 114 | return curr 115 | } 116 | 117 | Optional find(Fx1 pred) { 118 | stream().filter { Navigator n -> pred.call(n) }.findFirst() 119 | } 120 | 121 | void each(VoidFx1 fx) { 122 | stream().forEach { Navigator n -> fx.call(n) } 123 | } 124 | 125 | } 126 | 127 | -------------------------------------------------------------------------------- /src/main/groovy/fuzzycsv/RecordFx.groovy: -------------------------------------------------------------------------------- 1 | package fuzzycsv 2 | 3 | import fuzzycsv.javaly.Fx1 4 | import groovy.transform.CompileStatic 5 | import groovy.transform.TypeCheckingMode 6 | import groovy.transform.stc.ClosureParams 7 | import groovy.transform.stc.SimpleType 8 | 9 | class RecordFx { 10 | 11 | String name 12 | Fx1 c 13 | ResolutionStrategy resolutionStrategy//todo delete 14 | private boolean useFuzzy = false //todo delete 15 | public headerEnabled = false //todo delete 16 | boolean useCoercion = true //todo delete 17 | 18 | protected RecordFx() {} 19 | 20 | RecordFx(String name, Fx1 c) { 21 | this.name = name 22 | this.c = c 23 | } 24 | 25 | /* @CompileStatic 26 | Object getValue(Record record) { 27 | if (record.isHeader() && !headerEnabled) //todo delete 28 | return null 29 | 30 | if (resolutionStrategy != null) 31 | record.resolutionStrategy = resolutionStrategy 32 | 33 | def rt 34 | if (useCoercion) { 35 | rt = getValueWithCoercion(record) //todo delete 36 | } else { 37 | rt = c.call(record) 38 | } 39 | 40 | return rt 41 | }*/ 42 | 43 | @CompileStatic 44 | Object getValue(Record record) { 45 | 46 | 47 | def rt = c.call(record) 48 | 49 | 50 | return rt 51 | } 52 | 53 | 54 | private def getValueWithCoercion(Record record) { 55 | return use(FxExtensions) { c.call(record) } 56 | } 57 | /** 58 | * use @fn 59 | */ 60 | @Deprecated 61 | static RecordFx get(String name, @ClosureParams(value = SimpleType.class, options = "fuzzycsv.Record") Closure c) { 62 | return fx(name, c) //todo delete 63 | } 64 | 65 | /** 66 | Record function with coercion ON -> SLOWER 67 | * @param function 68 | * @return 69 | */ 70 | @CompileStatic 71 | static RecordFx fn(@ClosureParams(value = SimpleType.class, options = "fuzzycsv.Record") Closure function) { 72 | fn(RecordFx.class.getSimpleName(), function) //todo delete 73 | } 74 | 75 | @CompileStatic(TypeCheckingMode.SKIP) 76 | static RecordFx fn(String name, @ClosureParams(value = SimpleType.class, options = "fuzzycsv.Record") Closure function) { 77 | def finalC = { p-> 78 | use(FxExtensions) { 79 | function(p) 80 | } 81 | } 82 | return new RecordFx(name, finalC)//todo delete 83 | } 84 | 85 | /** 86 | * Record function with coercion OFF -> FASTER 87 | * @param function 88 | * @returnq 89 | */ 90 | @CompileStatic 91 | static RecordFx fx(Fx1 function) { 92 | fx(RecordFx.class.getSimpleName(), function) 93 | } 94 | 95 | @CompileStatic 96 | static RecordFx fx(String name, Fx1 function) { 97 | def r = new RecordFx(name, function) 98 | r.useCoercion = false 99 | return r 100 | } 101 | 102 | @CompileStatic 103 | RecordFx withSourceFirst() { 104 | resolutionStrategy = ResolutionStrategy.LEFT_FIRST 105 | return this 106 | } 107 | 108 | RecordFx withResolution(ResolutionStrategy strategy) {//todo delete 109 | this.resolutionStrategy = strategy 110 | return this 111 | } 112 | 113 | @CompileStatic 114 | RecordFx az(String name) { 115 | this.name = name 116 | return this 117 | } 118 | 119 | 120 | @CompileStatic 121 | static String resolveName(o) { 122 | switch (o) { 123 | case RecordFx: 124 | return (o as RecordFx).name 125 | case String: 126 | return (String) o 127 | default: 128 | return o?.toString() 129 | } 130 | 131 | } 132 | 133 | } 134 | -------------------------------------------------------------------------------- /src/main/groovy/fuzzycsv/FastIndexOfList.groovy: -------------------------------------------------------------------------------- 1 | package fuzzycsv 2 | 3 | import groovy.transform.CompileStatic 4 | 5 | import java.util.function.Predicate 6 | import java.util.function.UnaryOperator 7 | 8 | @CompileStatic 9 | class FastIndexOfList extends ArrayList implements List { 10 | 11 | private Map indexCache = new HashMap<>() 12 | 13 | private int lastVisited = 0 14 | 15 | FastIndexOfList() { 16 | 17 | } 18 | 19 | FastIndexOfList(Collection var1) { 20 | super(var1) 21 | } 22 | 23 | 24 | FastIndexOfList(int var1) { 25 | super(var1) 26 | } 27 | 28 | int indexOf(Object o) { 29 | 30 | def i1 = indexCache[nullObjIfNull(o)] 31 | if (i1 != null) { 32 | return i1 33 | } 34 | 35 | def size = size() 36 | if (o == null) { 37 | for (int i = lastVisited; i < size; i++) { 38 | def e = get(i) 39 | mayBeCacheIdx(nullObjIfNull(e), i) 40 | if (e == null) i 41 | } 42 | } else { 43 | for (int i = lastVisited; i < size; i++) { 44 | def e = get(i) 45 | mayBeCacheIdx(nullObjIfNull(e), i) 46 | if (o == e) return i 47 | } 48 | } 49 | return -1 50 | } 51 | 52 | private int mayBeCacheIdx(e, int i) { 53 | if (i > lastVisited || lastVisited == 0) { 54 | if (!indexCache.containsKey(e)) indexCache.put(e, i) 55 | lastVisited = i 56 | } 57 | return i 58 | } 59 | 60 | private def nullObjIfNull(o) { 61 | if (o == null) { 62 | return null//;NullObject.getNullObject() 63 | } 64 | return o 65 | } 66 | 67 | @Override 68 | void add(int index, E element) { 69 | clearCache() 70 | super.add(index, element) 71 | } 72 | 73 | void clearCache() { 74 | indexCache.clear() 75 | lastVisited = 0 76 | } 77 | 78 | @Override 79 | E set(int index, E element) { 80 | clearCache() 81 | return super.set(index, element) 82 | } 83 | 84 | @Override 85 | void clear() { 86 | clearCache() 87 | super.clear() 88 | } 89 | 90 | @Override 91 | boolean addAll(int index, Collection c) { 92 | clearCache() 93 | return super.addAll(index, c) 94 | } 95 | 96 | @Override 97 | boolean removeAll(Collection c) { 98 | clearCache() 99 | return super.removeAll(c) 100 | } 101 | 102 | @Override 103 | boolean retainAll(Collection c) { 104 | clearCache() 105 | return super.retainAll(c) 106 | } 107 | 108 | @Override 109 | void replaceAll(UnaryOperator operator) { 110 | clearCache() 111 | super.replaceAll(operator) 112 | } 113 | 114 | @Override 115 | void sort(Comparator c) { 116 | clearCache() 117 | super.sort(c) 118 | } 119 | 120 | @Override 121 | protected void removeRange(int fromIndex, int toIndex) { 122 | clearCache() 123 | super.removeRange(fromIndex, toIndex) 124 | } 125 | 126 | @Override 127 | boolean remove(Object o) { 128 | clearCache() 129 | return super.remove(o) 130 | } 131 | 132 | @Override 133 | E remove(int index) { 134 | clearCache() 135 | return super.remove(index) 136 | } 137 | 138 | @Override 139 | boolean removeIf(Predicate filter) { 140 | clearCache() 141 | return super.removeIf(filter) 142 | } 143 | 144 | static FastIndexOfList wrap(Collection data) { 145 | if (data instanceof FastIndexOfList) return (FastIndexOfList) data 146 | return new FastIndexOfList(data) 147 | } 148 | 149 | static FastIndexOfList wrap(T[] data) { 150 | return new FastIndexOfList(data as List) 151 | } 152 | } 153 | -------------------------------------------------------------------------------- /src/main/groovy/fuzzycsv/rdbms/DbColumnSync.groovy: -------------------------------------------------------------------------------- 1 | package fuzzycsv.rdbms 2 | 3 | import fuzzycsv.FuzzyCSVTable 4 | import fuzzycsv.Record 5 | import fuzzycsv.rdbms.FuzzyCSVDbExporter.Column 6 | import fuzzycsv.rdbms.stmt.SqlRenderer 7 | import groovy.sql.Sql 8 | import org.slf4j.Logger 9 | import org.slf4j.LoggerFactory 10 | 11 | import java.sql.JDBCType 12 | 13 | class DbColumnSync { 14 | 15 | private static Logger log = LoggerFactory.getLogger(DbColumnSync) 16 | 17 | 18 | List columns 19 | Sql gSql 20 | String tableName 21 | FuzzyCSVTable table 22 | int maxVarCharSize = 4000 23 | SqlRenderer sqlRenderer 24 | 25 | 26 | def sync() { 27 | assert sqlRenderer != null, "Sql renderer is not set" 28 | /* 29 | 1. Fetch all columns 30 | 2. Find the ones out of sync 31 | 3. Create the 32 | */ 33 | 34 | def dbColumns = DDLUtils.allColumns(gSql.connection, tableName) 35 | 36 | def receivedColumns = 37 | FuzzyCSVTable.fromPojoList(columns) 38 | .renameHeader('name', 'COLUMN_NAME') 39 | 40 | 41 | getMissingColumns(receivedColumns.copy(), dbColumns.copy()) 42 | .each { addColumn(it) } 43 | 44 | 45 | def joined = receivedColumns.join(dbColumns, 'COLUMN_NAME') 46 | 47 | 48 | joined.each { adjust(it) } 49 | 50 | 51 | } 52 | 53 | void adjust(Record r) { 54 | def string = r.TYPE_NAME.toString() 55 | 56 | Column newCol 57 | switch (string.toUpperCase()) { 58 | case 'DECIMAL': 59 | newCol = adjustForDecimal(r) 60 | break 61 | case 'VARCHAR': 62 | newCol = adjustForVarChar(r) 63 | break 64 | default: 65 | if (r.DATA_TYPE == JDBCType.VARCHAR.vendorTypeNumber) 66 | newCol = adjustForVarChar(r) 67 | 68 | } 69 | 70 | 71 | if (newCol) modifyColumn(newCol) 72 | } 73 | 74 | private Column adjustForDecimal(Record r) { 75 | def max = table[r.COLUMN_NAME].collect { (it as BigDecimal)?.abs() }.max() as BigDecimal 76 | if (max == null) return null 77 | def origSize = r.COLUMN_SIZE as int 78 | 79 | def origRight = r.DECIMAL_DIGITS as int 80 | def origLeft = origSize - origRight 81 | 82 | def newRight = max.scale() 83 | def newLeft = max.precision() - max.scale() 84 | 85 | def finalLeft = [origLeft, newLeft].max() 86 | def finalRight = [origRight, newRight].max() 87 | 88 | if (origRight >= finalRight && origLeft >= finalLeft) { 89 | log.trace("no adjustment required for [${r.COLUMN_NAME}]") 90 | return null 91 | } 92 | 93 | 94 | new Column( 95 | name: r.COLUMN_NAME, 96 | type: r.TYPE_NAME, 97 | size: finalLeft + finalRight, 98 | decimals: finalRight) 99 | } 100 | 101 | private Column adjustForVarChar(Record r) { 102 | def max = table[r.COLUMN_NAME as String].max { it?.toString()?.length() } 103 | if (max == null) return null 104 | 105 | def length = max.toString().length() 106 | 107 | if (r.COLUMN_SIZE >= length) { 108 | log.trace("no adjustment required for [${r.COLUMN_NAME}]") 109 | return null 110 | } 111 | 112 | 113 | if (length <= maxVarCharSize) 114 | new Column(name: r.COLUMN_NAME, type: r.TYPE_NAME, size: length, decimals: 0) 115 | else 116 | new Column(name: r.COLUMN_NAME, type: 'text', size: 0, decimals: 0) 117 | 118 | } 119 | 120 | 121 | private static List getMissingColumns(FuzzyCSVTable receivedColumns, FuzzyCSVTable dbColumns) { 122 | def joined = receivedColumns.leftJoin(dbColumns, 'COLUMN_NAME') 123 | 124 | 125 | def missionColumns = joined.filter { it.TABLE_NAME == null } 126 | 127 | missionColumns.renameHeader('COLUMN_NAME', 'name').toPojoList(Column.class) 128 | } 129 | 130 | def addColumn(Column column) { 131 | def ddl = sqlRenderer.addColumn(tableName, column) 132 | log.trace("adding column [$ddl]") 133 | gSql.execute(ddl as String) 134 | } 135 | 136 | def modifyColumn(Column column) { 137 | def ddl = sqlRenderer.modifyColumn(tableName, column) 138 | log.trace("adjusting column [$ddl]") 139 | gSql.execute(ddl as String) 140 | 141 | } 142 | 143 | 144 | } 145 | -------------------------------------------------------------------------------- /src/main/groovy/fuzzycsv/Excel2Csv.groovy: -------------------------------------------------------------------------------- 1 | package fuzzycsv 2 | 3 | 4 | import groovy.transform.CompileStatic 5 | import org.apache.poi.hssf.usermodel.HSSFWorkbook 6 | import org.apache.poi.ss.usermodel.* 7 | import org.apache.poi.xssf.usermodel.XSSFWorkbook 8 | import org.slf4j.Logger 9 | import org.slf4j.LoggerFactory 10 | 11 | @CompileStatic 12 | class Excel2Csv { 13 | 14 | private static Logger log = LoggerFactory.getLogger(Excel2Csv) 15 | 16 | static void testClassPath() { 17 | try { 18 | Class.forName('org.apache.poi.ss.usermodel.Workbook') 19 | } catch (Throwable e) { 20 | log.error("Apache Poi No Found.") 21 | printRequiredDependencies() 22 | throw e 23 | } 24 | } 25 | 26 | static void printRequiredDependencies() { 27 | println("""Add to Gradle: 28 | compileOnly 'org.apache.poi:poi-ooxml:3.16', { 29 | exclude group: 'stax', module: 'stax-api' 30 | } 31 | compileOnly 'org.apache.poi:ooxml-schemas:1.3', { 32 | exclude group: 'stax', module: 'stax-api' 33 | }""") 34 | } 35 | 36 | static Map toCsv(File file, int startRow = 0, int endRow = Integer.MAX_VALUE) { 37 | 38 | Workbook wb = null 39 | if (file.name.endsWith(".xls")) { 40 | file.withInputStream { 41 | wb = new HSSFWorkbook(it) 42 | } 43 | } else { 44 | wb = new XSSFWorkbook(file) 45 | } 46 | return allSheetsToCsv(wb, startRow, endRow) 47 | 48 | } 49 | 50 | static Map allSheetsToCsv(Workbook wb, int startRow = 0, int endRow = Integer.MAX_VALUE) { 51 | FormulaEvaluator fe = wb.getCreationHelper().createFormulaEvaluator() 52 | Map entries = wb.collectEntries { Sheet sheet -> [sheet.sheetName, sheetToCsvImpl(sheet, fe, startRow, endRow)] } 53 | return entries 54 | 55 | 56 | } 57 | 58 | static FuzzyCSVTable toCsv(Workbook wb, int sheetNo = 0) { 59 | FormulaEvaluator fe = wb.getCreationHelper().createFormulaEvaluator() 60 | def sheet = wb.getSheetAt(sheetNo) 61 | return sheetToCsvImpl(sheet, fe) 62 | 63 | } 64 | 65 | static FuzzyCSVTable sheetToCsv(Sheet sheet, int startRow = 0, int endRow = Integer.MAX_VALUE) { 66 | def evaluator = sheet.workbook.creationHelper.createFormulaEvaluator() 67 | return sheetToCsvImpl(sheet, evaluator, startRow, endRow) 68 | 69 | } 70 | 71 | private 72 | static FuzzyCSVTable sheetToCsvImpl(Sheet sheet, FormulaEvaluator fe, int startRow = 0, int endRow = Integer.MAX_VALUE) { 73 | List result = [] 74 | 75 | int index = 0 76 | for (Row row in sheet) { 77 | if (row == null || index++ < startRow) { 78 | continue 79 | } 80 | List csvRow = rowToList(row, fe) 81 | result.add(csvRow) 82 | 83 | if (index > endRow) break 84 | } 85 | return FuzzyCSVTable.tbl(result).equalizeRowWidths() 86 | } 87 | 88 | static List rowToList(Row row, FormulaEvaluator fe) { 89 | def result = [] 90 | short minColIx = row.getFirstCellNum() 91 | short maxColIx = row.getLastCellNum() 92 | for (short colIx = minColIx; colIx < maxColIx; colIx++) { 93 | Cell cell = row.getCell(colIx) 94 | def value = getCellValue(fe, cell) 95 | result.add(value) 96 | } 97 | return result 98 | 99 | } 100 | 101 | static def getCellValue(FormulaEvaluator fe, Cell cell) { 102 | 103 | if (cell == null) return null 104 | 105 | if (cell.getCellTypeEnum() == CellType.FORMULA) { 106 | cell = fe.evaluateInCell(cell) 107 | } 108 | 109 | 110 | switch (cell.cellTypeEnum) { 111 | case CellType.BOOLEAN: 112 | return cell.booleanCellValue 113 | case CellType.NUMERIC: 114 | if (DateUtil.isCellDateFormatted(cell)) 115 | return cell.getDateCellValue() 116 | else 117 | return cell.numericCellValue 118 | case CellType.STRING: 119 | return cell.stringCellValue 120 | case CellType.BLANK: 121 | return null 122 | case CellType.ERROR: 123 | log.error("Unable to read data from cell[{},{}]", cell.rowIndex, cell.columnIndex) 124 | return "!!ERROR!!" 125 | 126 | } 127 | return "!!UNKNOWN DATA TYPE!!" 128 | } 129 | 130 | } 131 | -------------------------------------------------------------------------------- /src/main/groovy/fuzzycsv/rdbms/FuzzyCsvDbInserter.groovy: -------------------------------------------------------------------------------- 1 | package fuzzycsv.rdbms 2 | 3 | import fuzzycsv.FuzzyCSVTable 4 | import fuzzycsv.Record 5 | import fuzzycsv.rdbms.stmt.SqlRenderer 6 | import groovy.transform.CompileStatic 7 | 8 | import java.util.concurrent.Callable 9 | import java.util.stream.StreamSupport 10 | 11 | //todo remove apache commons dependency 12 | @CompileStatic 13 | class FuzzyCsvDbInserter { 14 | 15 | private FuzzyCsvDbInserter() { 16 | } 17 | 18 | 19 | static String inTicks(String s) { 20 | if (s.contains('`' as CharSequence)) { 21 | throw new IllegalArgumentException("Header cannot contain backtick") 22 | } 23 | return '`' + s + '`' 24 | } 25 | 26 | 27 | static Map.Entry> generateUpdate(SqlRenderer sqlRenderer, 28 | Record r, 29 | String tableName, 30 | String... identifiers) { 31 | 32 | 33 | String updateStart = "UPDATE " + sqlRenderer.quoteName(tableName) + "\n" 34 | 35 | Collection finalHeaders = r.getFinalHeaders().findAll { String h -> !identifiers.contains(h) } 36 | 37 | 38 | List valueParams = new ArrayList<>() 39 | 40 | StringJoiner joiner = new StringJoiner(",\n", "SET\n", "\n") 41 | for (String h : finalHeaders) { 42 | String s = " " + sqlRenderer.quoteName(h) + " = ?" 43 | joiner.add(s) 44 | valueParams.add(r.get(h)) 45 | } 46 | String fieldUpdates = joiner.toString() 47 | 48 | 49 | StringJoiner result = new StringJoiner(" AND ") 50 | for (String i : identifiers) { 51 | String s = sqlRenderer.quoteName(i) + " = ?" 52 | result.add(s) 53 | valueParams.add(r.get(i)) 54 | } 55 | String filterClause = " WHERE " + result.toString() 56 | 57 | return new AbstractMap.SimpleImmutableEntry>(updateStart + fieldUpdates + filterClause, valueParams) 58 | 59 | 60 | } 61 | 62 | static List>> generateUpdate(SqlRenderer sqlRenderer, 63 | FuzzyCSVTable table, 64 | String tableName, 65 | String... identifiers) { 66 | return StreamSupport.stream(table.spliterator(), false) 67 | .collect { Record r -> generateUpdate(sqlRenderer, r, tableName, identifiers) } 68 | } 69 | 70 | static Map.Entry> generateInsert(SqlRenderer sqlRenderer, FuzzyCSVTable table, String tableName) { 71 | String insertInto = "INSERT INTO " + sqlRenderer.quoteName(tableName) 72 | 73 | String insertHeader = table.getHeader().collect { sqlRenderer.quoteName(it) }.join(", ") 74 | 75 | String valuePhrase = insertInto + "\n (" + insertHeader + ") \nVALUES\n" 76 | 77 | List params = new ArrayList<>() 78 | 79 | List values = new ArrayList<>() 80 | 81 | String valueRow = table.getHeader().collect { "?" }.join(", ") 82 | 83 | for (Record r : table) { 84 | values.add("($valueRow)".toString()) 85 | params.addAll(r.getFinalRecord()) 86 | } 87 | 88 | 89 | return new AbstractMap.SimpleImmutableEntry(valuePhrase + values.join(",\n"), params) 90 | } 91 | 92 | 93 | static List>> generateInserts(SqlRenderer sqlRenderer, int pageSize, FuzzyCSVTable table, String tableName) { 94 | 95 | def tables = paginate(table, pageSize) 96 | 97 | return tables.collect { generateInsert(sqlRenderer, it, tableName) } 98 | } 99 | 100 | 101 | static List paginate(FuzzyCSVTable table, int pageSize) { 102 | return lazyPaginate(table, pageSize) 103 | .collect { it.call() } 104 | } 105 | 106 | static List> lazyPaginate(FuzzyCSVTable table, int pageSize) { 107 | 108 | if (table.size() <= pageSize) return [{ table } as Callable] 109 | 110 | def size = table.size() 111 | 112 | int equalSizePageCount = (size / pageSize) as int 113 | int pageCount = size % pageSize == 0 ? equalSizePageCount : equalSizePageCount + 1 114 | 115 | 116 | return (0..pageCount - 1).collect { Integer page -> 117 | int start = page * pageSize 118 | int end = start + pageSize 119 | def callable = { table.slice(start + 1,end) } as Callable 120 | return callable 121 | } 122 | } 123 | 124 | 125 | } 126 | -------------------------------------------------------------------------------- /release.sh: -------------------------------------------------------------------------------- 1 | #!/usr/bin/env bash 2 | 3 | # fail if any commands fails 4 | set -e 5 | 6 | assert_false() { 7 | local expression=$1 8 | local message=$2 9 | if [[ $(eval "$expression") ]]; then 10 | echo "Assertion failed: (>$expression) - $message" 11 | exit 1 12 | fi 13 | } 14 | 15 | assert_eq() { 16 | if [[ "$1" != "$2" ]]; then 17 | echo "$3" 18 | exit 1 19 | fi 20 | } 21 | 22 | assert_not_empty() { 23 | # check if the first argument is empty after trimming 24 | if [[ -z "${1//[[:space:]]/}" ]]; then 25 | echo "$2" 26 | exit 1 27 | fi 28 | } 29 | 30 | get_current_branch() { git rev-parse --abbrev-ref HEAD; } 31 | 32 | assert_on_branch() { 33 | local branch=$1 34 | local current_branch 35 | current_branch=$(get_current_branch) 36 | if [[ "$current_branch" != "$branch" ]]; then 37 | echo "Not on branch $branch, aborting." 38 | exit 1 39 | fi 40 | } 41 | 42 | confirm() { 43 | local message=$1 44 | local response 45 | read -p "$message [n]: " response 46 | response=${response:-n} 47 | if [[ "$response" != "y" ]]; then 48 | echo "Aborting." 49 | exit 1 50 | fi 51 | } 52 | 53 | assert_clean_branch() { assert_false "git status --porcelain" "There are uncommitted changes, aborting."; } 54 | 55 | increment_version() { echo "$1" | awk -F. '{$NF = $NF + 1;} 1' | sed 's/ /./g'; } 56 | 57 | assert_tag_not_exists() { assert_false "git tag -l $1" "Tag $1 already exists, aborting."; } 58 | 59 | assert_branch_not_exist() { assert_false "git branch -l $1" "Branch $1 already exists, aborting."; } 60 | 61 | get_current_project_version() { 62 | # shellcheck disable=SC2155 63 | local OUTPUT=$(./gradlew -q printVersion) 64 | # shellcheck disable=SC2155 65 | local VERSION=$(echo "$OUTPUT" | tr '\n' ' ' | awk '{print $NF}') 66 | echo "$VERSION" 67 | } 68 | 69 | prompt_for_version() { 70 | local default_version=$1 71 | read -p "Enter the version [$default_version]: " actual_default_version 72 | actual_default_version=${actual_default_version:-$default_version} 73 | echo "$actual_default_version" 74 | } 75 | 76 | assert_branch_is_up_to_date() { 77 | git fetch 78 | HEAD=$(git rev-parse HEAD) 79 | UPSTREAM=$(git rev-parse '@{u}') 80 | assert_eq "$HEAD" "$UPSTREAM" "Local branch is not up-to-date, aborting." 81 | } 82 | 83 | update_version_in_properties_and_readme() { 84 | echo "Updating README.md and gradle.properties to [$1]" 85 | sed -i -e "s/implementation 'io\.github\.kayr:fuzzy-csv:.*-groovy3'/implementation 'io.github.kayr:fuzzy-csv:$1-groovy3'/g" README.md 86 | sed -i -e "s/implementation 'io\.github\.kayr:fuzzy-csv:.*-groovy4'/implementation 'io.github.kayr:fuzzy-csv:$1-groovy4'/g" README.md 87 | sed -i -e "s/VERSION_NAME=.*/VERSION_NAME=$1/g" gradle.properties 88 | } 89 | 90 | MAIN_BRANCH="master" 91 | CURRENT_BRANCH=$(get_current_branch) 92 | RELEASE_VERSION=$(get_current_project_version) 93 | RELEASE_VERSION_INCREMENTED=$(increment_version "$RELEASE_VERSION") 94 | 95 | echo "check the current branch is clean" 96 | assert_clean_branch 97 | 98 | echo "check the current branch is up-to-date" 99 | assert_branch_is_up_to_date 100 | 101 | echo "check the current branch is $MAIN_BRANCH" 102 | assert_eq "$CURRENT_BRANCH" "$MAIN_BRANCH" "Not on branch $MAIN_BRANCH, aborting." 103 | 104 | NEW_VERSION=$(prompt_for_version "$RELEASE_VERSION_INCREMENTED") 105 | assert_not_empty "$NEW_VERSION" "Version cannot be empty" 106 | 107 | echo "check tag [$NEW_VERSION] and branch [release/$NEW_VERSION] does not exist" 108 | assert_tag_not_exists "$NEW_VERSION" 109 | assert_branch_not_exist "release/$NEW_VERSION" 110 | 111 | echo " -> Creating branch release/$NEW_VERSION" 112 | git checkout -b "release/$NEW_VERSION" 113 | 114 | echo " -> Updating README.md and gradle.properties to [$NEW_VERSION]" 115 | update_version_in_properties_and_readme "$NEW_VERSION" 116 | 117 | echo " -> Committing changes" 118 | git commit -am "Release $NEW_VERSION" 119 | 120 | echo " -> Pushing branch release/$NEW_VERSION" 121 | git push --set-upstream origin "release/$NEW_VERSION" 122 | 123 | echo " -> Run Tests" 124 | make test 125 | 126 | echo " -> Publish groovy 3" 127 | make publish-groovy3 128 | echo " ####### Successfully published groovy 3 artifacts #########" 129 | 130 | echo " -> Publish groovy 4" 131 | make publish-groovy4 132 | echo " ####### Successfully published groovy 4 artifacts #########" 133 | 134 | echo " -> Creating tag $NEW_VERSION" 135 | git tag -a "$NEW_VERSION" -m "Release $NEW_VERSION" 136 | 137 | echo " -> Pushing tag $NEW_VERSION" 138 | git push origin "$NEW_VERSION" 139 | 140 | echo " -> Switching to branch $MAIN_BRANCH" 141 | git checkout "$MAIN_BRANCH" 142 | 143 | echo " -> Merging branch release/$NEW_VERSION into $MAIN_BRANCH" 144 | git merge "release/$NEW_VERSION" 145 | 146 | echo " -> Pushing branch $MAIN_BRANCH" 147 | git push 148 | -------------------------------------------------------------------------------- /src/test/groovy/fuzzycsv/ArrayListTest.groovy: -------------------------------------------------------------------------------- 1 | /* 2 | * Copyright (C) 2008 The Android Open Source Project 3 | * 4 | * Licensed under the Apache License, Version 2.0 (the "License"); 5 | * you may not use this file except in compliance with the License. 6 | * You may obtain a copy of the License at 7 | * 8 | * http://www.apache.org/licenses/LICENSE-2.0 9 | * 10 | * Unless required by applicable law or agreed to in writing, software 11 | * distributed under the License is distributed on an "AS IS" BASIS, 12 | * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 13 | * See the License for the specific language governing permissions and 14 | * limitations under the License. 15 | */ 16 | 17 | package fuzzycsv 18 | 19 | import junit.framework.TestCase 20 | import org.junit.Test 21 | 22 | /** 23 | * This test case tests several often used functionality of ArrayLists. 24 | */ 25 | class ArrayListTest extends TestCase { 26 | 27 | @SuppressWarnings("unchecked") 28 | @Test 29 | void testArrayList() throws Exception { 30 | FastIndexOfList array = new FastIndexOfList() 31 | assertEquals(0, array.size()) 32 | assertTrue(array.isEmpty()) 33 | 34 | array.add(new Integer(0)) 35 | array.add(0, new Integer(1)) 36 | array.add(1, new Integer(2)) 37 | array.add(new Integer(3)) 38 | array.add(new Integer(1)) 39 | 40 | assertEquals(5, array.size()) 41 | assertFalse(array.isEmpty()) 42 | 43 | assertEquals(1, ((Integer) array.get(0)).intValue()) 44 | assertEquals(2, ((Integer) array.get(1)).intValue()) 45 | assertEquals(0, ((Integer) array.get(2)).intValue()) 46 | assertEquals(3, ((Integer) array.get(3)).intValue()) 47 | assertEquals(1, ((Integer) array.get(4)).intValue()) 48 | 49 | assertFalse(array.contains(null)) 50 | assertTrue(array.contains(new Integer(2))) 51 | assertEquals(0, array.indexOf(new Integer(1))) 52 | assertEquals(4, array.lastIndexOf(new Integer(1))) 53 | assertTrue(array.indexOf(new Integer(5)) < 0) 54 | assertTrue(array.lastIndexOf(new Integer(5)) < 0) 55 | 56 | 57 | array.remove(1) 58 | array.remove(1) 59 | 60 | assertEquals(3, array.size()) 61 | assertFalse(array.isEmpty()) 62 | assertEquals(1, ((Integer) array.get(0)).intValue()) 63 | assertEquals(3, ((Integer) array.get(1)).intValue()) 64 | assertEquals(1, ((Integer) array.get(2)).intValue()) 65 | 66 | assertFalse(array.contains(null)) 67 | assertFalse(array.contains(new Integer(2))) 68 | assertEquals(0, array.indexOf(new Integer(1))) 69 | assertEquals(2, array.lastIndexOf(new Integer(1))) 70 | assertTrue(array.indexOf(new Integer(5)) < 0) 71 | assertTrue(array.lastIndexOf(new Integer(5)) < 0) 72 | 73 | array.clear() 74 | 75 | assertEquals(0, array.size()) 76 | assertTrue(array.isEmpty()) 77 | assertTrue(array.indexOf(new Integer(5)) < 0) 78 | assertTrue(array.lastIndexOf(new Integer(5)) < 0) 79 | 80 | FastIndexOfList al = new FastIndexOfList() 81 | 82 | assertFalse(al.remove(null)) 83 | assertFalse(al.remove("string")) 84 | 85 | al.add("string") 86 | al.add(null) 87 | 88 | assertTrue(al.remove(null)) 89 | assertTrue(al.remove("string")) 90 | } 91 | 92 | @Test 93 | void testListMutation() { 94 | def a = { 95 | def wrap = FastIndexOfList.wrap([1, 2, 3, 4, 5]) 96 | wrap.indexOf(4) 97 | wrap 98 | } 99 | 100 | assert a()[1] == 2 101 | 102 | def a2 = a() 103 | a2.set(1, 22) 104 | assert a2[1] == 22 105 | 106 | 107 | def a1 = a() 108 | assert a1.indexOf(5) == 4 109 | a1.clear() 110 | assert a1.indexOf(5) == -1 111 | 112 | def a3 = a() 113 | a3.indexOf(5) 114 | a3.retainAll([1, 2, 3]) 115 | assert a3.indexOf(2) == 1 116 | assert a3.indexOf(1) == 0 117 | assert a3.indexOf(3) == 2 118 | 119 | def a4 = a() 120 | a4.addAll(3, [6, 7]) 121 | assert a4.indexOf(6) == 3 122 | 123 | def a5 = a() 124 | a5.removeAll([4, 5]) 125 | assert a5.indexOf(5) == -1 126 | assert a5.indexOf(4) == -1 127 | assert a5.indexOf(1) == 0 128 | 129 | def a6 = a() 130 | a6.replaceAll({ s -> s + 10 }) 131 | assert a6.indexOf(3) == -1 132 | assert a6.indexOf(13) == 2 133 | 134 | def a7 = a() 135 | a7.add(-10) 136 | a7.sort(Comparator.naturalOrder()) 137 | assert a7.indexOf(-10) == 0 138 | 139 | def a8 = a() 140 | a8.removeRange(0, 2) 141 | assert a8.indexOf(1) == -1 142 | 143 | def a9 = a() 144 | a9.remove(0) 145 | assert a9.indexOf(1) == -1 146 | 147 | a9.removeIf { it == 2 } 148 | assert a9.indexOf(2) == -1 149 | } 150 | 151 | 152 | } 153 | -------------------------------------------------------------------------------- /src/main/groovy/fuzzycsv/FuzzyCSVUtils.java: -------------------------------------------------------------------------------- 1 | package fuzzycsv; 2 | 3 | import org.codehaus.groovy.runtime.DefaultGroovyMethods; 4 | import org.slf4j.Logger; 5 | import org.slf4j.LoggerFactory; 6 | 7 | import java.io.BufferedReader; 8 | import java.io.Reader; 9 | import java.math.BigDecimal; 10 | import java.math.BigInteger; 11 | import java.util.ArrayList; 12 | import java.util.Collections; 13 | import java.util.List; 14 | import java.util.Map; 15 | 16 | @SuppressWarnings("rawtypes") 17 | public class FuzzyCSVUtils { 18 | public static List toNumbers(List list) { 19 | List rt = new ArrayList<>(list.size()); 20 | for (Object o : list) { 21 | rt.add(FuzzyCSVUtils.toNumber(o)); 22 | } 23 | return rt; 24 | } 25 | 26 | public static List list(T... arr) { 27 | List rt = new ArrayList(arr.length); 28 | Collections.addAll(rt, arr); 29 | return rt; 30 | } 31 | 32 | public static Object safeGet(List it, int idx) { 33 | if (idx < 0 || it == null) return null; 34 | return it.size() > idx ? it.get(idx) : null; 35 | } 36 | 37 | public static List moveElem2Idx(List h, Object from, int idx) { 38 | return move(h, h.indexOf(from), idx); 39 | } 40 | 41 | public static List moveElems(List h, String from, String to) { 42 | return move(h, h.indexOf(from), h.indexOf(to)); 43 | } 44 | 45 | public static List move(List h, int from, int to) { 46 | Object get = h.get(from); 47 | h.remove(from); 48 | h.add(to, get); 49 | return h; 50 | } 51 | 52 | public static List replace(List source, T from, T to) { 53 | int idx = source.indexOf(from); 54 | if (idx != -1) source.set(idx, to); 55 | return source; 56 | } 57 | 58 | public static Number coerceToNumber(Object obj, Class preferredType) { 59 | return toNumber(obj, false, preferredType); 60 | } 61 | 62 | public static Number coerceToNumber(Object obj) { 63 | return FuzzyCSVUtils.coerceToNumber(obj, Integer.class); 64 | } 65 | 66 | private static Number toNumber(Object obj, boolean strict, Class preferredType) { 67 | if (obj == null) 68 | return preferredType.equals(Integer.class) || preferredType.equals(BigInteger.class) ? BigInteger.ZERO : BigDecimal.ZERO; 69 | 70 | if (obj instanceof Aggregator) obj = ((Aggregator) obj).getValue(); 71 | 72 | if (obj instanceof Number) return (Number) obj; 73 | 74 | if (obj instanceof Boolean) 75 | return obj == Boolean.TRUE ? BigInteger.ONE : BigInteger.ZERO; 76 | 77 | String strValue = obj.toString(); 78 | 79 | try { 80 | return Integer.parseInt(strValue); 81 | } catch (Exception x) { 82 | } 83 | 84 | 85 | try { 86 | return Double.parseDouble(strValue); 87 | } catch (Exception x) { 88 | String msg = "FuzzyCSVUtils:toNumbers() Could not convert [" + strValue + "] to a Number (" + String.valueOf(x) + ")"; 89 | if (strict) throw new NumberFormatException(msg); 90 | else log.trace(msg); 91 | } 92 | 93 | 94 | return Integer.class.equals(preferredType) || BigInteger.class.equals(preferredType) ? BigInteger.ZERO : BigDecimal.ZERO; 95 | } 96 | 97 | private static Number toNumber(Object obj, boolean strict) { 98 | return FuzzyCSVUtils.toNumber(obj, strict, Integer.class); 99 | } 100 | 101 | private static Number toNumber(Object obj) { 102 | return FuzzyCSVUtils.toNumber(obj, true, Integer.class); 103 | } 104 | 105 | 106 | public static void closeQuietly(AutoCloseable c) { 107 | if (c != null) { 108 | try { 109 | c.close(); 110 | } catch (Exception e) { 111 | /* ignore */ 112 | } 113 | } 114 | } 115 | 116 | public static Map toProperties(Object o) { 117 | final Map properties = DefaultGroovyMethods.getProperties(o); 118 | properties.remove("class"); 119 | return properties; 120 | } 121 | 122 | private static Logger log = LoggerFactory.getLogger(FuzzyCSVUtils.class); 123 | 124 | public static String[] listToStrArray(List items) { 125 | int size = items.size(); 126 | String[] array = new String[size]; 127 | for (int i = 0; i < size; i++) { 128 | Object item = items.get(i); 129 | if (item == null) continue; 130 | array[i] = item.toString(); 131 | } 132 | 133 | return array; 134 | } 135 | 136 | 137 | public static String[] objArrayToSrArray(Object[] items) { 138 | int size = items.length; 139 | String[] array = new String[size]; 140 | for (int i = 0; i < size; i++) { 141 | Object item = items[i]; 142 | if (item == null) continue; 143 | array[i] = item.toString(); 144 | } 145 | 146 | return array; 147 | } 148 | 149 | public static String toString(Reader reader) { 150 | StringBuilder sb = new StringBuilder(); 151 | BufferedReader br = new BufferedReader(reader); 152 | char[] charBuffer = new char[8192]; 153 | int bytesRead; 154 | try { 155 | while ((bytesRead = br.read(charBuffer)) != -1) { 156 | sb.append(charBuffer, 0, bytesRead); 157 | } 158 | } catch (Exception e) { 159 | throw FuzzyCsvException.wrap(e); 160 | } 161 | return sb.toString(); 162 | } 163 | } 164 | 165 | -------------------------------------------------------------------------------- /src/main/groovy/fuzzycsv/javaly/Numb.java: -------------------------------------------------------------------------------- 1 | package fuzzycsv.javaly; 2 | 3 | import org.apache.groovy.util.SystemUtil; 4 | 5 | import java.math.BigDecimal; 6 | import java.math.BigInteger; 7 | import java.math.MathContext; 8 | import java.math.RoundingMode; 9 | 10 | public class Numb { 11 | 12 | private static final Numb NULL_NUMB = new Numb(null); 13 | private final BigDecimal value; 14 | 15 | private Numb(BigDecimal value) { 16 | this.value = value; 17 | } 18 | 19 | 20 | public static Numb of(Object obj) { 21 | return new Numb(toBigDecimal(obj)); 22 | } 23 | 24 | public static Numb nullV() { 25 | return NULL_NUMB; 26 | } 27 | 28 | 29 | public static boolean isNumber(Object object) { 30 | return object instanceof Number || object instanceof Numb; 31 | } 32 | 33 | private static BigDecimal toBigDecimal(Object obj) { 34 | 35 | 36 | if (obj instanceof Dynamic) 37 | return toBigDecimal(((Dynamic) obj).get()); 38 | 39 | if (obj instanceof String) { 40 | return new BigDecimal(obj.toString()); 41 | } 42 | 43 | return toBigDecimalStrict(obj); 44 | 45 | } 46 | 47 | 48 | private static BigDecimal toBigDecimalStrict(Object obj) { 49 | 50 | if (obj == null) { 51 | return null; 52 | } 53 | 54 | if (obj instanceof BigDecimal) { 55 | return ((BigDecimal) obj); 56 | } 57 | 58 | if (obj instanceof BigInteger) { 59 | return new BigDecimal((BigInteger) obj); 60 | } 61 | 62 | if (obj instanceof Number) { 63 | return BigDecimal.valueOf(((Number) obj).doubleValue()); 64 | } 65 | 66 | if (obj instanceof Dynamic) { 67 | return toBigDecimalStrict(((Dynamic) obj).get()); 68 | } 69 | 70 | if (obj instanceof Numb) { 71 | return ((Numb) obj).value; 72 | } 73 | throw new IllegalArgumentException("Cannot coerce to number: " + obj); 74 | } 75 | 76 | public boolean eq(Object other) { 77 | return compareTo(other) == 0; 78 | } 79 | 80 | public boolean neq(Object bigDecimal) { 81 | return !eq(bigDecimal); 82 | } 83 | 84 | 85 | public boolean lt(Object other) { 86 | return compareTo(other) < 0; 87 | } 88 | 89 | public boolean lte(Object other) { 90 | return compareTo(other) <= 0; 91 | } 92 | 93 | public boolean gt(Object other) { 94 | return compareTo(other) > 0; 95 | } 96 | 97 | public boolean gte(Object other) { 98 | return compareTo(other) >= 0; 99 | } 100 | 101 | 102 | public int compareTo(Object other) { 103 | if (other instanceof Numb) return compareTo(((Numb) other).value); 104 | if (other == null && isNull()) return 0; 105 | if (other == null) return 1; 106 | if (isNull()) return -1; 107 | return value.compareTo(toBigDecimalStrict(other)); 108 | } 109 | 110 | public boolean isNull() { 111 | return value == null; 112 | } 113 | 114 | 115 | public Numb plus(Object other) { 116 | BigDecimal addend = toBigDecimalIfArithAllowed(other); 117 | return of(value.add(addend)); 118 | } 119 | 120 | public Numb minus(Object other) { 121 | BigDecimal subtrahend = toBigDecimalIfArithAllowed(other); 122 | return of(value.subtract(subtrahend)); 123 | } 124 | 125 | public Numb times(Object other) { 126 | BigDecimal multiplicand = toBigDecimalIfArithAllowed(other); 127 | return of(value.multiply(multiplicand)); 128 | } 129 | 130 | public Numb div(Object right) { 131 | BigDecimal divisor = toBigDecimalIfArithAllowed(right); 132 | try { 133 | return of(value.divide(divisor)); 134 | } catch (ArithmeticException e) { 135 | //borrowed from groovy 136 | int precision = Math.max(value.precision(), divisor.precision()) + DIVISION_EXTRA_PRECISION; 137 | BigDecimal result = value.divide(divisor, new MathContext(precision)); 138 | int scale = Math.max(Math.max(value.scale(), divisor.scale()), DIVISION_MIN_SCALE); 139 | if (result.scale() > scale) result = result.setScale(scale, RoundingMode.HALF_UP); 140 | return of(result); 141 | 142 | } 143 | } 144 | 145 | public Numb pow(Object other) { 146 | BigDecimal exponent = toBigDecimalIfArithAllowed(other); 147 | return of(value.pow(exponent.intValue())); 148 | } 149 | 150 | public Numb neg() { 151 | assertIsNotNull(); 152 | return of(value.negate()); 153 | } 154 | 155 | 156 | private static final int DIVISION_EXTRA_PRECISION = SystemUtil.getIntegerSafe("groovy.division.extra.precision", 10); 157 | private static final int DIVISION_MIN_SCALE = SystemUtil.getIntegerSafe("groovy.division.min.scale", 10); 158 | 159 | 160 | 161 | private BigDecimal toBigDecimalIfArithAllowed(Object other) { 162 | assertIsNotNull(); 163 | BigDecimal operand = toBigDecimalStrict(other); 164 | if (operand == null) throw new IllegalArgumentException("Cannot perform math operation with null"); 165 | return operand; 166 | } 167 | 168 | private void assertIsNotNull() { 169 | if (isNull()) throw new IllegalArgumentException("Cannot perform math operation on null"); 170 | } 171 | 172 | 173 | public BigDecimal unwrap() { 174 | return value; 175 | } 176 | 177 | @Override 178 | public String toString() { 179 | //print formatted 180 | return value == null ? "null" : value.toPlainString(); 181 | } 182 | } 183 | -------------------------------------------------------------------------------- /src/main/groovy/fuzzycsv/Converter.java: -------------------------------------------------------------------------------- 1 | package fuzzycsv; 2 | 3 | import com.jakewharton.fliptables.FlipTable; 4 | import org.codehaus.groovy.runtime.DefaultGroovyMethods; 5 | 6 | import java.io.StringWriter; 7 | import java.util.ArrayList; 8 | import java.util.List; 9 | import java.util.Map; 10 | 11 | public class Converter { 12 | private final FuzzyCSVTable table; 13 | 14 | private Converter(FuzzyCSVTable table) { 15 | this.table = table; 16 | } 17 | 18 | public static Converter create(FuzzyCSVTable table) { 19 | return new Converter(table); 20 | } 21 | 22 | public Pretty pretty() { 23 | return Pretty.create().withTable(table); 24 | } 25 | 26 | public Json json() { 27 | return Json.create().withTable(table); 28 | } 29 | 30 | public Csv csv() { 31 | return Csv.create().withTable(table); 32 | } 33 | 34 | public Maps maps() { 35 | return Maps.create().withTable(table); 36 | } 37 | 38 | public static class Maps{ 39 | private final FuzzyCSVTable table; 40 | 41 | private Maps(FuzzyCSVTable table) { 42 | this.table = table; 43 | } 44 | 45 | static Maps create() { 46 | return new Maps(null); 47 | } 48 | 49 | public Maps withTable(FuzzyCSVTable table) { 50 | return new Maps(table); 51 | } 52 | 53 | public List> getResult() { 54 | List> csv = table.getCsv(); 55 | List header = table.getHeader(); 56 | int csvSize = csv.size(); 57 | 58 | List> result = new ArrayList<>(csvSize); 59 | for(int i = 0; i < csvSize; i++) { 60 | if (i == 0) continue; 61 | result.add(Record.getRecord(header, csv.get(i), csv,i).toMap()); 62 | } 63 | 64 | return result; 65 | } 66 | } 67 | 68 | public static class Csv { 69 | private Exporter.Csv exporter = Exporter.Csv.create(); 70 | 71 | private Csv() { 72 | } 73 | 74 | private Csv(Exporter.Csv exporter) { 75 | this.exporter = exporter; 76 | } 77 | 78 | public Csv withDelimiter(String delimiter) { 79 | return new Csv(exporter.withDelimiter(delimiter)); 80 | } 81 | 82 | public Csv withQuote(String quoteChar) { 83 | return new Csv(exporter.withQuote(quoteChar)); 84 | } 85 | 86 | public Csv withEscape(String escapeChar) { 87 | return new Csv(exporter.withEscape(escapeChar)); 88 | } 89 | 90 | public Csv withLineSeparator(String lineSeparator) { 91 | return new Csv(exporter.withLineSeparator(lineSeparator)); 92 | } 93 | 94 | public Csv withQuoteAll(boolean applyQuotesToAll) { 95 | return new Csv(exporter.withQuoteAll(applyQuotesToAll)); 96 | } 97 | 98 | public Csv withTable(FuzzyCSVTable table) { 99 | return new Csv(exporter.withTable(table)); 100 | } 101 | 102 | public String getResult() { 103 | StringWriter w = new StringWriter(); 104 | exporter.write(w); 105 | return w.toString(); 106 | } 107 | 108 | public static Csv create() { 109 | return new Csv(); 110 | } 111 | } 112 | 113 | public static class Json { 114 | private Exporter.Json exporter = Exporter.Json.create(); 115 | 116 | private Json() { 117 | } 118 | 119 | private Json(Exporter.Json exporter) { 120 | this.exporter = exporter; 121 | } 122 | 123 | public Json withPrettyPrint(boolean prettyPrint) { 124 | return new Json(exporter.withPrettyPrint(prettyPrint)); 125 | } 126 | 127 | public Json withTable(FuzzyCSVTable table) { 128 | return new Json(exporter.withTable(table)); 129 | } 130 | 131 | public Json withAsMaps(boolean asMaps) { 132 | return new Json(exporter.withAsMaps(asMaps)); 133 | } 134 | 135 | public String getResult() { 136 | return exporter.jsonText(); 137 | } 138 | 139 | public static Json create() { 140 | return new Json(); 141 | } 142 | } 143 | 144 | public static class Pretty { 145 | private FuzzyCSVTable table; 146 | 147 | private Pretty() { 148 | } 149 | 150 | @SuppressWarnings("GrMethodMayBeStatic") 151 | public Pretty withTable(FuzzyCSVTable table) { 152 | Pretty pretty = new Pretty(); 153 | pretty.table = table; 154 | return pretty; 155 | } 156 | 157 | public String getResult() { 158 | //todo use some kind of array utils 159 | String[] header = DefaultGroovyMethods.asType(table.getHeader(), String[].class); 160 | String[][] data = toStrArray(table); 161 | return FlipTable.of(header, data); 162 | } 163 | 164 | public static Pretty create() { 165 | return new Pretty(); 166 | } 167 | 168 | private static String[][] toStrArray(FuzzyCSVTable theTable) { 169 | int columns = theTable.getHeader().size(); 170 | int rows = theTable.size(); 171 | String[][] tableArray = new String[rows][]; 172 | for (int r = 0; r < rows; r++) { 173 | String[] newRow = new String[columns]; 174 | 175 | for (int c = 0; c < columns; c++) { 176 | Object cellValue = theTable.get(c, r + 1); 177 | 178 | if (cellValue == null || cellValue.equals("")) cellValue = "-"; 179 | else if (cellValue instanceof FuzzyCSVTable) 180 | cellValue = create().withTable((FuzzyCSVTable) cellValue).getResult(); 181 | else cellValue = cellValue.toString().replace("\t", " "); 182 | 183 | newRow[c] = cellValue.toString(); 184 | } 185 | 186 | tableArray[r] = newRow; 187 | } 188 | 189 | return tableArray; 190 | } 191 | 192 | } 193 | } 194 | -------------------------------------------------------------------------------- /src/test/groovy/fuzzycsv/NavigatorTest.groovy: -------------------------------------------------------------------------------- 1 | package fuzzycsv 2 | 3 | import fuzzycsv.javaly.Fx1 4 | import fuzzycsv.nav.Navigator 5 | import org.junit.Test 6 | 7 | import static fuzzycsv.FuzzyCSVTable.tbl 8 | import static groovy.test.GroovyAssert.shouldFail 9 | 10 | class NavigatorTest { 11 | def data = [['1', '2', '3', '4', '5'], 12 | [6, 7, 8, 9, 10], 13 | [11, 12, 13, 14, 15] 14 | ] 15 | 16 | @Test 17 | void testUp() { 18 | 19 | def navigator = Navigator.start().table(tbl(data)) 20 | 21 | assert navigator.down().to('3').get() == 8 22 | 23 | def t = shouldFail { 24 | assert navigator.down().to('31').get() == 8 25 | } 26 | 27 | assert t.message == 'column[31] not found' 28 | 29 | assert navigator.down().to('3').get() == 8 30 | 31 | assert navigator.get() == '1' 32 | assert navigator.right().right().right().get() == '4' 33 | assert navigator.right().right().right().left().get() == '3' 34 | assert navigator.right().right().right().down().get() == 9 35 | assert navigator.right().right().right().down().up().get() == '4' 36 | 37 | assert navigator.right().right().right().up().get() == 14 //rotation 38 | 39 | 40 | def collect = navigator.allIter().collect { it.get() } 41 | 42 | assert collect == ['1', '2', '3', '4', '5', 6, 7, 8, 9, 10, 11, 12, 13, 14, 15] 43 | 44 | def coll2 = navigator.allBoundedIter(2, 1).collect { it.get() } 45 | assert coll2 == ['1', '2', '3', 6, 7, 8] 46 | 47 | assert navigator.downIter().skip().collect { it.get() } == [6, 11] 48 | assert navigator.downIter().collect { it.get() } == ['1', 6, 11] 49 | assert navigator.downIter().collect { it.get() } == ['1', 6, 11] 50 | assert navigator.rightIter().collect { it.get() } == ['1', '2', '3', '4', '5'] 51 | assert navigator.right().right().right().rightIter().collect { it.get() } == ['4', '5'] 52 | 53 | assert navigator.downIter().last().upIter().collect { it.get() } == [11, 6, '1'] 54 | 55 | def row = navigator.row(4) 56 | assert row.row == 4 && row.col == navigator.col 57 | assert !navigator.canGoLeft() && !navigator.canGoUp() 58 | assert navigator.canGoRight() && navigator.canGoDown() 59 | assert navigator.getTable().csv == data 60 | assert row.getTable().csv == data 61 | 62 | 63 | def copy = tbl(data).copy() 64 | assert navigator.set("Hhe", copy) 65 | assert copy.get(navigator) == 'Hhe' 66 | 67 | navigator.right().right().down().set(900, copy) 68 | assert copy.csv[1][2] == 900 69 | 70 | 71 | } 72 | 73 | @Test 74 | void testNavigationOnBorder() { 75 | def copy = tbl(data).copy() 76 | def navigator = Navigator.start().table(copy) 77 | 78 | def corner = navigator.row(0).col(4) 79 | 80 | assert corner.get() == '5' 81 | assert corner.rightIter().collect { it.get() } == ['5'] 82 | assert corner.right().rightIter().collect { it.get() } == [] 83 | assert corner.rightIter().find({ it.get() == '5' } as Fx1).get().get() == '5' 84 | 85 | 86 | assert corner.toTopLeft().get() == '1' 87 | assert corner.toToRight().get() == '5' 88 | assert corner.toBottomLeft().get() == 11 89 | assert corner.toBottomRight().get() == 15 90 | 91 | } 92 | 93 | @Test 94 | void testMutableNav() { 95 | 96 | def navigator = new Navigator(0, 0, tbl(data)) 97 | 98 | def nav = navigator.toMutableNav() 99 | 100 | 101 | nav.right().right() 102 | assert nav.value() == '3' 103 | 104 | 105 | nav.down().down().up() 106 | assert nav.value() == 8 107 | 108 | nav.right().right().left() 109 | assert nav.value() == 9 110 | 111 | nav.up().left().left().right().left() 112 | assert nav.value() == '2' 113 | 114 | 115 | } 116 | 117 | @Test 118 | void testAddAbove() { 119 | 120 | def table = tbl(data) 121 | def navigator = new Navigator(0, 0, table) 122 | 123 | navigator.down().addAbove().up().set("VVV") 124 | 125 | assert table.csv == [['1', '2', '3', '4', '5'], 126 | ['VVV', null, null, null, null], 127 | [6, 7, 8, 9, 10], 128 | [11, 12, 13, 14, 15]] 129 | 130 | } 131 | 132 | @Test 133 | void testAddBelow() { 134 | 135 | def table = tbl(data) 136 | def navigator = new Navigator(0, 0, table) 137 | 138 | navigator.down().addBelow().down().set("VVV") 139 | 140 | assert table.csv == [['1', '2', '3', '4', '5'], 141 | [6, 7, 8, 9, 10], 142 | ['VVV', null, null, null, null], 143 | [11, 12, 13, 14, 15]] 144 | 145 | 146 | } 147 | 148 | @Test 149 | void testDeleteRow() { 150 | 151 | def table = tbl(data) 152 | def navigator = new Navigator(0, 0, table) 153 | 154 | def n = navigator.down().deleteRow() 155 | 156 | assert n.table.csv == [['1', '2', '3', '4', '5'], 157 | [11, 12, 13, 14, 15]] 158 | 159 | 160 | } 161 | 162 | @Test 163 | void testDeleteCol() { 164 | 165 | def table = tbl(data) 166 | def navigator = new Navigator(0, 0, table) 167 | 168 | def n = navigator.down().deleteRow().right().deleteCol() 169 | 170 | assert n.table.csv == [['1', '3', '4', '5'], 171 | [11, 13, 14, 15]] 172 | 173 | 174 | } 175 | 176 | @Test 177 | void testDeleteColOnBoarder() { 178 | 179 | def table = tbl(data) 180 | def navigator = new Navigator(0, 0, table) 181 | 182 | def n = navigator.right(table.header.size() - 1).deleteCol() 183 | 184 | assert n.get() == '4' 185 | assert n.table.csv == tbl(data).delete('5').csv 186 | } 187 | 188 | @Test 189 | void testDeleteRowOnBoarder() { 190 | 191 | def table = tbl(data) 192 | def navigator = new Navigator(0, 0, table) 193 | 194 | def n = navigator.down(table.csv.size() - 1).deleteRow() 195 | 196 | assert n.get() == 6 // should be 6 coz 11 is deleted 197 | assert n.table.csv == tbl(data).delete { it.'1' == 11 }.csv 198 | } 199 | } 200 | -------------------------------------------------------------------------------- /src/test/groovy/fuzzycsv/FxExtensionsTest.groovy: -------------------------------------------------------------------------------- 1 | package fuzzycsv 2 | 3 | import org.junit.Test 4 | 5 | 6 | class FxExtensionsTest { 7 | 8 | @Test 9 | void testPlus() { 10 | 11 | use(FxExtensions) { 12 | assert ('3' + '2') == 5 13 | assert ('3'.toInteger() + '2') == 5 14 | assert ('3'.toBigInteger() + '2') == 5 15 | assert ('3'.toFloat() + '2') == 5 16 | assert ('3'.toDouble() + '2') == 5 17 | assert ('3'.toBigDecimal() + '2') == 5 18 | 19 | assert ('3' + '2'.toInteger()) == 5 20 | assert ('3' + '2'.toBigInteger()) == 5 21 | assert ('3' + '2'.toFloat()) == 5 22 | assert ('3' + '2'.toDouble()) == 5 23 | assert ('3' + '2'.toBigDecimal()) == 5 24 | 25 | assert ('3'.toInteger() + '2'.toDouble()) == 5 26 | assert ('3'.toBigInteger() + '2'.toBigInteger()) == 5 27 | assert ('3'.toFloat() + '2'.toFloat()) == 5 28 | assert ('3'.toDouble() + '2'.toDouble()) == 5 29 | assert ('3'.toBigDecimal() + '2'.toBigDecimal()) == 5 30 | } 31 | 32 | } 33 | 34 | @Test 35 | void testDiv() { 36 | 37 | use(FxExtensions) { 38 | assert ('4' / '2') == 2 39 | assert ('4'.toInteger() / '2') == 2 40 | assert ('4'.toBigInteger() / '2') == 2 41 | assert ('4'.toFloat() / '2') == 2 42 | assert ('4'.toDouble() / '2') == 2 43 | assert ('4'.toBigDecimal() / '2') == 2 44 | 45 | assert ('4' / '2'.toInteger()) == 2 46 | assert ('4' / '2'.toBigInteger()) == 2 47 | assert ('4' / '2'.toFloat()) == 2 48 | assert ('4' / '2'.toDouble()) == 2 49 | assert ('4' / '2'.toBigDecimal()) == 2 50 | 51 | assert ('4'.toInteger() / '2'.toDouble()) == 2 52 | assert ('4'.toBigInteger() / '2'.toBigInteger()) == 2 53 | assert ('4'.toFloat() / '2'.toFloat()) == 2 54 | assert ('4'.toDouble() / '2'.toDouble()) == 2 55 | assert ('4'.toBigDecimal() / '2'.toBigDecimal()) == 2 56 | } 57 | 58 | } 59 | 60 | @Test 61 | void testMinus() { 62 | 63 | use(FxExtensions) { 64 | assert ('4' - '2') == 2 65 | assert ('4'.toInteger() - '2') == 2 66 | assert ('4'.toBigInteger() - '2') == 2 67 | assert ('4'.toFloat() - '2') == 2 68 | assert ('4'.toDouble() - '2') == 2 69 | assert ('4'.toBigDecimal() - '2') == 2 70 | 71 | assert ('4' - '2'.toInteger()) == 2 72 | assert ('4' - '2'.toBigInteger()) == 2 73 | assert ('4' - '2'.toFloat()) == 2 74 | assert ('4' - '2'.toDouble()) == 2 75 | assert ('4' - '2'.toBigDecimal()) == 2 76 | 77 | assert ('4'.toInteger() - '2'.toDouble()) == 2 78 | assert ('4'.toBigInteger() - '2'.toBigInteger()) == 2 79 | assert ('4'.toFloat() - '2'.toFloat()) == 2 80 | assert ('4'.toDouble() - '2'.toDouble()) == 2 81 | assert ('4'.toBigDecimal() - '2'.toBigDecimal()) == 2 82 | } 83 | 84 | } 85 | 86 | @Test 87 | void testMultiply() { 88 | 89 | use(FxExtensions) { 90 | assert ('4' * '2') == 8 91 | assert ('4'.toInteger() * '2') == 8 92 | assert ('4'.toBigInteger() * '2') == 8 93 | assert ('4'.toFloat() * '2') == 8 94 | assert ('4'.toDouble() * '2') == 8 95 | assert ('4'.toBigDecimal() * '2') == 8 96 | 97 | assert ('4' * '2'.toInteger()) == 8 98 | assert ('4' * '2'.toBigInteger()) == 8 99 | assert ('4' * '2'.toFloat()) == 8 100 | assert ('4' * '2'.toDouble()) == 8 101 | assert ('4' * '2'.toBigDecimal()) == 8 102 | 103 | assert ('4'.toInteger() * '2'.toDouble()) == 8 104 | assert ('4'.toBigInteger() * '2'.toBigInteger()) == 8 105 | assert ('4'.toFloat() * '2'.toFloat()) == 8 106 | assert ('4'.toDouble() * '2'.toDouble()) == 8 107 | assert ('4'.toBigDecimal() * '2'.toBigDecimal()) == 8 108 | } 109 | 110 | } 111 | 112 | @Test 113 | void testNullToZero() { 114 | FxExtensions.treatNullAsZero() 115 | use(FxExtensions) { 116 | assert ('4' * null) == 0 117 | assert (null * '2') == 0 118 | assert (null * null) == 0 119 | } 120 | 121 | } 122 | 123 | 124 | @Test 125 | void testFloats() { 126 | FxExtensions.treatNullAsZero() 127 | use(FxExtensions) { 128 | assert 4.3 * 2 == 8.6 129 | assert (1 / 2) == 0.5 130 | assert 3.2 / 1.6 == 2 131 | assert (1.5 + 1.7) == 3.2 132 | assert 1.5 - 0.2 == 1.3 133 | assert null * 3 == 0 134 | assert null * null == 0 135 | 136 | //test the data types 137 | assert (2.0 * null) instanceof BigDecimal 138 | assert (2 * null) instanceof BigInteger 139 | assert (null * 2.0) instanceof BigDecimal 140 | assert (null * 2) instanceof BigInteger 141 | 142 | assert (2.0 + null) instanceof BigDecimal 143 | assert (2 + null) instanceof BigInteger 144 | assert (null + 2.0) instanceof BigDecimal 145 | assert (null + 2) instanceof BigInteger 146 | 147 | assert (2.0 - null) instanceof BigDecimal 148 | assert (2 - null) instanceof BigInteger 149 | assert (null - 2.0) instanceof BigDecimal 150 | assert (null - 2) instanceof BigInteger 151 | 152 | assert (2.0 / null) == null 153 | assert (2 / null) == null 154 | assert (null / 2.0) instanceof BigDecimal 155 | //divisions always return Decimal values 156 | assert (null / 2) instanceof BigDecimal 157 | } 158 | 159 | } 160 | 161 | @Test 162 | void testNullToNull() { 163 | FxExtensions.treatNullAsNull() 164 | use(FxExtensions) { 165 | assert ('4' * null) == null 166 | assert (null * '2') == null 167 | assert (null * null) == null 168 | assert null / null == null 169 | assert (null) / null * 100.0 == null 170 | } 171 | } 172 | 173 | @Test 174 | void testAvg() { 175 | use(FxExtensions) { 176 | assert [2, null, 4].avg() == 3 177 | assert ['2', null, 4].avg() == 3 178 | assert [null, null, null].avg() == null 179 | assert [2, 3, 5, 0, 0].avg() == 2 180 | assert [null, 2, 3, 5, 0, 0].avg() == 2 181 | } 182 | } 183 | } 184 | -------------------------------------------------------------------------------- /src/test/groovy/fuzzycsv/ConverterTest.java: -------------------------------------------------------------------------------- 1 | package fuzzycsv; 2 | 3 | import org.junit.jupiter.api.Nested; 4 | import org.junit.jupiter.api.Test; 5 | 6 | import static fuzzycsv.Sort.byColumn; 7 | import static java.util.Arrays.asList; 8 | import static org.junit.jupiter.api.Assertions.assertEquals; 9 | 10 | class ConverterTest { 11 | FuzzyCSVTable table = FuzzyCSVTable.fromRows( 12 | asList("name", "lname", "data"), 13 | asList("joe", "lasty", "1.1"), 14 | asList("joz", "lasty", "1.1") 15 | ); 16 | 17 | 18 | @Nested 19 | class Csv { 20 | 21 | @Test 22 | void shouldConvertToCsvString() { 23 | String output = Converter.Csv.create().withTable(table).getResult(); 24 | 25 | assertEquals("\"name\",\"lname\",\"data\"\n" + 26 | "\"joe\",\"lasty\",\"1.1\"\n" + 27 | "\"joz\",\"lasty\",\"1.1\"\n", output); 28 | } 29 | 30 | 31 | @Test 32 | void testWithSemiColonDelimiter() { 33 | 34 | String output = Converter.Csv.create().withTable(table).withDelimiter(";").getResult(); 35 | 36 | assertEquals("\"name\";\"lname\";\"data\"\n" + 37 | "\"joe\";\"lasty\";\"1.1\"\n" + 38 | "\"joz\";\"lasty\";\"1.1\"\n", output); 39 | 40 | } 41 | 42 | @Test 43 | void testWithCustomQuote() { 44 | String output = Converter.Csv.create().withTable(table).withQuote("'").getResult(); 45 | 46 | assertEquals("'name','lname','data'\n" + 47 | "'joe','lasty','1.1'\n" + 48 | "'joz','lasty','1.1'\n", output); 49 | } 50 | 51 | @Test 52 | void testWithCustomEscape() { 53 | FuzzyCSVTable tableCopy = table.copy().set(1, 1, "J\"ane"); 54 | String output = Converter.Csv.create() 55 | .withTable(tableCopy) 56 | .withEscape("-") 57 | .getResult(); 58 | 59 | assertEquals("\"name\",\"lname\",\"data\"\n" + 60 | "\"joe\",\"J-\"ane\",\"1.1\"\n" + 61 | "\"joz\",\"lasty\",\"1.1\"\n", output); 62 | 63 | 64 | } 65 | 66 | @Test 67 | void testWithAllCustoms() { 68 | FuzzyCSVTable tableCopy = table.copy().set(1, 1, "J'ane"); 69 | String ouput = Converter.Csv.create() 70 | .withTable(tableCopy) 71 | .withEscape("-") 72 | .withQuote("'") 73 | .withDelimiter(";") 74 | .getResult(); 75 | 76 | assertEquals("'name';'lname';'data'\n" + 77 | "'joe';'J-'ane';'1.1'\n" + 78 | "'joz';'lasty';'1.1'\n", ouput); 79 | 80 | } 81 | 82 | @Test 83 | void testNoQuotes() { 84 | String output = Converter.Csv.create().withTable(table).withQuoteAll(false).getResult(); 85 | 86 | assertEquals("name,lname,data\n" + 87 | "joe,lasty,1.1\n" + 88 | "joz,lasty,1.1\n", output); 89 | } 90 | 91 | @Test 92 | void testWithCustomLineSeparator() { 93 | String output = Converter.Csv.create().withTable(table).withLineSeparator("\r\n").getResult(); 94 | 95 | assertEquals("\"name\",\"lname\",\"data\"\r\n" + 96 | "\"joe\",\"lasty\",\"1.1\"\r\n" + 97 | "\"joz\",\"lasty\",\"1.1\"\r\n", output); 98 | } 99 | 100 | 101 | } 102 | 103 | @Nested 104 | class Json { 105 | 106 | @Test 107 | void shouldConvertToJsonWithMaps() { 108 | Converter.Json json = Converter.Json.create().withTable(table); 109 | 110 | String output = json.withAsMaps(true).getResult(); 111 | 112 | assertEquals("[{\"name\":\"joe\",\"lname\":\"lasty\",\"data\":\"1.1\"},{\"name\":\"joz\",\"lname\":\"lasty\",\"data\":\"1.1\"}]", output); 113 | } 114 | 115 | @Test 116 | void shouldConvertToJsonWithArrays() { 117 | Converter.Json json = Converter.Json.create().withTable(table); 118 | 119 | String output = json.withAsMaps(false).getResult(); 120 | 121 | assertEquals("[[\"name\",\"lname\",\"data\"],[\"joe\",\"lasty\",\"1.1\"],[\"joz\",\"lasty\",\"1.1\"]]", output); 122 | } 123 | 124 | @Test 125 | void shouldConvertToJsonWithMapsPrettyPrinted() { 126 | Converter.Json json = Converter.Json.create().withTable(table); 127 | 128 | String output = json.withAsMaps(true).withPrettyPrint(true).getResult(); 129 | 130 | assertEquals("[\n" + 131 | " {\n" + 132 | " \"name\": \"joe\",\n" + 133 | " \"lname\": \"lasty\",\n" + 134 | " \"data\": \"1.1\"\n" + 135 | " },\n" + 136 | " {\n" + 137 | " \"name\": \"joz\",\n" + 138 | " \"lname\": \"lasty\",\n" + 139 | " \"data\": \"1.1\"\n" + 140 | " }\n" + 141 | "]", output); 142 | } 143 | 144 | } 145 | 146 | @Nested 147 | class Pretty { 148 | 149 | @Test 150 | void withTable() { 151 | Converter.Pretty pretty = Converter.Pretty.create().withTable(table); 152 | 153 | String output = pretty.getResult(); 154 | 155 | assertEquals("╔══════╤═══════╤══════╗\n" + 156 | "║ name │ lname │ data ║\n" + 157 | "╠══════╪═══════╪══════╣\n" + 158 | "║ joe │ lasty │ 1.1 ║\n" + 159 | "╟──────┼───────┼──────╢\n" + 160 | "║ joz │ lasty │ 1.1 ║\n" + 161 | "╚══════╧═══════╧══════╝\n", output); 162 | } 163 | 164 | @Test 165 | void shouldPrettyPrintNestedTable() { 166 | String t = "{\"name\":\"joe\",\"lname\":\"lasty\",\"data\":[[\"name\",\"number\"],[\"john\",1.1]]}"; 167 | 168 | String output = FuzzyCSVTable.fromJsonText(t) 169 | .sortBy(byColumn("key")) 170 | .toGrid(GridOptions.LIST_AS_TABLE) 171 | .to().pretty().getResult(); 172 | 173 | String expected = "╔═══════╤═══════════════════╗\n" + 174 | "║ key │ value ║\n" + 175 | "╠═══════╪═══════════════════╣\n" + 176 | "║ data │ ╔══════╤════════╗ ║\n" + 177 | "║ │ ║ name │ number ║ ║\n" + 178 | "║ │ ╠══════╪════════╣ ║\n" + 179 | "║ │ ║ john │ 1.1 ║ ║\n" + 180 | "║ │ ╚══════╧════════╝ ║\n" + 181 | "╟───────┼───────────────────╢\n" + 182 | "║ lname │ lasty ║\n" + 183 | "╟───────┼───────────────────╢\n" + 184 | "║ name │ joe ║\n" + 185 | "╚═══════╧═══════════════════╝\n"; 186 | 187 | assertEquals(expected, output); 188 | } 189 | 190 | } 191 | } 192 | -------------------------------------------------------------------------------- /src/test/groovy/fuzzycsv/javaly/NumbTest.java: -------------------------------------------------------------------------------- 1 | package fuzzycsv.javaly; 2 | 3 | import org.junit.jupiter.api.Test; 4 | 5 | import java.math.BigDecimal; 6 | 7 | import static org.junit.jupiter.api.Assertions.*; 8 | 9 | class NumbTest { 10 | 11 | @Test 12 | void of() { 13 | assertEquals(1L, Numb.of("1").unwrap().longValue()); 14 | assertEquals(1.0d, Numb.of("1.0").unwrap().doubleValue(), 0.0001); 15 | assertEquals(1, Numb.of(new BigDecimal("1")).unwrap().longValue()); 16 | assertEquals(1, Numb.of(1).unwrap().longValue()); 17 | assertEquals(1, Numb.of(1L).unwrap().longValue()); 18 | assertEquals(1, Numb.of(1.0d).unwrap().doubleValue(), 0.0001); 19 | assertEquals(1, Numb.of(1.0f).unwrap().doubleValue(), 0.0001); 20 | } 21 | 22 | @Test 23 | void eq() { 24 | assertTrue(Numb.of("1").eq(new BigDecimal("1"))); 25 | assertTrue(Numb.of("1").eq(1)); 26 | assertTrue(Numb.of("1").eq(1L)); 27 | assertTrue(Numb.of("1").eq(1.0d)); 28 | assertTrue(Numb.of("1").eq(1.0f)); 29 | assertTrue(Numb.of(null).eq(null)); 30 | assertTrue(Numb.of(null).eq(Numb.nullV())); 31 | 32 | 33 | assertThrows(IllegalArgumentException.class, () -> Numb.of("1").eq("1")); 34 | assertFalse(Numb.of("1").eq(null)); 35 | assertFalse(Numb.of(null).eq(1)); 36 | } 37 | 38 | @Test 39 | void neq() { 40 | assertFalse(Numb.of("1").neq(new BigDecimal("1"))); 41 | assertFalse(Numb.of("1").neq(1)); 42 | assertFalse(Numb.of("1").neq(1L)); 43 | assertFalse(Numb.of("1").neq(1.0d)); 44 | assertFalse(Numb.of("1").neq(1.0f)); 45 | assertFalse(Numb.of(null).neq(null)); 46 | assertFalse(Numb.of(null).neq(Numb.nullV())); 47 | 48 | 49 | assertThrows(IllegalArgumentException.class, () -> Numb.of("1").neq("1")); 50 | assertTrue(Numb.of("1").neq(null)); 51 | assertTrue(Numb.of(null).neq(1)); 52 | } 53 | 54 | @Test 55 | void gt() { 56 | 57 | assertTrue(Numb.of(2).gt(1)); 58 | assertTrue(Numb.of(2.03).gt(2.02)); 59 | 60 | //NULLS 61 | assertFalse(Numb.of(null).gt(null)); 62 | assertFalse(Numb.of(null).gt(Numb.nullV())); 63 | //not(NULL > 1) 64 | assertFalse(Numb.of(null).gt(1)); 65 | //1 > NULL 66 | assertTrue(Numb.of(1).gt(null)); 67 | assertTrue(Numb.of(1).gt(Numb.nullV())); 68 | //0 > NULL 69 | assertTrue(Numb.of(0).gt(null)); 70 | //-1 > NULL 71 | assertTrue(Numb.of(-1).gt(null)); 72 | } 73 | 74 | @Test 75 | void gte() { 76 | 77 | assertTrue(Numb.of(2).gte(1)); 78 | assertTrue(Numb.of(2).gte(2)); 79 | assertTrue(Numb.of(2.03).gte(2.02)); 80 | 81 | //NULLS 82 | assertTrue(Numb.of(null).gte(null)); 83 | assertTrue(Numb.of(null).gte(Numb.nullV())); 84 | //not(NULL >= 1) 85 | assertFalse(Numb.of(null).gte(1)); 86 | //1 >= NULL 87 | assertTrue(Numb.of(1).gte(null)); 88 | //0 >= NULL 89 | assertTrue(Numb.of(0).gte(null)); 90 | //-1 >= NULL 91 | assertTrue(Numb.of(-1).gte(null)); 92 | } 93 | 94 | @Test 95 | void lt() { 96 | 97 | assertTrue(Numb.of(1).lt(2)); 98 | assertTrue(Numb.of(2.02).lt(2.03)); 99 | 100 | //NULLS 101 | assertFalse(Numb.of(null).lt(null)); 102 | assertFalse(Numb.of(null).lt(Numb.nullV())); 103 | //NULL < 1 104 | assertTrue(Numb.of(null).lt(1)); 105 | //NULL < 0 106 | assertTrue(Numb.of(null).lt(0)); 107 | //NULL < -1 108 | assertTrue(Numb.of(null).lt(-1)); 109 | //1 < NULL 110 | assertFalse(Numb.of(1).lt(null)); 111 | //0 < NULL 112 | assertFalse(Numb.of(0).lt(null)); 113 | //-1 < NULL 114 | assertFalse(Numb.of(-1).lt(null)); 115 | } 116 | 117 | @Test 118 | void lte() { 119 | 120 | assertTrue(Numb.of(1).lte(2)); 121 | assertTrue(Numb.of(2).lte(2)); 122 | assertTrue(Numb.of(2.02).lte(2.03)); 123 | 124 | //NULLS 125 | assertTrue(Numb.of(null).lte(null)); 126 | assertTrue(Numb.of(null).lte(Numb.nullV())); 127 | //NULL <= 1 128 | assertTrue(Numb.of(null).lte(1)); 129 | //NULL <= 0 130 | assertTrue(Numb.of(null).lte(0)); 131 | //NULL <= -1 132 | assertTrue(Numb.of(null).lte(-1)); 133 | //1 <= NULL 134 | assertFalse(Numb.of(1).lte(null)); 135 | //0 <= NULL 136 | assertFalse(Numb.of(0).lte(null)); 137 | //-1 <= NULL 138 | assertFalse(Numb.of(-1).lte(null)); 139 | } 140 | 141 | @Test 142 | void plus() { 143 | Numb one = Numb.of(1); 144 | Numb nullNumb = Numb.nullV(); 145 | 146 | assertEquals(3, one.plus(2).unwrap().intValue()); 147 | 148 | //1.4 + 1.6 = 3.0 149 | assertEquals(3.0, Numb.of(1.4).plus(1.6).unwrap().doubleValue(), 0.0001); 150 | 151 | //nulls throw exception 152 | assertThrows(IllegalArgumentException.class, () -> nullNumb.plus(1)); 153 | assertThrows(IllegalArgumentException.class, () -> one.plus(null)); 154 | assertThrows(IllegalArgumentException.class, () -> nullNumb.plus(null)); 155 | assertThrows(IllegalArgumentException.class, () -> nullNumb.plus(nullNumb)); 156 | } 157 | 158 | @Test 159 | void minus() { 160 | Numb one = Numb.of(1); 161 | Numb nullNumb = Numb.nullV(); 162 | 163 | assertEquals(-1, one.minus(2).unwrap().intValue()); 164 | 165 | //1.4 - 1.6 = -0.2 166 | assertEquals(-0.2, Numb.of(1.4).minus(1.6).unwrap().doubleValue(), 0.0001); 167 | 168 | //nulls throw exception 169 | assertThrows(IllegalArgumentException.class, () -> nullNumb.minus(1)); 170 | assertThrows(IllegalArgumentException.class, () -> one.minus(null)); 171 | assertThrows(IllegalArgumentException.class, () -> nullNumb.minus(null)); 172 | assertThrows(IllegalArgumentException.class, () -> nullNumb.minus(nullNumb)); 173 | } 174 | 175 | @Test 176 | void times() { 177 | Numb one = Numb.of(1); 178 | Numb nullNumb = Numb.nullV(); 179 | 180 | assertEquals(2, one.times(2).unwrap().intValue()); 181 | 182 | //1.4 * 1.6 = 2.24 183 | assertEquals(2.24, Numb.of(1.4).times(1.6).unwrap().doubleValue(), 0.0001); 184 | 185 | //nulls throw exception 186 | assertThrows(IllegalArgumentException.class, () -> nullNumb.times(1)); 187 | assertThrows(IllegalArgumentException.class, () -> one.times(null)); 188 | assertThrows(IllegalArgumentException.class, () -> nullNumb.times(null)); 189 | assertThrows(IllegalArgumentException.class, () -> nullNumb.times(nullNumb)); 190 | } 191 | 192 | @Test 193 | void div() { 194 | Numb one = Numb.of(1); 195 | Numb nullNumb = Numb.nullV(); 196 | 197 | assertEquals(0.5, one.div(2).unwrap().doubleValue(), 0.0001); 198 | assertTrue(one.div(3).eq(Numb.of("0.3333333333"))); 199 | 200 | //1.4 / 1.6 = 0.875 201 | assertEquals(0.875, Numb.of(1.4).div(1.6).unwrap().doubleValue(), 0.0001); 202 | 203 | //nulls throw exception 204 | assertThrows(IllegalArgumentException.class, () -> nullNumb.div(1)); 205 | assertThrows(IllegalArgumentException.class, () -> one.div(null)); 206 | assertThrows(IllegalArgumentException.class, () -> nullNumb.div(null)); 207 | assertThrows(IllegalArgumentException.class, () -> nullNumb.div(nullNumb)); 208 | } 209 | 210 | @Test 211 | void pow() { 212 | Numb one = Numb.of(1); 213 | Numb nullNumb = Numb.nullV(); 214 | 215 | assertEquals(100, Numb.of(10).pow(2).unwrap().intValue()); 216 | 217 | //1.4 ^ 1.6 = 1.4 218 | assertEquals(1.4, Numb.of(1.4).pow(1.6).unwrap().doubleValue(), 0.0001); 219 | 220 | //nulls throw exception 221 | assertThrows(IllegalArgumentException.class, () -> nullNumb.pow(1)); 222 | assertThrows(IllegalArgumentException.class, () -> one.pow(null)); 223 | assertThrows(IllegalArgumentException.class, () -> nullNumb.pow(null)); 224 | assertThrows(IllegalArgumentException.class, () -> nullNumb.pow(nullNumb)); 225 | } 226 | 227 | @Test 228 | void neg() { 229 | Numb one = Numb.of(1); 230 | Numb nullNumb = Numb.nullV(); 231 | 232 | assertEquals(-1, one.neg().unwrap().intValue()); 233 | 234 | //nulls throw exception 235 | assertThrows(IllegalArgumentException.class, nullNumb::neg); 236 | } 237 | 238 | } -------------------------------------------------------------------------------- /src/main/groovy/fuzzycsv/Importer.java: -------------------------------------------------------------------------------- 1 | package fuzzycsv; 2 | 3 | 4 | import com.opencsv.CSVParser; 5 | import com.opencsv.CSVReader; 6 | import com.opencsv.CSVReaderBuilder; 7 | import com.opencsv.exceptions.CsvValidationException; 8 | import groovy.json.JsonSlurper; 9 | import lombok.AccessLevel; 10 | 11 | import javax.sql.DataSource; 12 | import java.io.IOException; 13 | import java.io.Reader; 14 | import java.io.StringReader; 15 | import java.nio.file.Files; 16 | import java.nio.file.Path; 17 | import java.nio.file.Paths; 18 | import java.sql.Connection; 19 | import java.sql.ResultSet; 20 | import java.sql.SQLException; 21 | import java.sql.Statement; 22 | import java.util.*; 23 | import java.util.stream.Collectors; 24 | 25 | import static fuzzycsv.FuzzyCSVUtils.closeQuietly; 26 | import static fuzzycsv.FuzzyCSVUtils.list; 27 | 28 | public class Importer { 29 | 30 | private Importer() { 31 | } 32 | 33 | static Importer from() { 34 | return new Importer(); 35 | } 36 | 37 | public Csv csv() { 38 | return Csv.create(); 39 | } 40 | 41 | public Json json() { 42 | return Json.create(); 43 | } 44 | 45 | public Database db() { 46 | return Database.create(); 47 | } 48 | 49 | public FuzzyCSVTable listsOrMaps(Object obj) { 50 | return FuzzyCSVTable.coerceFromObj(obj); 51 | } 52 | 53 | public FuzzyCSVTable map(Map map) { 54 | List> csv = new ArrayList<>(map.size()); 55 | csv.add(list("key", "value")); 56 | for (Map.Entry entry : map.entrySet()) { 57 | String k = entry.getKey(); 58 | Object v = entry.getValue(); 59 | csv.add(list(k, v)); 60 | } 61 | return lists(csv); 62 | } 63 | 64 | 65 | public FuzzyCSVTable maps(Collection> maps) { 66 | return FuzzyCSVTable.tbl(FuzzyCSV.toCSVLenient(maps)); 67 | } 68 | 69 | public FuzzyCSVTable records(Collection records) { 70 | return FuzzyCSVTable.tbl(FuzzyCSV.toCSVFromRecordList(records)); 71 | } 72 | 73 | public FuzzyCSVTable lists(List> csv) { 74 | return FuzzyCSVTable.tbl(csv); 75 | } 76 | 77 | public FuzzyCSVTable rows(List... rows) { 78 | return FuzzyCSVTable.tbl(list(rows)); 79 | } 80 | 81 | public FuzzyCSVTable pojos(Collection pojos) { 82 | List> mapList = pojos.stream().map(FuzzyCSVUtils::toProperties).collect(Collectors.toList()); 83 | return maps(mapList); 84 | } 85 | 86 | public FuzzyCSVTable pojo(Object pojo) { 87 | return map(FuzzyCSVUtils.toProperties(pojo)); 88 | } 89 | 90 | 91 | @lombok.With 92 | @lombok.NoArgsConstructor(access = AccessLevel.PRIVATE) 93 | @lombok.AllArgsConstructor(access = AccessLevel.PRIVATE) 94 | public static class Csv { 95 | private char delimiter = ','; 96 | private char quote = '"'; 97 | private char escape = '\\'; 98 | 99 | public static Csv create() { 100 | return new Csv(); 101 | } 102 | 103 | public FuzzyCSVTable parseText(String csvString) { 104 | return parse(new StringReader(csvString)); 105 | } 106 | 107 | public FuzzyCSVTable parse(String path) { 108 | return parse(Paths.get(path)); 109 | } 110 | 111 | public FuzzyCSVTable parse(Path path) { 112 | try (Reader reader = Files.newBufferedReader(path)) { 113 | return parse(reader); 114 | } catch (IOException e) { 115 | throw new FuzzyCsvException("Failed parsing CSV File: " + path, e); 116 | } 117 | } 118 | 119 | public FuzzyCSVTable parse(Reader reader) { 120 | 121 | CSVParser parser = new com.opencsv.CSVParserBuilder() 122 | .withSeparator(delimiter) 123 | .withQuoteChar(quote) 124 | .withEscapeChar(escape) 125 | .build(); 126 | 127 | CSVReader builder = new CSVReaderBuilder(reader) 128 | .withCSVParser(parser) 129 | .build(); 130 | 131 | 132 | FuzzyCSVTable tbl = FuzzyCSVTable.tbl(); 133 | 134 | try (CSVReader rd = builder) { 135 | String[] header = rd.readNext(); 136 | if (header != null) { 137 | tbl = tbl.setHeader(Arrays.asList(header)); 138 | } 139 | Object[] nextRow = rd.readNext(); 140 | while (nextRow != null) { 141 | tbl = tbl.addRow(nextRow); 142 | nextRow = rd.readNext(); 143 | } 144 | 145 | } catch (IOException | CsvValidationException e) { 146 | throw new FuzzyCsvException("Failed parsing CSV From Reader", e); 147 | } 148 | return tbl; 149 | } 150 | 151 | // public FuzzyCSVTable parse(Reader reader) { 152 | // CsvReader csvReader = CsvReader.builder() 153 | // .fieldSeparator(delimiter) 154 | // .quoteCharacter(quote) 155 | // .skipEmptyRows(false) 156 | // .errorOnDifferentFieldCount(false) 157 | // .build(reader); 158 | // FuzzyCSVTable tbl = FuzzyCSVTable.tbl(); 159 | // boolean first = true; 160 | // for (CsvRow row : csvReader) { 161 | // if (first) { 162 | // tbl.setHeader(row.getFields()); 163 | // first = false; 164 | // }else { 165 | // tbl = tbl.addRows(row.getFields()); 166 | // } 167 | // } 168 | // 169 | // return tbl; 170 | // 171 | // } 172 | } 173 | 174 | @lombok.NoArgsConstructor(access = AccessLevel.PRIVATE) 175 | public static class Json { 176 | 177 | public static Json create() { 178 | return new Json(); 179 | } 180 | 181 | public FuzzyCSVTable parseText(String json) { 182 | Object o = new JsonSlurper().parseText(json); 183 | return Importer.from().listsOrMaps(o); 184 | } 185 | 186 | public FuzzyCSVTable parse(Path path) { 187 | try { 188 | Object object = new JsonSlurper().parse(Files.newBufferedReader(path)); 189 | return Importer.from().listsOrMaps(object); 190 | } catch (IOException e) { 191 | throw new FuzzyCsvException("Failed parsing JSON File", e); 192 | } 193 | } 194 | 195 | public FuzzyCSVTable parse(Reader reader) { 196 | Object object = new JsonSlurper().parse(reader); 197 | return Importer.from().listsOrMaps(object); 198 | } 199 | 200 | public FuzzyCSVTable parsePath(String path) { 201 | return parse(Paths.get(path)); 202 | } 203 | 204 | } 205 | 206 | @lombok.With 207 | @lombok.NoArgsConstructor(access = AccessLevel.PRIVATE) 208 | @lombok.AllArgsConstructor(access = AccessLevel.PRIVATE) 209 | public static class Database { 210 | 211 | private DataSource dataSource; 212 | private Connection connection; 213 | 214 | public static Database create() { 215 | return new Database(); 216 | } 217 | 218 | public FuzzyCSVTable fetch(String query) { 219 | Connection theConnection = getConnection(); 220 | try ( 221 | Statement preparedStatement = theConnection.createStatement(); 222 | ResultSet resultSet = preparedStatement.executeQuery(query)) { 223 | 224 | return fetch(resultSet); 225 | 226 | } catch (SQLException e) { 227 | throw FuzzyCsvException.wrap(e); 228 | } finally { 229 | mayBeCloseConnection(theConnection); 230 | } 231 | } 232 | 233 | public FuzzyCSVTable fetch(ResultSet resultSet) { 234 | List> csv = FuzzyCSV.toCSV(resultSet); 235 | return FuzzyCSVTable.tbl(csv); 236 | } 237 | 238 | 239 | private void mayBeCloseConnection(Connection connection) { 240 | if (isUsingDataSource()) { 241 | closeQuietly(connection); 242 | } 243 | } 244 | 245 | private boolean isUsingDataSource() { 246 | return dataSource != null; 247 | } 248 | 249 | public Connection getConnection() { 250 | if (isUsingDataSource()) { 251 | return FuzzyCsvException.wrap(() -> dataSource.getConnection()); 252 | } 253 | if (connection == null) { 254 | throw new IllegalStateException("No connection or datasource set"); 255 | } 256 | return connection; 257 | } 258 | 259 | 260 | } 261 | } 262 | -------------------------------------------------------------------------------- /gradlew: -------------------------------------------------------------------------------- 1 | #!/bin/sh 2 | 3 | # 4 | # Copyright © 2015-2021 the original authors. 5 | # 6 | # Licensed under the Apache License, Version 2.0 (the "License"); 7 | # you may not use this file except in compliance with the License. 8 | # You may obtain a copy of the License at 9 | # 10 | # https://www.apache.org/licenses/LICENSE-2.0 11 | # 12 | # Unless required by applicable law or agreed to in writing, software 13 | # distributed under the License is distributed on an "AS IS" BASIS, 14 | # WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 15 | # See the License for the specific language governing permissions and 16 | # limitations under the License. 17 | # 18 | 19 | ############################################################################## 20 | # 21 | # Gradle start up script for POSIX generated by Gradle. 22 | # 23 | # Important for running: 24 | # 25 | # (1) You need a POSIX-compliant shell to run this script. If your /bin/sh is 26 | # noncompliant, but you have some other compliant shell such as ksh or 27 | # bash, then to run this script, type that shell name before the whole 28 | # command line, like: 29 | # 30 | # ksh Gradle 31 | # 32 | # Busybox and similar reduced shells will NOT work, because this script 33 | # requires all of these POSIX shell features: 34 | # * functions; 35 | # * expansions «$var», «${var}», «${var:-default}», «${var+SET}», 36 | # «${var#prefix}», «${var%suffix}», and «$( cmd )»; 37 | # * compound commands having a testable exit status, especially «case»; 38 | # * various built-in commands including «command», «set», and «ulimit». 39 | # 40 | # Important for patching: 41 | # 42 | # (2) This script targets any POSIX shell, so it avoids extensions provided 43 | # by Bash, Ksh, etc; in particular arrays are avoided. 44 | # 45 | # The "traditional" practice of packing multiple parameters into a 46 | # space-separated string is a well documented source of bugs and security 47 | # problems, so this is (mostly) avoided, by progressively accumulating 48 | # options in "$@", and eventually passing that to Java. 49 | # 50 | # Where the inherited environment variables (DEFAULT_JVM_OPTS, JAVA_OPTS, 51 | # and GRADLE_OPTS) rely on word-splitting, this is performed explicitly; 52 | # see the in-line comments for details. 53 | # 54 | # There are tweaks for specific operating systems such as AIX, CygWin, 55 | # Darwin, MinGW, and NonStop. 56 | # 57 | # (3) This script is generated from the Groovy template 58 | # https://github.com/gradle/gradle/blob/master/subprojects/plugins/src/main/resources/org/gradle/api/internal/plugins/unixStartScript.txt 59 | # within the Gradle project. 60 | # 61 | # You can find Gradle at https://github.com/gradle/gradle/. 62 | # 63 | ############################################################################## 64 | 65 | # Attempt to set APP_HOME 66 | 67 | # Resolve links: $0 may be a link 68 | app_path=$0 69 | 70 | # Need this for daisy-chained symlinks. 71 | while 72 | APP_HOME=${app_path%"${app_path##*/}"} # leaves a trailing /; empty if no leading path 73 | [ -h "$app_path" ] 74 | do 75 | ls=$( ls -ld "$app_path" ) 76 | link=${ls#*' -> '} 77 | case $link in #( 78 | /*) app_path=$link ;; #( 79 | *) app_path=$APP_HOME$link ;; 80 | esac 81 | done 82 | 83 | APP_HOME=$( cd "${APP_HOME:-./}" && pwd -P ) || exit 84 | 85 | APP_NAME="Gradle" 86 | APP_BASE_NAME=${0##*/} 87 | 88 | # Add default JVM options here. You can also use JAVA_OPTS and GRADLE_OPTS to pass JVM options to this script. 89 | DEFAULT_JVM_OPTS='"-Xmx64m" "-Xms64m"' 90 | 91 | # Use the maximum available, or set MAX_FD != -1 to use that value. 92 | MAX_FD=maximum 93 | 94 | warn () { 95 | echo "$*" 96 | } >&2 97 | 98 | die () { 99 | echo 100 | echo "$*" 101 | echo 102 | exit 1 103 | } >&2 104 | 105 | # OS specific support (must be 'true' or 'false'). 106 | cygwin=false 107 | msys=false 108 | darwin=false 109 | nonstop=false 110 | case "$( uname )" in #( 111 | CYGWIN* ) cygwin=true ;; #( 112 | Darwin* ) darwin=true ;; #( 113 | MSYS* | MINGW* ) msys=true ;; #( 114 | NONSTOP* ) nonstop=true ;; 115 | esac 116 | 117 | CLASSPATH=$APP_HOME/gradle/wrapper/gradle-wrapper.jar 118 | 119 | 120 | # Determine the Java command to use to start the JVM. 121 | if [ -n "$JAVA_HOME" ] ; then 122 | if [ -x "$JAVA_HOME/jre/sh/java" ] ; then 123 | # IBM's JDK on AIX uses strange locations for the executables 124 | JAVACMD=$JAVA_HOME/jre/sh/java 125 | else 126 | JAVACMD=$JAVA_HOME/bin/java 127 | fi 128 | if [ ! -x "$JAVACMD" ] ; then 129 | die "ERROR: JAVA_HOME is set to an invalid directory: $JAVA_HOME 130 | 131 | Please set the JAVA_HOME variable in your environment to match the 132 | location of your Java installation." 133 | fi 134 | else 135 | JAVACMD=java 136 | which java >/dev/null 2>&1 || die "ERROR: JAVA_HOME is not set and no 'java' command could be found in your PATH. 137 | 138 | Please set the JAVA_HOME variable in your environment to match the 139 | location of your Java installation." 140 | fi 141 | 142 | # Increase the maximum file descriptors if we can. 143 | if ! "$cygwin" && ! "$darwin" && ! "$nonstop" ; then 144 | case $MAX_FD in #( 145 | max*) 146 | MAX_FD=$( ulimit -H -n ) || 147 | warn "Could not query maximum file descriptor limit" 148 | esac 149 | case $MAX_FD in #( 150 | '' | soft) :;; #( 151 | *) 152 | ulimit -n "$MAX_FD" || 153 | warn "Could not set maximum file descriptor limit to $MAX_FD" 154 | esac 155 | fi 156 | 157 | # Collect all arguments for the java command, stacking in reverse order: 158 | # * args from the command line 159 | # * the main class name 160 | # * -classpath 161 | # * -D...appname settings 162 | # * --module-path (only if needed) 163 | # * DEFAULT_JVM_OPTS, JAVA_OPTS, and GRADLE_OPTS environment variables. 164 | 165 | # For Cygwin or MSYS, switch paths to Windows format before running java 166 | if "$cygwin" || "$msys" ; then 167 | APP_HOME=$( cygpath --path --mixed "$APP_HOME" ) 168 | CLASSPATH=$( cygpath --path --mixed "$CLASSPATH" ) 169 | 170 | JAVACMD=$( cygpath --unix "$JAVACMD" ) 171 | 172 | # Now convert the arguments - kludge to limit ourselves to /bin/sh 173 | for arg do 174 | if 175 | case $arg in #( 176 | -*) false ;; # don't mess with options #( 177 | /?*) t=${arg#/} t=/${t%%/*} # looks like a POSIX filepath 178 | [ -e "$t" ] ;; #( 179 | *) false ;; 180 | esac 181 | then 182 | arg=$( cygpath --path --ignore --mixed "$arg" ) 183 | fi 184 | # Roll the args list around exactly as many times as the number of 185 | # args, so each arg winds up back in the position where it started, but 186 | # possibly modified. 187 | # 188 | # NB: a `for` loop captures its iteration list before it begins, so 189 | # changing the positional parameters here affects neither the number of 190 | # iterations, nor the values presented in `arg`. 191 | shift # remove old arg 192 | set -- "$@" "$arg" # push replacement arg 193 | done 194 | fi 195 | 196 | # Collect all arguments for the java command; 197 | # * $DEFAULT_JVM_OPTS, $JAVA_OPTS, and $GRADLE_OPTS can contain fragments of 198 | # shell script including quotes and variable substitutions, so put them in 199 | # double quotes to make sure that they get re-expanded; and 200 | # * put everything else in single quotes, so that it's not re-expanded. 201 | 202 | set -- \ 203 | "-Dorg.gradle.appname=$APP_BASE_NAME" \ 204 | -classpath "$CLASSPATH" \ 205 | org.gradle.wrapper.GradleWrapperMain \ 206 | "$@" 207 | 208 | # Use "xargs" to parse quoted args. 209 | # 210 | # With -n1 it outputs one arg per line, with the quotes and backslashes removed. 211 | # 212 | # In Bash we could simply go: 213 | # 214 | # readarray ARGS < <( xargs -n1 <<<"$var" ) && 215 | # set -- "${ARGS[@]}" "$@" 216 | # 217 | # but POSIX shell has neither arrays nor command substitution, so instead we 218 | # post-process each arg (as a line of input to sed) to backslash-escape any 219 | # character that might be a shell metacharacter, then use eval to reverse 220 | # that process (while maintaining the separation between arguments), and wrap 221 | # the whole thing up as a single "set" statement. 222 | # 223 | # This will of course break if any of these variables contains a newline or 224 | # an unmatched quote. 225 | # 226 | 227 | eval "set -- $( 228 | printf '%s\n' "$DEFAULT_JVM_OPTS $JAVA_OPTS $GRADLE_OPTS" | 229 | xargs -n1 | 230 | sed ' s~[^-[:alnum:]+,./:=@_]~\\&~g; ' | 231 | tr '\n' ' ' 232 | )" '"$@"' 233 | 234 | exec "$JAVACMD" "$@" 235 | -------------------------------------------------------------------------------- /src/main/groovy/fuzzycsv/nav/Navigator.groovy: -------------------------------------------------------------------------------- 1 | package fuzzycsv.nav 2 | 3 | import fuzzycsv.FuzzyCSV 4 | import fuzzycsv.FuzzyCSVTable 5 | import fuzzycsv.Record 6 | import groovy.transform.CompileStatic 7 | 8 | @CompileStatic 9 | class Navigator { 10 | private static final Navigator START = new Navigator(0, 0) 11 | private int col 12 | private int row 13 | private FuzzyCSVTable table 14 | 15 | 16 | Navigator(int col, int row) { 17 | this(col, row, null) 18 | } 19 | 20 | Navigator(int col, int row, FuzzyCSVTable table) { 21 | this.col = col 22 | this.row = row 23 | this.table = table 24 | } 25 | 26 | static Navigator start() { 27 | return START 28 | } 29 | 30 | static Navigator atTopLeft(FuzzyCSVTable t) { 31 | return new Navigator(0, 0, t) 32 | } 33 | 34 | static Navigator atTopRight(FuzzyCSVTable t) { 35 | return new Navigator(t.header.size() - 1, 0, t) 36 | } 37 | 38 | 39 | static Navigator atBottomLeft(FuzzyCSVTable t) { 40 | return new Navigator(0, t.csv.size() - 1, t) 41 | } 42 | 43 | static Navigator atBottomRight(FuzzyCSVTable t) { 44 | return new Navigator(t.header.size() - 1, t.csv.size() - 1, t) 45 | } 46 | 47 | int getCol() { 48 | return col 49 | } 50 | 51 | int getRow() { 52 | return row 53 | } 54 | 55 | Navigator row(int newRow) { 56 | def navigator = copy() 57 | navigator.@row = newRow 58 | return navigator 59 | } 60 | 61 | Navigator col(int newCol) { 62 | def navigator = copy() 63 | navigator.@col = newCol 64 | return navigator 65 | } 66 | 67 | Navigator table(FuzzyCSVTable t) { 68 | def navigator = copy() 69 | navigator.@table = t 70 | return navigator 71 | } 72 | 73 | FuzzyCSVTable getTable() { 74 | return table 75 | } 76 | 77 | 78 | Navigator up(int steps = 1) { 79 | return new Navigator(col, row - steps, table) 80 | } 81 | 82 | Navigator down(int steps = 1) { 83 | return new Navigator(col, row + steps, table) 84 | } 85 | 86 | Navigator left(int steps = 1) { 87 | return new Navigator(col - steps, row, table) 88 | } 89 | 90 | Navigator right(int steps = 1) { 91 | return new Navigator(col + steps, row, table) 92 | } 93 | 94 | Navigator toTopLeft(FuzzyCSVTable t = table) { 95 | return atTopLeft(t) 96 | } 97 | 98 | Navigator toToRight(FuzzyCSVTable t = table) { 99 | return atTopRight(t) 100 | } 101 | 102 | Navigator toBottomLeft(FuzzyCSVTable t = table) { 103 | return atBottomLeft(t) 104 | } 105 | 106 | Navigator toBottomRight(FuzzyCSVTable t = table) { 107 | return atBottomRight(t) 108 | } 109 | 110 | Navigator to(String column, FuzzyCSVTable t = table) { 111 | def idx = t.header.indexOf(column) 112 | if (idx == -1) throw new IllegalArgumentException("column[$column] not found") 113 | return col(idx) 114 | } 115 | 116 | Navigator deleteRow(FuzzyCSVTable t = table) { 117 | t.csv.remove(row) 118 | return copy().fixLocation(t) 119 | } 120 | 121 | Navigator deleteCol(FuzzyCSVTable t = table) { 122 | def r = t.deleteColumns(col) 123 | return copy().fixLocation(r) 124 | } 125 | 126 | Navigator copy() { 127 | new Navigator(col, row, table) 128 | } 129 | 130 | Navigator mark(FuzzyCSVTable t = table) { 131 | set('[' + get(t) + ']', t) 132 | return this 133 | } 134 | 135 | Navigator mark(String i, FuzzyCSVTable t = table) { 136 | set(i + get(t), t) 137 | return this 138 | } 139 | 140 | 141 | def get(FuzzyCSVTable t = table) { 142 | return t.get(this) 143 | } 144 | 145 | Navigator set(obj, FuzzyCSVTable t = table) { 146 | t.set(col, row, obj) 147 | return this 148 | } 149 | 150 | Navigator clear(FuzzyCSVTable t = table) { 151 | set(null, t) 152 | return this 153 | } 154 | 155 | boolean canGoLeft() { 156 | return col > 0 157 | } 158 | 159 | boolean canGoDown(FuzzyCSVTable t = table) { 160 | return row < t.size() 161 | } 162 | 163 | boolean canGoUp() { 164 | return row > 0 165 | } 166 | 167 | boolean canGoRight(FuzzyCSVTable t = table) { 168 | def list = t.csv[row] 169 | return list != null && col < list.size() - 1 170 | } 171 | 172 | Navigator fixLocation(FuzzyCSVTable t = table) { 173 | def newCol = fixRange(col, t.header) 174 | def newRow = fixRange(row, t.csv) 175 | return new Navigator(newCol, newRow, t) 176 | } 177 | 178 | private static int fixRange(int oldValue, List records) { 179 | int rSize 180 | if (oldValue < 0) { 181 | return 0 182 | } else if (oldValue >= (rSize = records.size())) { 183 | return rSize - 1 184 | } else { 185 | return oldValue 186 | } 187 | 188 | } 189 | 190 | boolean inBounds(FuzzyCSVTable t = table) { 191 | return fixRange(row, t.csv) == row && 192 | fixRange(col, t.header) == col 193 | } 194 | 195 | NavIterator upIter(FuzzyCSVTable pTable = table) { 196 | def hasNextFn = { FuzzyCSVTable t, Navigator n -> n.canGoUp() } 197 | def navFn = { Navigator n -> n.up() } 198 | return NavIterator.from(this, pTable).withStopper(hasNextFn).withStepper(navFn) 199 | } 200 | 201 | NavIterator downIter(FuzzyCSVTable pTable = table) { 202 | def hasNextFn = { FuzzyCSVTable t, Navigator n -> n.canGoDown(t) } 203 | def navFn = { Navigator n -> n.down() } 204 | return NavIterator.from(this, pTable).withStopper(hasNextFn).withStepper(navFn) 205 | } 206 | 207 | NavIterator rightIter(FuzzyCSVTable pTable = table) { 208 | def hasNextFn = { FuzzyCSVTable t, Navigator n -> n.canGoRight(t) } 209 | def navFn = { Navigator n -> n.right() } 210 | return NavIterator.from(this, pTable).withStopper(hasNextFn).withStepper(navFn) 211 | } 212 | 213 | NavIterator leftIter(FuzzyCSVTable pTable = table) { 214 | def hasNextFn = { FuzzyCSVTable t, Navigator n -> n.canGoLeft() } 215 | def navFn = { Navigator n -> n.left() } 216 | return NavIterator.from(this, pTable).withStopper(hasNextFn).withStepper(navFn) 217 | } 218 | 219 | NavIterator allBoundedIter(int colBound, int rowBound, FuzzyCSVTable pTable = table) { 220 | 221 | def hasNextFn = { FuzzyCSVTable t, Navigator n -> 222 | (n.canGoRight(t) || n.canGoDown(t)) && 223 | (n.col < colBound || n.row < rowBound) 224 | } 225 | 226 | def navFn = { Navigator n -> 227 | if (n.canGoRight() && n.col < colBound) 228 | n.right() 229 | else { 230 | return n.down().col(col) 231 | } 232 | } 233 | 234 | return NavIterator.from(this, pTable).withStopper(hasNextFn).withStepper(navFn) 235 | } 236 | 237 | NavIterator allIter(FuzzyCSVTable pTable = table) { 238 | 239 | def hasNextFn = { FuzzyCSVTable t, Navigator n -> 240 | n.canGoRight(t) || n.canGoDown(t) 241 | } 242 | 243 | def navFn = { Navigator n -> 244 | if (n.canGoRight()) 245 | n.right() 246 | else { 247 | return n.down().col(col) 248 | } 249 | } 250 | return NavIterator.from(this, pTable).withStopper(hasNextFn).withStepper(navFn) 251 | } 252 | 253 | boolean sameCoords(Navigator other) { 254 | return other.col == this.col && other.row == this.row 255 | } 256 | 257 | 258 | MutableNav toMutableNav() { 259 | return new MutableNav(this) 260 | } 261 | 262 | @Override 263 | String toString() { 264 | return "Navigator{col=$col, row=$row}" 265 | } 266 | 267 | Navigator addAbove(FuzzyCSVTable t = table) { 268 | addAbove(t, FuzzyCSV.listOfSameSize(t.header)) 269 | } 270 | 271 | Navigator addAbove(FuzzyCSVTable t = table, List list) { 272 | t.addRows(row, list) 273 | return down() 274 | } 275 | 276 | Navigator addBelow(FuzzyCSVTable t = table) { 277 | addBelow(t, FuzzyCSV.listOfSameSize(t.header)) 278 | } 279 | 280 | Navigator addBelow(FuzzyCSVTable t = table, List list) { 281 | t.addRows(row + 1, list) 282 | return this 283 | } 284 | 285 | Record row() { 286 | return table.row(row) 287 | } 288 | 289 | 290 | // 291 | 292 | /** 293 | * @deprecated use {@link #get()} instead 294 | */ 295 | @Deprecated 296 | Object value() { 297 | return get() 298 | } 299 | 300 | 301 | /** 302 | * @deprecated use {@link #set(Object)} instead 303 | */ 304 | @Deprecated 305 | Navigator value(Object obj,FuzzyCSVTable t=this.table) { 306 | set(obj,t) 307 | return this 308 | } 309 | // 310 | 311 | } 312 | 313 | -------------------------------------------------------------------------------- /src/main/groovy/fuzzycsv/rdbms/FuzzyCSVDbExporter.groovy: -------------------------------------------------------------------------------- 1 | package fuzzycsv.rdbms 2 | 3 | import fuzzycsv.FuzzyCSVTable 4 | import fuzzycsv.nav.Navigator 5 | import fuzzycsv.rdbms.stmt.DefaultSqlRenderer 6 | import fuzzycsv.rdbms.stmt.DumbH2Renderer 7 | import fuzzycsv.rdbms.stmt.MySqlRenderer 8 | import fuzzycsv.rdbms.stmt.SqlDialect 9 | import fuzzycsv.rdbms.stmt.SqlRenderer 10 | import groovy.sql.Sql 11 | import groovy.transform.CompileStatic 12 | import groovy.transform.ToString 13 | import org.slf4j.Logger 14 | import org.slf4j.LoggerFactory 15 | 16 | import java.sql.Connection 17 | import java.util.concurrent.Callable 18 | 19 | import static fuzzycsv.rdbms.FuzzyCsvDbInserter.inTicks 20 | 21 | class FuzzyCSVDbExporter { 22 | 23 | private static Logger log = LoggerFactory.getLogger(FuzzyCSVDbExporter) 24 | 25 | Connection connection 26 | int defaultDecimals = 6 27 | 28 | SqlRenderer sqlRenderer 29 | ExportParams exportParams 30 | 31 | FuzzyCSVDbExporter(ExportParams params) { 32 | this.exportParams = params 33 | mayBeSetRenderer() 34 | } 35 | 36 | FuzzyCSVDbExporter(Connection c, ExportParams params) { 37 | this(params) 38 | this.connection = c 39 | } 40 | 41 | 42 | ExportResult dbExport(FuzzyCSVTable table) { 43 | assert table.name() != null 44 | 45 | ExportResult r = new ExportResult(createdTable: false) 46 | r.exportedData = table 47 | 48 | if (exportParams.exportFlags.contains(DbExportFlags.CREATE)) { 49 | createTable(table) 50 | r.createdTable = true 51 | } 52 | 53 | if (!r.createdTable && exportParams.exportFlags.contains(DbExportFlags.CREATE_IF_NOT_EXISTS)) { 54 | r.createdTable = createTableIfNotExists(table) 55 | } 56 | 57 | if (exportParams.exportFlags.contains(DbExportFlags.INSERT)) { 58 | def data = insertData(table) 59 | r.primaryKeys = data 60 | } 61 | 62 | return r 63 | 64 | } 65 | 66 | void mayBeSetRenderer() { 67 | sqlRenderer = resolveSqlRenderer(exportParams) 68 | } 69 | 70 | FuzzyCSVTable insertData(FuzzyCSVTable table) { 71 | 72 | def idList = doInsertData(table, exportParams.pageSize) 73 | 74 | return toPks(idList) 75 | 76 | } 77 | 78 | FuzzyCSVTable toPks(List> lists) { 79 | if (!lists || !lists[0]) { 80 | return FuzzyCSVTable.withHeader('pk') 81 | } 82 | 83 | def first = lists.first() 84 | 85 | def headers = (0..first.size() - 1).collect { "pk_$it".toString() } 86 | 87 | return FuzzyCSVTable.tbl([headers, *lists]) 88 | } 89 | 90 | 91 | boolean createTableIfNotExists(FuzzyCSVTable table) { 92 | def exists = DDLUtils.tableExists(connection, table.name()) 93 | if (!exists) { 94 | createTable(table) 95 | return true 96 | } 97 | return false 98 | } 99 | 100 | void createTable(FuzzyCSVTable table) { 101 | 102 | def ddl = createDDL(table) 103 | 104 | log.trace("creating table [$ddl]") 105 | sql().execute(ddl) 106 | } 107 | 108 | 109 | private Sql gSql 110 | 111 | private Sql sql() { 112 | assert connection != null 113 | if (gSql == null) gSql = new Sql(connection) 114 | return gSql 115 | } 116 | 117 | 118 | String createDDL(FuzzyCSVTable table) { 119 | def name = table.tableName 120 | assert name != null, "tables should contain name" 121 | 122 | def columns = createColumns(table) 123 | 124 | return sqlRenderer.createTable(name, columns) 125 | 126 | 127 | } 128 | 129 | List createColumns(FuzzyCSVTable table) { 130 | def header = table.header 131 | 132 | def start = Navigator.atTopLeft(table) 133 | 134 | def columns = header.collect { name -> 135 | def firstValue = start.to(name).downIter().skip() 136 | .find { it.get() != null }?.get() 137 | 138 | if (exportParams.exportFlags.contains(DbExportFlags.USE_DECIMAL_FOR_INTS) && firstValue instanceof Number) 139 | firstValue = firstValue as BigDecimal 140 | 141 | def column = resolveType(name, firstValue) 142 | 143 | if (exportParams.primaryKeys?.contains(name)) 144 | column.isPrimaryKey = true 145 | 146 | if (exportParams.autoIncrement?.contains(name)) 147 | column.autoIncrement = true 148 | 149 | return column 150 | } 151 | 152 | return columns 153 | } 154 | 155 | 156 | def restructureTable(FuzzyCSVTable table) { 157 | def tableColumns = createColumns(table) 158 | def structureSync = new DbColumnSync(columns: tableColumns, 159 | gSql: sql(), tableName: table.tableName, table: table, sqlRenderer: sqlRenderer) 160 | 161 | structureSync.sync() 162 | 163 | 164 | } 165 | 166 | 167 | List> doInsertData(FuzzyCSVTable table, int pageSize) { 168 | 169 | def inserts = FuzzyCsvDbInserter.generateInserts(sqlRenderer, pageSize, table, table.tableName) 170 | 171 | def rt = [] 172 | for (Map.Entry> q in inserts) { 173 | doWithRestructure(table) { 174 | logQuery(q) 175 | def insert = sql().executeInsert(q.key, q.value) 176 | rt.addAll(insert) 177 | } 178 | } 179 | 180 | 181 | return rt 182 | } 183 | 184 | private static logQuery(Map.Entry> queryAndParams) { 185 | log.trace("executing [$queryAndParams.key] params $queryAndParams.value") 186 | } 187 | 188 | @CompileStatic 189 | def updateData(FuzzyCSVTable table, String... identifiers) { 190 | def queries = FuzzyCsvDbInserter.generateUpdate(sqlRenderer, table, table.name(), identifiers) 191 | 192 | for (q in queries) { 193 | doWithRestructure(table) { 194 | logQuery(q) 195 | sql().executeUpdate(q.key, q.value) 196 | } 197 | } 198 | } 199 | 200 | @CompileStatic 201 | private T doWithRestructure(FuzzyCSVTable table, Callable operation) { 202 | 203 | try { 204 | return operation.call() 205 | } catch (x) { 206 | if (!exportParams.exportFlags.contains(DbExportFlags.RESTRUCTURE)) throw x 207 | 208 | try { 209 | log.warn("error while exporting [${table.name()}] trying to restructure: $x") 210 | 211 | restructureTable(table) 212 | return operation.call() 213 | 214 | } catch (Exception x2) { 215 | x.addSuppressed(x2) 216 | throw x2 217 | } 218 | } 219 | 220 | } 221 | 222 | 223 | Column resolveType(String name, String data) { 224 | new Column(type: 'varchar', name: name, size: Math.max(data.size(), 255)) 225 | } 226 | 227 | 228 | Column resolveType(String name, Integer data) { 229 | new Column(type: 'bigint', name: name, size: 11) 230 | } 231 | 232 | Column resolveType(String name, BigInteger data) { 233 | new Column(type: 'bigint', name: name, size: 11) 234 | } 235 | 236 | Column resolveType(String name, Long data) { 237 | new Column(type: 'bigint', name: name, size: 11) 238 | } 239 | 240 | Column resolveType(String name, BigDecimal data) { 241 | def decimals = Math.max(data.scale(), defaultDecimals) 242 | def wholeNumbers = data.precision() - data.scale() 243 | 244 | 245 | def scale = bigDecimalScale(wholeNumbers, decimals) 246 | 247 | new Column(type: 'decimal', name: name, size: scale.precision, decimals: scale.scale) 248 | } 249 | 250 | Column resolveType(String name, Number data) { 251 | resolveType(name, data as BigDecimal) 252 | } 253 | 254 | Column resolveType(String name, Boolean data) { 255 | new Column(type: 'boolean', name: name) 256 | } 257 | 258 | Column resolveType(String name, byte[] data) { 259 | new Column(type: 'boolean', name: name) 260 | } 261 | 262 | Column resolveType(String name, Object data) { 263 | new Column(type: 'varchar', name: name, size: 255) 264 | } 265 | 266 | static Map bigDecimalScale(int wholeNumbers, int decimals) { 267 | def precision = wholeNumbers + decimals 268 | [precision: precision, scale: decimals] 269 | } 270 | 271 | @CompileStatic 272 | SqlRenderer resolveSqlRenderer(ExportParams params) { 273 | assert params.dialect || params.sqlRenderer, "a sql dialect or sql renderer should be set " 274 | 275 | if (params.sqlRenderer) return params.sqlRenderer 276 | 277 | switch (params.dialect) { 278 | case SqlDialect.DEFAULT: return DefaultSqlRenderer.instance 279 | case SqlDialect.MYSQL: return MySqlRenderer.instance 280 | case SqlDialect.H2: return DumbH2Renderer.instance 281 | default: throw new UnsupportedOperationException("dialect $params.dialect not yet supported") 282 | } 283 | 284 | } 285 | 286 | @ToString(includePackage = false) 287 | static class Column { 288 | String name 289 | String type 290 | int size 291 | int decimals 292 | boolean isPrimaryKey 293 | boolean autoIncrement 294 | 295 | @Override 296 | String toString() { 297 | def primaryKeyStr = isPrimaryKey ? 'primary key' : '' 298 | 299 | if (autoIncrement) { 300 | primaryKeyStr = "$primaryKeyStr AUTO_INCREMENT" 301 | } 302 | 303 | if (decimals > 0) 304 | return "${inTicks(name)} $type($size, $decimals) ${primaryKeyStr}" 305 | 306 | if (size > 0) 307 | return "${inTicks(name)} $type($size) ${primaryKeyStr}" 308 | 309 | return "${inTicks(name)} $type ${primaryKeyStr}" 310 | 311 | 312 | } 313 | 314 | String sqlString() { 315 | return toString() 316 | } 317 | } 318 | 319 | static class ExportResult { 320 | private boolean createdTable 321 | private FuzzyCSVTable primaryKeys 322 | private FuzzyCSVTable exportedData 323 | 324 | FuzzyCSVTable getExportedData() { 325 | primaryKeys.fullJoinOnIdx(exportedData) 326 | } 327 | } 328 | 329 | 330 | } 331 | -------------------------------------------------------------------------------- /src/main/groovy/fuzzycsv/Exporter.java: -------------------------------------------------------------------------------- 1 | package fuzzycsv; 2 | 3 | import com.opencsv.CSVWriterBuilder; 4 | import com.opencsv.ICSVWriter; 5 | import fuzzycsv.rdbms.DbExportFlags; 6 | import fuzzycsv.rdbms.ExportParams; 7 | import fuzzycsv.rdbms.FuzzyCSVDbExporter; 8 | import groovy.json.JsonOutput; 9 | import lombok.AccessLevel; 10 | import lombok.AllArgsConstructor; 11 | 12 | import javax.sql.DataSource; 13 | import java.io.BufferedWriter; 14 | import java.io.IOException; 15 | import java.io.Writer; 16 | import java.nio.charset.StandardCharsets; 17 | import java.nio.file.Files; 18 | import java.nio.file.Path; 19 | import java.nio.file.Paths; 20 | import java.sql.Connection; 21 | import java.sql.ResultSet; 22 | import java.sql.ResultSetMetaData; 23 | import java.sql.SQLException; 24 | import java.util.ArrayList; 25 | import java.util.List; 26 | import java.util.Objects; 27 | 28 | public class Exporter { 29 | private FuzzyCSVTable table; 30 | 31 | private Exporter(FuzzyCSVTable table) { 32 | this.table = table; 33 | } 34 | 35 | static Exporter create(FuzzyCSVTable table) { 36 | return new Exporter(table); 37 | } 38 | 39 | 40 | public Database toDb() { 41 | return Database.create().withTable(table); 42 | } 43 | 44 | public Csv toCsv() { 45 | return Csv.create().withTable(table); 46 | } 47 | 48 | public Json toJson() { 49 | return Json.create().withTable(table); 50 | } 51 | 52 | public static DbToWriter toWriterFromDb() { 53 | return DbToWriter.create(); 54 | } 55 | 56 | @lombok.With 57 | @lombok.AllArgsConstructor 58 | @lombok.NoArgsConstructor(access = AccessLevel.PRIVATE) 59 | public static class Json { 60 | 61 | 62 | private FuzzyCSVTable table; 63 | private boolean prettyPrint = false; 64 | private boolean asMaps = false; 65 | 66 | 67 | public static Json create() { 68 | return new Json(); 69 | } 70 | 71 | public Json write(String path) { 72 | return write(Paths.get(path)); 73 | } 74 | 75 | public Json write(Path path) { 76 | try (BufferedWriter writer = Files.newBufferedWriter(path, StandardCharsets.UTF_8)) { 77 | return write(writer); 78 | } catch (Exception e) { 79 | throw FuzzyCsvException.wrap(e); 80 | } 81 | } 82 | 83 | public Json write(Writer writer) { 84 | try { 85 | writer.write(jsonText()); 86 | } catch (IOException e) { 87 | throw FuzzyCsvException.wrap(e); 88 | } 89 | return this; 90 | } 91 | 92 | String jsonText() { 93 | String json = asMaps ? JsonOutput.toJson(table.to().maps().getResult()) : JsonOutput.toJson(table.getCsv()); 94 | if (prettyPrint) { 95 | return JsonOutput.prettyPrint(json); 96 | } 97 | 98 | return json; 99 | } 100 | 101 | } 102 | 103 | @lombok.With 104 | @lombok.AllArgsConstructor 105 | @lombok.NoArgsConstructor(access = AccessLevel.PUBLIC, staticName = "create") 106 | 107 | public static class Csv { 108 | 109 | private String delimiter = ","; 110 | private String quote = "\""; 111 | private String escape = "\\"; 112 | private String lineSeparator = "\n"; 113 | private boolean quoteAll = true; 114 | private FuzzyCSVTable table; 115 | 116 | public Csv write(String path) { 117 | return write(Paths.get(path)); 118 | } 119 | 120 | public Csv write(Path path) { 121 | try ( 122 | BufferedWriter writer = Files.newBufferedWriter(path, StandardCharsets.UTF_8)) { 123 | return write(writer); 124 | } catch (IOException e) { 125 | throw FuzzyCsvException.wrap(e); 126 | } 127 | } 128 | 129 | public Csv write(Writer writer) { 130 | SimpleCsvWriter simpleCsvWriter = createWriter(writer); 131 | simpleCsvWriter.writeRows(table.getCsv()); 132 | return this; 133 | } 134 | 135 | public SimpleCsvWriter createWriter(Writer writer) { 136 | ICSVWriter openCsvWriter = createOpenCsvWriter(writer); 137 | return SimpleCsvWriter.create(openCsvWriter,quoteAll); 138 | } 139 | 140 | private ICSVWriter createOpenCsvWriter(Writer writer) { 141 | return new CSVWriterBuilder(writer) 142 | .withSeparator(delimiter.charAt(0)) 143 | .withQuoteChar(quote.charAt(0)) 144 | .withEscapeChar(escape.charAt(0)) 145 | .withLineEnd(lineSeparator) 146 | .build(); 147 | 148 | } 149 | 150 | } 151 | 152 | 153 | @lombok.With 154 | @lombok.AllArgsConstructor 155 | @lombok.NoArgsConstructor(access = AccessLevel.PUBLIC, staticName = "create") 156 | public static class Database { 157 | 158 | private FuzzyCSVTable table; 159 | private Connection connection; 160 | private DataSource dataSource; 161 | private ExportParams exportParams = ExportParams.of(DbExportFlags.CREATE_IF_NOT_EXISTS, DbExportFlags.INSERT); 162 | private FuzzyCSVDbExporter.ExportResult exportResult; 163 | 164 | public FuzzyCSVDbExporter.ExportResult getExportResult() { 165 | return Objects.requireNonNull(exportResult, "export() must be called before getExportResult()"); 166 | } 167 | 168 | public Database export() { 169 | Connection exportConnection = exportConnection(); 170 | try { 171 | FuzzyCSVDbExporter exporter = new FuzzyCSVDbExporter(exportConnection, exportParams); 172 | final FuzzyCSVDbExporter.ExportResult exportResult = exporter.dbExport(table); 173 | return withExportResult(exportResult); 174 | } finally { 175 | if (!isUsingConnection()) FuzzyCSVUtils.closeQuietly(exportConnection); 176 | } 177 | 178 | } 179 | 180 | public Database update(String... identifiers) { 181 | Connection exportConnection = exportConnection(); 182 | try { 183 | FuzzyCSVDbExporter exporter = new FuzzyCSVDbExporter(exportConnection, exportParams); 184 | exporter.updateData(table, identifiers); 185 | return this; 186 | } finally { 187 | if (!isUsingConnection()) FuzzyCSVUtils.closeQuietly(exportConnection); 188 | } 189 | 190 | } 191 | 192 | private Connection exportConnection() { 193 | assertDatasourceAndConnectionNotBothSet(); 194 | if (isUsingConnection()) return connection; 195 | 196 | assert dataSource != null : "dataSource or connection must be set before exporting"; 197 | try { 198 | return dataSource.getConnection(); 199 | } catch (SQLException e) { 200 | throw FuzzyCsvException.wrap(e); 201 | } 202 | } 203 | 204 | private Boolean isUsingConnection() { 205 | return connection != null; 206 | } 207 | 208 | private void assertDatasourceAndConnectionNotBothSet() { 209 | if (dataSource != null && connection != null) { 210 | throw new IllegalStateException("dataSource and connection cannot both be set"); 211 | } 212 | } 213 | } 214 | 215 | @lombok.With 216 | @lombok.AllArgsConstructor 217 | @lombok.NoArgsConstructor(access = AccessLevel.PRIVATE, staticName = "create") 218 | public static class DbToWriter { 219 | 220 | private ResultSet resultSet; 221 | private Exporter.Csv csvExporter = Csv.create(); 222 | private boolean includeHeader = true; 223 | 224 | public int write(Writer writer) { 225 | try { 226 | SimpleCsvWriter csvWriter = csvExporter.createWriter(writer); 227 | if (includeHeader) { 228 | csvWriter.writeRow(getColumns()); 229 | } 230 | return writeResultSet(csvWriter, resultSet); 231 | } catch (SQLException e) { 232 | throw FuzzyCsvException.wrap(e); 233 | } 234 | 235 | } 236 | 237 | private int writeResultSet(SimpleCsvWriter csvWriter, ResultSet resultSet) throws SQLException { 238 | int columnCount = resultSet.getMetaData().getColumnCount(); 239 | int row = 0; 240 | while (resultSet.next()) { 241 | Object[] nextLine = new Object[columnCount]; 242 | for (int i = 0; i < columnCount; i++) { 243 | nextLine[i] = resultSet.getObject(i + 1); 244 | } 245 | csvWriter.writeRow(nextLine); 246 | row++; 247 | } 248 | return row; 249 | } 250 | 251 | 252 | private List getColumns() throws SQLException { 253 | ResultSetMetaData metaData = resultSet.getMetaData(); 254 | return getColumns(metaData); 255 | 256 | } 257 | 258 | 259 | static List getColumns(ResultSetMetaData metadata) throws SQLException { 260 | int columnCount = metadata.getColumnCount(); 261 | List nextLine = new ArrayList<>(columnCount); 262 | for (int i = 0; i < columnCount; i++) { 263 | nextLine.add(i, metadata.getColumnLabel(i + 1)); 264 | } 265 | return nextLine; 266 | } 267 | } 268 | 269 | @AllArgsConstructor(staticName = "create") 270 | public static class SimpleCsvWriter { 271 | private ICSVWriter csvWriter; 272 | private boolean quoteAll ; 273 | 274 | public SimpleCsvWriter writeRow(List row) { 275 | writeRow(FuzzyCSVUtils.listToStrArray(row)); 276 | return this; 277 | } 278 | 279 | public SimpleCsvWriter writeRow(Object... row) { 280 | writeRow(FuzzyCSVUtils.objArrayToSrArray(row)); 281 | return this; 282 | } 283 | 284 | private void writeRow(String[] stringArray) { 285 | csvWriter.writeNext(stringArray,quoteAll); 286 | } 287 | 288 | public SimpleCsvWriter writeRows(List> rows) { 289 | for (List row : rows) { 290 | writeRow(row); 291 | } 292 | return this; 293 | } 294 | 295 | 296 | } 297 | } 298 | -------------------------------------------------------------------------------- /License: -------------------------------------------------------------------------------- 1 | Apache License 2 | Version 2.0, January 2004 3 | http://www.apache.org/licenses/ 4 | 5 | TERMS AND CONDITIONS FOR USE, REPRODUCTION, AND DISTRIBUTION 6 | 7 | 1. Definitions. 8 | 9 | "License" shall mean the terms and conditions for use, reproduction, 10 | and distribution as defined by Sections 1 through 9 of this document. 11 | 12 | "Licensor" shall mean the copyright owner or entity authorized by 13 | the copyright owner that is granting the License. 14 | 15 | "Legal Entity" shall mean the union of the acting entity and all 16 | other entities that control, are controlled by, or are under common 17 | control with that entity. For the purposes of this definition, 18 | "control" means (i) the power, direct or indirect, to cause the 19 | direction or management of such entity, whether by contract or 20 | otherwise, or (ii) ownership of fifty percent (50%) or more of the 21 | outstanding shares, or (iii) beneficial ownership of such entity. 22 | 23 | "You" (or "Your") shall mean an individual or Legal Entity 24 | exercising permissions granted by this License. 25 | 26 | "Source" form shall mean the preferred form for making modifications, 27 | including but not limited to software source code, documentation 28 | source, and configuration files. 29 | 30 | "Object" form shall mean any form resulting from mechanical 31 | transformation or translation of a Source form, including but 32 | not limited to compiled object code, generated documentation, 33 | and conversions to other media types. 34 | 35 | "Work" shall mean the work of authorship, whether in Source or 36 | Object form, made available under the License, as indicated by a 37 | copyright notice that is included in or attached to the work 38 | (an example is provided in the Appendix below). 39 | 40 | "Derivative Works" shall mean any work, whether in Source or Object 41 | form, that is based on (or derived from) the Work and for which the 42 | editorial revisions, annotations, elaborations, or other modifications 43 | represent, as a whole, an original work of authorship. For the purposes 44 | of this License, Derivative Works shall not include works that remain 45 | separable from, or merely link (or bind by name) to the interfaces of, 46 | the Work and Derivative Works thereof. 47 | 48 | "Contribution" shall mean any work of authorship, including 49 | the original version of the Work and any modifications or additions 50 | to that Work or Derivative Works thereof, that is intentionally 51 | submitted to Licensor for inclusion in the Work by the copyright owner 52 | or by an individual or Legal Entity authorized to submit on behalf of 53 | the copyright owner. For the purposes of this definition, "submitted" 54 | means any form of electronic, verbal, or written communication sent 55 | to the Licensor or its representatives, including but not limited to 56 | communication on electronic mailing lists, source code control systems, 57 | and issue tracking systems that are managed by, or on behalf of, the 58 | Licensor for the purpose of discussing and improving the Work, but 59 | excluding communication that is conspicuously marked or otherwise 60 | designated in writing by the copyright owner as "Not a Contribution." 61 | 62 | "Contributor" shall mean Licensor and any individual or Legal Entity 63 | on behalf of whom a Contribution has been received by Licensor and 64 | subsequently incorporated within the Work. 65 | 66 | 2. Grant of Copyright License. Subject to the terms and conditions of 67 | this License, each Contributor hereby grants to You a perpetual, 68 | worldwide, non-exclusive, no-charge, royalty-free, irrevocable 69 | copyright license to reproduce, prepare Derivative Works of, 70 | publicly display, publicly perform, sublicense, and distribute the 71 | Work and such Derivative Works in Source or Object form. 72 | 73 | 3. Grant of Patent License. Subject to the terms and conditions of 74 | this License, each Contributor hereby grants to You a perpetual, 75 | worldwide, non-exclusive, no-charge, royalty-free, irrevocable 76 | (except as stated in this section) patent license to make, have made, 77 | use, offer to sell, sell, import, and otherwise transfer the Work, 78 | where such license applies only to those patent claims licensable 79 | by such Contributor that are necessarily infringed by their 80 | Contribution(s) alone or by combination of their Contribution(s) 81 | with the Work to which such Contribution(s) was submitted. If You 82 | institute patent litigation against any entity (including a 83 | cross-claim or counterclaim in a lawsuit) alleging that the Work 84 | or a Contribution incorporated within the Work constitutes direct 85 | or contributory patent infringement, then any patent licenses 86 | granted to You under this License for that Work shall terminate 87 | as of the date such litigation is filed. 88 | 89 | 4. Redistribution. You may reproduce and distribute copies of the 90 | Work or Derivative Works thereof in any medium, with or without 91 | modifications, and in Source or Object form, provided that You 92 | meet the following conditions: 93 | 94 | (a) You must give any other recipients of the Work or 95 | Derivative Works a copy of this License; and 96 | 97 | (b) You must cause any modified files to carry prominent notices 98 | stating that You changed the files; and 99 | 100 | (c) You must retain, in the Source form of any Derivative Works 101 | that You distribute, all copyright, patent, trademark, and 102 | attribution notices from the Source form of the Work, 103 | excluding those notices that do not pertain to any part of 104 | the Derivative Works; and 105 | 106 | (d) If the Work includes a "NOTICE" text file as part of its 107 | distribution, then any Derivative Works that You distribute must 108 | include a readable copy of the attribution notices contained 109 | within such NOTICE file, excluding those notices that do not 110 | pertain to any part of the Derivative Works, in at least one 111 | of the following places: within a NOTICE text file distributed 112 | as part of the Derivative Works; within the Source form or 113 | documentation, if provided along with the Derivative Works; or, 114 | within a display generated by the Derivative Works, if and 115 | wherever such third-party notices normally appear. The contents 116 | of the NOTICE file are for informational purposes only and 117 | do not modify the License. You may add Your own attribution 118 | notices within Derivative Works that You distribute, alongside 119 | or as an addendum to the NOTICE text from the Work, provided 120 | that such additional attribution notices cannot be construed 121 | as modifying the License. 122 | 123 | You may add Your own copyright statement to Your modifications and 124 | may provide additional or different license terms and conditions 125 | for use, reproduction, or distribution of Your modifications, or 126 | for any such Derivative Works as a whole, provided Your use, 127 | reproduction, and distribution of the Work otherwise complies with 128 | the conditions stated in this License. 129 | 130 | 5. Submission of Contributions. Unless You explicitly state otherwise, 131 | any Contribution intentionally submitted for inclusion in the Work 132 | by You to the Licensor shall be under the terms and conditions of 133 | this License, without any additional terms or conditions. 134 | Notwithstanding the above, nothing herein shall supersede or modify 135 | the terms of any separate license agreement you may have executed 136 | with Licensor regarding such Contributions. 137 | 138 | 6. Trademarks. This License does not grant permission to use the trade 139 | names, trademarks, service marks, or product names of the Licensor, 140 | except as required for reasonable and customary use in describing the 141 | origin of the Work and reproducing the content of the NOTICE file. 142 | 143 | 7. Disclaimer of Warranty. Unless required by applicable law or 144 | agreed to in writing, Licensor provides the Work (and each 145 | Contributor provides its Contributions) on an "AS IS" BASIS, 146 | WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or 147 | implied, including, without limitation, any warranties or conditions 148 | of TITLE, NON-INFRINGEMENT, MERCHANTABILITY, or FITNESS FOR A 149 | PARTICULAR PURPOSE. You are solely responsible for determining the 150 | appropriateness of using or redistributing the Work and assume any 151 | risks associated with Your exercise of permissions under this License. 152 | 153 | 8. Limitation of Liability. In no event and under no legal theory, 154 | whether in tort (including negligence), contract, or otherwise, 155 | unless required by applicable law (such as deliberate and grossly 156 | negligent acts) or agreed to in writing, shall any Contributor be 157 | liable to You for damages, including any direct, indirect, special, 158 | incidental, or consequential damages of any character arising as a 159 | result of this License or out of the use or inability to use the 160 | Work (including but not limited to damages for loss of goodwill, 161 | work stoppage, computer failure or malfunction, or any and all 162 | other commercial damages or losses), even if such Contributor 163 | has been advised of the possibility of such damages. 164 | 165 | 9. Accepting Warranty or Additional Liability. While redistributing 166 | the Work or Derivative Works thereof, You may choose to offer, 167 | and charge a fee for, acceptance of support, warranty, indemnity, 168 | or other liability obligations and/or rights consistent with this 169 | License. However, in accepting such obligations, You may act only 170 | on Your own behalf and on Your sole responsibility, not on behalf 171 | of any other Contributor, and only if You agree to indemnify, 172 | defend, and hold each Contributor harmless for any liability 173 | incurred by, or claims asserted against, such Contributor by reason 174 | of your accepting any such warranty or additional liability. 175 | 176 | END OF TERMS AND CONDITIONS 177 | 178 | APPENDIX: How to apply the Apache License to your work. 179 | 180 | To apply the Apache License to your work, attach the following 181 | boilerplate notice, with the fields enclosed by brackets "{}" 182 | replaced with your own identifying information. (Don't include 183 | the brackets!) The text should be enclosed in the appropriate 184 | comment syntax for the file format. We also recommend that a 185 | file or class name and description of purpose be included on the 186 | same "printed page" as the copyright notice for easier 187 | identification within third-party archives. 188 | 189 | Copyright {yyyy} {name of copyright owner} 190 | 191 | Licensed under the Apache License, Version 2.0 (the "License"); 192 | you may not use this file except in compliance with the License. 193 | You may obtain a copy of the License at 194 | 195 | http://www.apache.org/licenses/LICENSE-2.0 196 | 197 | Unless required by applicable law or agreed to in writing, software 198 | distributed under the License is distributed on an "AS IS" BASIS, 199 | WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 200 | See the License for the specific language governing permissions and 201 | limitations under the License. 202 | --------------------------------------------------------------------------------