diff --git a/.github/workflows/native.yaml b/.github/workflows/native.yaml index bcf39e6..88e74bf 100644 --- a/.github/workflows/native.yaml +++ b/.github/workflows/native.yaml @@ -13,7 +13,7 @@ jobs: - uses: actions/checkout@v4 - uses: graalvm/setup-graalvm@v1 with: - java-version: '21' + java-version: '23' distribution: 'graalvm' github-token: ${{ secrets.GITHUB_TOKEN }} diff --git a/.github/workflows/test.yaml b/.github/workflows/test.yaml index 4255201..f2c8afa 100644 --- a/.github/workflows/test.yaml +++ b/.github/workflows/test.yaml @@ -16,7 +16,7 @@ jobs: - uses: actions/checkout@v4 - uses: actions/setup-java@v4 with: - java-version: '21' + java-version: '23' distribution: 'temurin' - name: Setup Gradle diff --git a/README.md b/README.md index cfb88e7..a7c2e96 100644 --- a/README.md +++ b/README.md @@ -1,10 +1,47 @@ -# Netcdf CLI +# netcdf cli [![Test](https://github.com/stellarsunset/netcdf-cli/actions/workflows/test.yaml/badge.svg)](https://github.com/stellarsunset/netcdf-cli/actions/workflows/test.yaml) -Minimal CLI tool for working with [netcdf](https://github.com/stellarsunset/netcdf) supported data files. +Minimal [CLI](https://picocli.info) tool for working with [netcdf](https://github.com/stellarsunset/netcdf) supported +data files. -### Build +## usage -- Activate GraalVM with [SDKMAN](https://sdkman.io/) `sdk use java 21.0.2-graalce` before building -- Run `just make` to build the `netcdf` binary and copy it into the `./bin` directory of the project \ No newline at end of file +compile the cli yourself for whatever your target platform is via graal + +```bash +$ sdk use java 23.0.2-graalce + +# build the binary +$ just binary + +# test out the binary locally +$ just cli help +Usage:
[-v] [COMMAND] + -v, --version +Commands: + help Display help information about the specified command. + json Convert a Netcdf file to a stream of JSON records. + describe Describe the contents of a NetCDF file in terms of dimensions and + variables. + +# show help docs for subcommands +$ just cli help describe +Usage:
describe [-cds] +Describe the contents of a NetCDF file in terms of dimensions and variables. + the file to analyze + -c, --coordinate-variables + print information about all 'coordinate' variables + in a file, that is variables which vary along + greater than one dimension + -d, --dimension-variables + print information about all the 'dimension' + variables in the file, that is variables which + vary only along a single dimension + -s, --scalar-variables print information about all 'scalar' variables, that + is variables with only a single value in the file + these may contain coordinate system information, + file publication time, etc. +``` + +use the cli to generate json or explore the contents of various netcdf files \ No newline at end of file diff --git a/app/build.gradle.kts b/app/build.gradle.kts index 09c78b3..ed9e688 100644 --- a/app/build.gradle.kts +++ b/app/build.gradle.kts @@ -47,7 +47,7 @@ graalvmNative { imageName = "netcdf" javaLauncher = javaToolchains.launcherFor { - languageVersion = JavaLanguageVersion.of(21) + languageVersion = JavaLanguageVersion.of(23) } resources.autodetect() diff --git a/app/src/config/reflect-config.json b/app/src/config/reflect-config.json index ba445f1..1dbc384 100644 --- a/app/src/config/reflect-config.json +++ b/app/src/config/reflect-config.json @@ -1,24 +1,10 @@ [ { - "name": "[Ljava.lang.Object;" - }, - { - "name": "[Ljava.util.HashMap;" + "name": "[Ljava.lang.String;" }, { "name": "[Lorg.jdom2.Content;" }, - { - "name": "com.fasterxml.jackson.databind.ext.Java7SupportImpl", - "methods": [ - { - "name": "", - "parameterTypes": [ - - ] - } - ] - }, { "name": "com.google.protobuf.ExtensionRegistry", "methods": [ @@ -31,250 +17,44 @@ ] }, { - "allDeclaredClasses": true, - "queryAllDeclaredConstructors": true, + "name": "com.sun.org.apache.xerces.internal.jaxp.SAXParserFactoryImpl", "methods": [ { "name": "", "parameterTypes": [ - ] - }, - { - "name": "testRead", - "parameterTypes": [ - ] } - ], - "name": "com.stellarsunset.netcdf.cli.BufrTest", - "queryAllDeclaredMethods": true, - "allDeclaredFields": true, - "queryAllPublicMethods": true + ] }, { - "allDeclaredClasses": true, - "queryAllDeclaredConstructors": true, - "methods": [ - { - "name": "", - "parameterTypes": [ - - ] - }, - { - "name": "testRead", - "parameterTypes": [ - - ] - } - ], - "name": "com.stellarsunset.netcdf.cli.Grib1Test", - "queryAllDeclaredMethods": true, - "allDeclaredFields": true, - "queryAllPublicMethods": true + "name": "groovy.lang.Closure" }, { - "allDeclaredClasses": true, - "queryAllDeclaredConstructors": true, - "methods": [ - { - "name": "", - "parameterTypes": [ - - ] - }, - { - "name": "testRead", - "parameterTypes": [ - - ] - } - ], - "name": "com.stellarsunset.netcdf.cli.Grib2Test", - "queryAllDeclaredMethods": true, - "allDeclaredFields": true, - "queryAllPublicMethods": true + "name": "libcore.io.Memory" }, { - "name": "com.stellarsunset.netcdf.cli.Nc3FileWriter", - "allDeclaredClasses": true, + "name": "org.example.Netcdf", "queryAllDeclaredMethods": true, - "queryAllPublicMethods": true + "allDeclaredFields": true }, { - "allDeclaredClasses": true, - "queryAllDeclaredConstructors": true, - "methods": [ - { - "name": "", - "parameterTypes": [ - - ] - }, - { - "name": "setup", - "parameterTypes": [ - "java.nio.file.Path" - ] - }, - { - "name": "testRead", - "parameterTypes": [ - - ] - } - ], - "name": "com.stellarsunset.netcdf.cli.Nc3Test", + "name": "org.example.info.Info", "queryAllDeclaredMethods": true, - "allDeclaredFields": true, - "queryAllPublicMethods": true + "allDeclaredFields": true }, { - "allDeclaredClasses": true, - "queryAllDeclaredConstructors": true, - "methods": [ - { - "name": "", - "parameterTypes": [ - - ] - }, - { - "name": "testRead", - "parameterTypes": [ - - ] - } - ], - "name": "com.stellarsunset.netcdf.cli.ZarrTest", - "queryAllDeclaredMethods": true, - "allDeclaredFields": true, - "queryAllPublicMethods": true - }, - { - "allDeclaredClasses": true, - "queryAllDeclaredConstructors": true, - "methods": [ - { - "name": "", - "parameterTypes": [ - - ] - }, - { - "name": "testParseBinding", - "parameterTypes": [ - - ] - } - ], - "name": "com.stellarsunset.netcdf.cli.json.JsonTest", + "name": "org.example.json.Json", "queryAllDeclaredMethods": true, - "allDeclaredFields": true, - "queryAllPublicMethods": true + "allDeclaredFields": true }, { - "name": "com.sun.org.apache.xerces.internal.jaxp.SAXParserFactoryImpl", - "methods": [ - { - "name": "", - "parameterTypes": [ - - ] - } - ] - }, - { - "name": "java.lang.Enum" + "name": "org.robolectric.Robolectric" }, { - "name": "java.lang.Record", - "allDeclaredClasses": true, + "name": "picocli.CommandLine$HelpCommand", "queryAllDeclaredMethods": true, - "queryAllPublicMethods": true - }, - { - "name": "java.lang.Thread", - "fields": [ - { - "name": "threadLocalRandomProbe" - } - ] - }, - { - "name": "java.lang.invoke.MethodHandle" - }, - { - "name": "java.nio.Buffer", - "fields": [ - { - "name": "address" - } - ] - }, - { - "name": "java.security.SecureRandomParameters" - }, - { - "name": "java.util.HashSet" - }, - { - "name": "java.util.LinkedHashSet" - }, - { - "name": "java.util.concurrent.ArrayBlockingQueue" - }, - { - "name": "java.util.concurrent.atomic.AtomicBoolean", - "fields": [ - { - "name": "value" - } - ] - }, - { - "name": "java.util.concurrent.atomic.AtomicReference", - "fields": [ - { - "name": "value" - } - ] - }, - { - "name": "java.util.concurrent.atomic.Striped64", - "fields": [ - { - "name": "base" - }, - { - "name": "cellsBusy" - } - ] - }, - { - "name": "java.util.concurrent.locks.AbstractOwnableSynchronizer" - }, - { - "name": "java.util.concurrent.locks.AbstractQueuedSynchronizer" - }, - { - "name": "java.util.concurrent.locks.AbstractQueuedSynchronizer$ConditionObject" - }, - { - "name": "java.util.concurrent.locks.ReentrantLock" - }, - { - "name": "java.util.concurrent.locks.ReentrantLock$NonfairSync" - }, - { - "name": "java.util.concurrent.locks.ReentrantLock$Sync" - }, - { - "name": "libcore.io.Memory" - }, - { - "name": "org.robolectric.Robolectric" + "allDeclaredFields": true }, { "name": "sun.misc.Unsafe", @@ -283,13 +63,13 @@ { "name": "arrayBaseOffset", "parameterTypes": [ - "java.lang.Class" + ] }, { "name": "arrayIndexScale", "parameterTypes": [ - "java.lang.Class" + ] }, { @@ -303,9 +83,7 @@ { "name": "copyMemory", "parameterTypes": [ - "java.lang.Object", "long", - "java.lang.Object", "long", "long" ] @@ -313,7 +91,6 @@ { "name": "getBoolean", "parameterTypes": [ - "java.lang.Object", "long" ] }, @@ -326,21 +103,18 @@ { "name": "getByte", "parameterTypes": [ - "java.lang.Object", "long" ] }, { "name": "getDouble", "parameterTypes": [ - "java.lang.Object", "long" ] }, { "name": "getFloat", "parameterTypes": [ - "java.lang.Object", "long" ] }, @@ -353,7 +127,6 @@ { "name": "getInt", "parameterTypes": [ - "java.lang.Object", "long" ] }, @@ -366,27 +139,24 @@ { "name": "getLong", "parameterTypes": [ - "java.lang.Object", "long" ] }, { "name": "getObject", "parameterTypes": [ - "java.lang.Object", "long" ] }, { "name": "objectFieldOffset", "parameterTypes": [ - "java.lang.reflect.Field" + ] }, { "name": "putBoolean", "parameterTypes": [ - "java.lang.Object", "long", "boolean" ] @@ -401,7 +171,6 @@ { "name": "putByte", "parameterTypes": [ - "java.lang.Object", "long", "byte" ] @@ -409,7 +178,6 @@ { "name": "putDouble", "parameterTypes": [ - "java.lang.Object", "long", "double" ] @@ -417,7 +185,6 @@ { "name": "putFloat", "parameterTypes": [ - "java.lang.Object", "long", "float" ] @@ -432,7 +199,6 @@ { "name": "putInt", "parameterTypes": [ - "java.lang.Object", "long", "int" ] @@ -447,7 +213,6 @@ { "name": "putLong", "parameterTypes": [ - "java.lang.Object", "long", "long" ] @@ -455,48 +220,7 @@ { "name": "putObject", "parameterTypes": [ - "java.lang.Object", - "long", - "java.lang.Object" - ] - } - ] - }, - { - "name": "sun.security.provider.NativePRNG", - "methods": [ - { - "name": "", - "parameterTypes": [ - - ] - }, - { - "name": "", - "parameterTypes": [ - "java.security.SecureRandomParameters" - ] - } - ] - }, - { - "name": "sun.security.provider.SHA", - "methods": [ - { - "name": "", - "parameterTypes": [ - - ] - } - ] - }, - { - "name": "ucar.nc2.grib.collection.Grib1Iosp", - "methods": [ - { - "name": "", - "parameterTypes": [ - + "long" ] } ] @@ -534,17 +258,6 @@ } ] }, - { - "name": "ucar.nc2.iosp.bufr.BufrIosp2", - "methods": [ - { - "name": "", - "parameterTypes": [ - - ] - } - ] - }, { "name": "ucar.nc2.iosp.hdf4.H4iosp", "methods": [ @@ -556,28 +269,6 @@ } ] }, - { - "name": "ucar.nc2.iosp.zarr.ZArray$ZArrayDeserializer", - "methods": [ - { - "name": "", - "parameterTypes": [ - - ] - } - ] - }, - { - "name": "ucar.nc2.iosp.zarr.ZarrIosp", - "methods": [ - { - "name": "", - "parameterTypes": [ - - ] - } - ] - }, { "name": "ucar.nc2.stream.NcStreamIosp", "methods": [ diff --git a/app/src/config/resource-config.json b/app/src/config/resource-config.json index 3b47f0e..0f036b0 100644 --- a/app/src/config/resource-config.json +++ b/app/src/config/resource-config.json @@ -1,138 +1,137 @@ { - "resources": { - "includes": [ - { - "pattern": "\\QMETA-INF/services/java.lang.System$LoggerFinder\\E" - }, - { - "pattern": "\\QMETA-INF/services/java.nio.channels.spi.SelectorProvider\\E" - }, - { - "pattern": "\\QMETA-INF/services/javax.xml.parsers.SAXParserFactory\\E" - }, - { - "pattern": "\\QMETA-INF/services/org.junit.platform.engine.TestEngine\\E" - }, - { - "pattern": "\\QMETA-INF/services/org.junit.platform.launcher.LauncherDiscoveryListener\\E" - }, - { - "pattern": "\\QMETA-INF/services/org.junit.platform.launcher.LauncherSessionListener\\E" - }, - { - "pattern": "\\QMETA-INF/services/org.junit.platform.launcher.PostDiscoveryFilter\\E" - }, - { - "pattern": "\\QMETA-INF/services/org.junit.platform.launcher.TestExecutionListener\\E" - }, - { - "pattern": "\\QMETA-INF/services/org.slf4j.spi.SLF4JServiceProvider\\E" - }, - { - "pattern": "\\QMETA-INF/services/thredds.inventory.MControllerProvider\\E" - }, - { - "pattern": "\\QMETA-INF/services/thredds.inventory.MFileProvider\\E" - }, - { - "pattern": "\\QMETA-INF/services/ucar.nc2.iosp.IOServiceProvider\\E" - }, - { - "pattern": "\\QMETA-INF/services/ucar.unidata.io.spi.RandomAccessFileProvider\\E" - }, - { - "pattern": "\\Qjunit-platform.properties\\E" - }, - { - "pattern": "\\Qorg/joda/time/tz/data/America/New_York\\E" - }, - { - "pattern": "\\Qorg/joda/time/tz/data/ZoneInfoMap\\E" - }, - { - "pattern": "\\Qorg/slf4j/impl/StaticLoggerBinder.class\\E" - }, - { - "pattern": "\\Qresources/bufrTables/local/tablelookup.csv\\E" - }, - { - "pattern": "\\Qresources/bufrTables/wmo/BUFRCREX_32_0_0_CodeFlag_en.xml\\E" - }, - { - "pattern": "\\Qresources/bufrTables/wmo/BUFRCREX_32_0_0_TableB_en.xml\\E" - }, - { - "pattern": "\\Qresources/bufrTables/wmo/BUFR_32_0_0_TableA_en.xml\\E" - }, - { - "pattern": "\\Qresources/bufrTables/wmo/BUFR_32_0_0_TableD_en.xml\\E" - }, - { - "pattern": "\\Qresources/grib1/dss/WMO_GRIB1.xml\\E" - }, - { - "pattern": "\\Qresources/grib1/dss/lookupTables.txt\\E" - }, - { - "pattern": "\\Qresources/grib1/ecmwf/lookupTables.txt\\E" - }, - { - "pattern": "\\Qresources/grib1/ecmwfEcCodes/lookupTables.txt\\E" - }, - { - "pattern": "\\Qresources/grib1/lookupTables.txt\\E" - }, - { - "pattern": "\\Qresources/grib1/ncep/ncepGrib1-2.xml\\E" - }, - { - "pattern": "\\Qresources/grib1/ncep/ncepTableA.xml\\E" - }, - { - "pattern": "\\Qresources/grib1/ncl/lookupTables.txt\\E" - }, - { - "pattern": "\\Qresources/grib1/wmoTable3.xml\\E" - }, - { - "pattern": "\\Qresources/grib1/wrf/lookupTables.txt\\E" - }, - { - "pattern": "\\Qresources/grib2/ecmwf/localConcepts/ecmf/cfName.def\\E" - }, - { - "pattern": "\\Qresources/grib2/ecmwf/localConcepts/ecmf/cfVarName.def\\E" - }, - { - "pattern": "\\Qresources/grib2/ecmwf/localConcepts/ecmf/name.def\\E" - }, - { - "pattern": "\\Qresources/grib2/ecmwf/localConcepts/ecmf/paramId.def\\E" - }, - { - "pattern": "\\Qresources/grib2/ecmwf/localConcepts/ecmf/shortName.def\\E" - }, - { - "pattern": "\\Qresources/grib2/ecmwf/localConcepts/ecmf/units.def\\E" - }, - { - "pattern": "\\Qresources/grib2/standardTableMap.txt\\E" - }, - { - "pattern": "\\Qresources/grib2/wmo/GRIB2_22_0_0_CodeFlag_exp_en.xml\\E" - }, - { - "pattern": "\\Qresources/wmo/Common_C11_20181107_en.xml\\E" - }, - { - "pattern": "\\Qresources/wmo/Common_C12_20181107_en.xml\\E" - }, - { - "pattern": "\\Qresources/wmo/Common_C13_20181107_en.xml\\E" - } - ] - }, - "bundles": [ - + "resources": { + "includes": [ + { + "pattern": "\\QMETA-INF/services/java.lang.System$LoggerFinder\\E" + }, + { + "pattern": "\\QMETA-INF/services/java.nio.channels.spi.SelectorProvider\\E" + }, + { + "pattern": "\\QMETA-INF/services/javax.xml.parsers.SAXParserFactory\\E" + }, + { + "pattern": "\\QMETA-INF/services/org.junit.platform.engine.TestEngine\\E" + }, + { + "pattern": "\\QMETA-INF/services/org.junit.platform.launcher.LauncherDiscoveryListener\\E" + }, + { + "pattern": "\\QMETA-INF/services/org.junit.platform.launcher.LauncherSessionListener\\E" + }, + { + "pattern": "\\QMETA-INF/services/org.junit.platform.launcher.PostDiscoveryFilter\\E" + }, + { + "pattern": "\\QMETA-INF/services/org.junit.platform.launcher.TestExecutionListener\\E" + }, + { + "pattern": "\\QMETA-INF/services/org.slf4j.spi.SLF4JServiceProvider\\E" + }, + { + "pattern": "\\QMETA-INF/services/thredds.inventory.MControllerProvider\\E" + }, + { + "pattern": "\\QMETA-INF/services/thredds.inventory.MFileProvider\\E" + }, + { + "pattern": "\\QMETA-INF/services/ucar.nc2.iosp.IOServiceProvider\\E" + }, + { + "pattern": "\\QMETA-INF/services/ucar.unidata.io.spi.RandomAccessFileProvider\\E" + }, + { + "pattern": "\\Qjunit-platform.properties\\E" + }, + { + "pattern": "\\Qorg/joda/time/tz/data/America/New_York\\E" + }, + { + "pattern": "\\Qorg/joda/time/tz/data/ZoneInfoMap\\E" + }, + { + "pattern": "\\Qorg/slf4j/impl/StaticLoggerBinder.class\\E" + }, + { + "pattern": "\\Qresources/bufrTables/local/tablelookup.csv\\E" + }, + { + "pattern": "\\Qresources/bufrTables/wmo/BUFRCREX_32_0_0_CodeFlag_en.xml\\E" + }, + { + "pattern": "\\Qresources/bufrTables/wmo/BUFRCREX_32_0_0_TableB_en.xml\\E" + }, + { + "pattern": "\\Qresources/bufrTables/wmo/BUFR_32_0_0_TableA_en.xml\\E" + }, + { + "pattern": "\\Qresources/bufrTables/wmo/BUFR_32_0_0_TableD_en.xml\\E" + }, + { + "pattern": "\\Qresources/grib1/dss/WMO_GRIB1.xml\\E" + }, + { + "pattern": "\\Qresources/grib1/dss/lookupTables.txt\\E" + }, + { + "pattern": "\\Qresources/grib1/ecmwf/lookupTables.txt\\E" + }, + { + "pattern": "\\Qresources/grib1/ecmwfEcCodes/lookupTables.txt\\E" + }, + { + "pattern": "\\Qresources/grib1/lookupTables.txt\\E" + }, + { + "pattern": "\\Qresources/grib1/ncep/ncepGrib1-2.xml\\E" + }, + { + "pattern": "\\Qresources/grib1/ncep/ncepTableA.xml\\E" + }, + { + "pattern": "\\Qresources/grib1/ncl/lookupTables.txt\\E" + }, + { + "pattern": "\\Qresources/grib1/wmoTable3.xml\\E" + }, + { + "pattern": "\\Qresources/grib1/wrf/lookupTables.txt\\E" + }, + { + "pattern": "\\Qresources/grib2/ecmwf/localConcepts/ecmf/cfName.def\\E" + }, + { + "pattern": "\\Qresources/grib2/ecmwf/localConcepts/ecmf/cfVarName.def\\E" + }, + { + "pattern": "\\Qresources/grib2/ecmwf/localConcepts/ecmf/name.def\\E" + }, + { + "pattern": "\\Qresources/grib2/ecmwf/localConcepts/ecmf/paramId.def\\E" + }, + { + "pattern": "\\Qresources/grib2/ecmwf/localConcepts/ecmf/shortName.def\\E" + }, + { + "pattern": "\\Qresources/grib2/ecmwf/localConcepts/ecmf/units.def\\E" + }, + { + "pattern": "\\Qresources/grib2/standardTableMap.txt\\E" + }, + { + "pattern": "\\Qresources/grib2/wmo/GRIB2_22_0_0_CodeFlag_exp_en.xml\\E" + }, + { + "pattern": "\\Qresources/wmo/Common_C11_20181107_en.xml\\E" + }, + { + "pattern": "\\Qresources/wmo/Common_C12_20181107_en.xml\\E" + }, + { + "pattern": "\\Qresources/wmo/Common_C13_20181107_en.xml\\E" + } ] + }, + "bundles": [ + ] } \ No newline at end of file diff --git a/app/src/main/java/com/stellarsunset/netcdf/cli/json/BindingMaker.java b/app/src/main/java/com/stellarsunset/netcdf/cli/json/BindingMaker.java index aee8a1d..ce631ec 100644 --- a/app/src/main/java/com/stellarsunset/netcdf/cli/json/BindingMaker.java +++ b/app/src/main/java/com/stellarsunset/netcdf/cli/json/BindingMaker.java @@ -1,15 +1,12 @@ package com.stellarsunset.netcdf.cli.json; -import com.fasterxml.jackson.core.JsonGenerator; -import com.stellarsunset.netcdf.SchemaBinding; import com.stellarsunset.netcdf.cli.json.JsonBinding.AliasedVariable; -import com.stellarsunset.netcdf.field.FieldSetter; +import io.github.stellarsunset.netcdf.FieldBinding; +import io.github.stellarsunset.netcdf.SchemaBinding; import ucar.ma2.DataType; import ucar.nc2.NetcdfFile; import ucar.nc2.Variable; -import java.io.IOException; - final class BindingMaker { private BindingMaker() { @@ -18,9 +15,9 @@ private BindingMaker() { /** * Uses the target {@link NetcdfFile} to convert the incoming {@link JsonBinding} into a full {@link SchemaBinding}. */ - static SchemaBinding createBindingFor(NetcdfFile file, JsonBinding binding, JsonGenerator generator) { + static SchemaBinding createBindingFor(NetcdfFile file, JsonBinding binding, SafeGenerator generator) { - SchemaBinding.Builder builder = SchemaBinding.builder(); + SchemaBinding.Builder builder = SchemaBinding.builder(); for (AliasedVariable dimensionVariable : binding.dimensionVariables()) { @@ -48,60 +45,58 @@ static SchemaBinding createBindingFor(NetcdfFile file, JsonBindin } return builder - .recordInitializer(() -> { - try { - generator.writeStartObject(); - return generator; - } catch (IOException e) { - throw new RuntimeException(e); - } - }) - .recordFinalizer(g -> { - try { - g.writeEndObject(); - g.writeRaw(System.lineSeparator()); - } catch (IOException e) { - throw new RuntimeException(e); - } - }) + .recordInitializer(generator::writeStartObject) + .recordFinalizer(g -> g.writeEndObject().writeRaw(System.lineSeparator())) .build(); } - private static FieldSetter jsonSetter(String fieldName, DataType type) { + private static FieldBinding jsonSetter(String fieldName, DataType type) { return switch (type) { - case DOUBLE -> FieldSetter.doubles((s, d) -> { - s.writeNumberField(fieldName, d); - return s; - }); - case FLOAT -> FieldSetter.floats((s, f) -> { - s.writeNumberField(fieldName, f); - return s; - }); - case CHAR -> FieldSetter.characters((s, c) -> { - s.writeStringField(fieldName, Character.toString(c)); - return s; - }); - case BOOLEAN -> FieldSetter.booleans((s, b) -> { - s.writeBooleanField(fieldName, b); - return s; - }); - case ENUM4, UINT, INT -> FieldSetter.ints((s, i) -> { - s.writeNumberField(fieldName, i); - return s; - }); - case ENUM2, USHORT, SHORT -> FieldSetter.shorts((s, h) -> { - s.writeNumberField(fieldName, h); - return s; - }); - case ENUM1, UBYTE, BYTE -> FieldSetter.bytes((s, b) -> { - s.writeBinaryField(fieldName, new byte[]{b}); - return s; - }); - case ULONG, LONG -> FieldSetter.longs((s, l) -> { - s.writeNumberField(fieldName, l); - return s; - }); - case STRING, STRUCTURE, SEQUENCE, OPAQUE, OBJECT -> FieldSetter.noop(); + case DOUBLE -> doubles((s, d) -> s.writeDouble(fieldName, d)); + case FLOAT -> floats((s, f) -> s.writeFloat(fieldName, f)); + case CHAR -> chars((s, c) -> s.writeChar(fieldName, c)); + case BOOLEAN -> bools((s, b) -> s.writeBool(fieldName, b)); + case UINT -> ints((s, b) -> s.writeLong(fieldName, Integer.toUnsignedLong(b))); // no unsigned types + case ENUM4, INT -> ints((s, b) -> s.writeInt(fieldName, b)); + case USHORT -> shorts((s, b) -> s.writeInt(fieldName, Short.toUnsignedInt(b))); // no unsigned + case ENUM2, SHORT -> shorts((s, b) -> s.writeShort(fieldName, b)); + case UBYTE -> bytes((s, b) -> s.writeInt(fieldName, Short.toUnsignedInt(b))); + case ENUM1, BYTE -> bytes((s, b) -> s.writeShort(fieldName, (short) b)); // no byte type - upcast to short + case LONG -> longs((s, l) -> s.writeLong(fieldName, l)); + case ULONG, STRING, STRUCTURE, SEQUENCE, OPAQUE, OBJECT -> + throw new IllegalArgumentException(String.format("Unhandled Json binding for NetCDF primitive type: %s", type)); }; } + + private static FieldBinding doubles(FieldBinding.Double doubles) { + return doubles; + } + + private static FieldBinding floats(FieldBinding.Float floats) { + return floats; + } + + private static FieldBinding chars(FieldBinding.Char chars) { + return chars; + } + + private static FieldBinding bools(FieldBinding.Bool bools) { + return bools; + } + + private static FieldBinding ints(FieldBinding.Int ints) { + return ints; + } + + private static FieldBinding shorts(FieldBinding.Short shorts) { + return shorts; + } + + private static FieldBinding bytes(FieldBinding.Byte bytes) { + return bytes; + } + + private static FieldBinding longs(FieldBinding.Long longs) { + return longs; + } } diff --git a/app/src/main/java/com/stellarsunset/netcdf/cli/json/Json.java b/app/src/main/java/com/stellarsunset/netcdf/cli/json/Json.java index fa0afdb..0aeed44 100644 --- a/app/src/main/java/com/stellarsunset/netcdf/cli/json/Json.java +++ b/app/src/main/java/com/stellarsunset/netcdf/cli/json/Json.java @@ -2,8 +2,8 @@ import com.fasterxml.jackson.core.JsonFactory; import com.fasterxml.jackson.core.JsonGenerator; -import com.stellarsunset.netcdf.NetcdfRecordReader; -import com.stellarsunset.netcdf.SchemaBinding; +import io.github.stellarsunset.netcdf.NetcdfRecordReader; +import io.github.stellarsunset.netcdf.SchemaBinding; import picocli.CommandLine.Command; import picocli.CommandLine.Option; import picocli.CommandLine.Parameters; @@ -11,13 +11,16 @@ import ucar.nc2.NetcdfFiles; import java.io.File; -import java.io.OutputStreamWriter; -import java.io.Writer; import java.util.concurrent.Callable; @Command( name = "json", - description = "Convert a Netcdf file to a stream of JSON records." + description = """ + Convert a Netcdf file to a stream of JSON records. + + Extract records with subsets of the record's fields including dimension and coordinate variables as seen + in via 'netcdf describe'. + """ ) public final class Json implements Callable { @@ -44,9 +47,9 @@ public final class Json implements Callable { @Override public Integer call() throws Exception { try (NetcdfFile netcdfFile = NetcdfFiles.open(file.getAbsolutePath()); - JsonGenerator generator = FACTORY.createGenerator(System.out)) { + SafeGenerator generator = new SafeGenerator(FACTORY.createGenerator(System.out))) { - SchemaBinding binding = BindingMaker.createBindingFor( + SchemaBinding binding = BindingMaker.createBindingFor( netcdfFile, parseBinding(dimensionVariables, coordinateVariables), generator diff --git a/app/src/main/java/com/stellarsunset/netcdf/cli/json/SafeGenerator.java b/app/src/main/java/com/stellarsunset/netcdf/cli/json/SafeGenerator.java new file mode 100644 index 0000000..127c4c8 --- /dev/null +++ b/app/src/main/java/com/stellarsunset/netcdf/cli/json/SafeGenerator.java @@ -0,0 +1,109 @@ +package com.stellarsunset.netcdf.cli.json; + +import com.fasterxml.jackson.core.JsonGenerator; + +import java.io.IOException; + +record SafeGenerator(JsonGenerator delegate) implements AutoCloseable { + + SafeGenerator writeStartObject() { + try { + this.delegate.writeStartObject(); + return this; + } catch (IOException e) { + throw new UnableToWriteJsonFieldException("start_object", e); + } + } + + SafeGenerator writeDouble(String name, double value) { + try { + this.delegate.writeNumberField(name, value); + return this; + } catch (IOException e) { + throw new UnableToWriteJsonFieldException(name, e); + } + } + + SafeGenerator writeFloat(String name, float value) { + try { + this.delegate.writeNumberField(name, value); + return this; + } catch (IOException e) { + throw new UnableToWriteJsonFieldException(name, e); + } + } + + SafeGenerator writeChar(String name, char value) { + try { + this.delegate.writeStringField(name, Character.toString(value)); + return this; + } catch (IOException e) { + throw new UnableToWriteJsonFieldException(name, e); + } + } + + SafeGenerator writeBool(String name, boolean value) { + try { + this.delegate.writeBooleanField(name, value); + return this; + } catch (IOException e) { + throw new UnableToWriteJsonFieldException(name, e); + } + } + + SafeGenerator writeInt(String name, int value) { + try { + this.delegate.writeNumberField(name, value); + return this; + } catch (IOException e) { + throw new UnableToWriteJsonFieldException(name, e); + } + } + + SafeGenerator writeShort(String name, short value) { + try { + this.delegate.writeNumberField(name, value); + return this; + } catch (IOException e) { + throw new UnableToWriteJsonFieldException(name, e); + } + } + + SafeGenerator writeLong(String name, long value) { + try { + this.delegate.writeNumberField(name, value); + return this; + } catch (IOException e) { + throw new UnableToWriteJsonFieldException(name, e); + } + } + + SafeGenerator writeEndObject() { + try { + this.delegate.writeEndObject(); + return this; + } catch (IOException e) { + throw new UnableToWriteJsonFieldException("end_object", e); + } + } + + SafeGenerator writeRaw(String text) { + try { + this.delegate.writeRaw(text); + return this; + } catch (IOException e) { + throw new UnableToWriteJsonFieldException("raw_content", e); + } + } + + @Override + public void close() throws Exception { + this.delegate.close(); + } + + static final class UnableToWriteJsonFieldException extends RuntimeException { + public UnableToWriteJsonFieldException(String field, Throwable throwable) { + super(String.format("Unable to write Json content for field: %s", field), throwable); + } + } +} diff --git a/gradle/libs.versions.toml b/gradle/libs.versions.toml index 14f89b6..e7f07ff 100644 --- a/gradle/libs.versions.toml +++ b/gradle/libs.versions.toml @@ -9,7 +9,7 @@ junit-jupiter = "5.10.1" picocli = { module = "info.picocli:picocli", version.ref = "picocli" } picocli-gen = { module = "info.picocli:picocli-codegen", version.ref = "picocli" } -netcdf = { module = "io.github.stellarsunset:netcdf", version = "0.0.3" } +netcdf = { module = "io.github.stellarsunset:netcdf", version = "1.0.0" } # Runtime dependencies supporting specific file formats bufr = { module = "edu.ucar:bufr", version.ref = "netcdf" } grib = { module = "edu.ucar:grib", version.ref = "netcdf" } diff --git a/gradle/wrapper/gradle-wrapper.jar b/gradle/wrapper/gradle-wrapper.jar index e644113..a4b76b9 100644 Binary files a/gradle/wrapper/gradle-wrapper.jar and b/gradle/wrapper/gradle-wrapper.jar differ diff --git a/gradle/wrapper/gradle-wrapper.properties b/gradle/wrapper/gradle-wrapper.properties index b82aa23..37f853b 100644 --- a/gradle/wrapper/gradle-wrapper.properties +++ b/gradle/wrapper/gradle-wrapper.properties @@ -1,6 +1,6 @@ distributionBase=GRADLE_USER_HOME distributionPath=wrapper/dists -distributionUrl=https\://services.gradle.org/distributions/gradle-8.7-bin.zip +distributionUrl=https\://services.gradle.org/distributions/gradle-8.13-bin.zip networkTimeout=10000 validateDistributionUrl=true zipStoreBase=GRADLE_USER_HOME diff --git a/gradlew b/gradlew index 1aa94a4..f3b75f3 100755 --- a/gradlew +++ b/gradlew @@ -15,6 +15,8 @@ # See the License for the specific language governing permissions and # limitations under the License. # +# SPDX-License-Identifier: Apache-2.0 +# ############################################################################## # @@ -55,7 +57,7 @@ # Darwin, MinGW, and NonStop. # # (3) This script is generated from the Groovy template -# https://github.com/gradle/gradle/blob/HEAD/subprojects/plugins/src/main/resources/org/gradle/api/internal/plugins/unixStartScript.txt +# https://github.com/gradle/gradle/blob/HEAD/platforms/jvm/plugins-application/src/main/resources/org/gradle/api/internal/plugins/unixStartScript.txt # within the Gradle project. # # You can find Gradle at https://github.com/gradle/gradle/. @@ -84,7 +86,7 @@ done # shellcheck disable=SC2034 APP_BASE_NAME=${0##*/} # Discard cd standard output in case $CDPATH is set (https://github.com/gradle/gradle/issues/25036) -APP_HOME=$( cd "${APP_HOME:-./}" > /dev/null && pwd -P ) || exit +APP_HOME=$( cd -P "${APP_HOME:-./}" > /dev/null && printf '%s\n' "$PWD" ) || exit # Use the maximum available, or set MAX_FD != -1 to use that value. MAX_FD=maximum diff --git a/gradlew.bat b/gradlew.bat index 25da30d..9d21a21 100644 --- a/gradlew.bat +++ b/gradlew.bat @@ -13,6 +13,8 @@ @rem See the License for the specific language governing permissions and @rem limitations under the License. @rem +@rem SPDX-License-Identifier: Apache-2.0 +@rem @if "%DEBUG%"=="" @echo off @rem ########################################################################## diff --git a/justfile b/justfile index 70f47c0..f6bccf7 100644 --- a/justfile +++ b/justfile @@ -1,10 +1,21 @@ -# Re-generate the resource/reflection configs based on the unit tests -configure: +set positional-arguments + +default: + just --list + +# generate graal resource/reflection configs from unit tests, these require some additional configuration +@configure: ./gradlew -Pagent test - cp -r ./app/build/native/agent-output/test/{reflect-config.json,resource-config.json} ./app/src/config + cp -r ./app/build/native/agent-output/run/{reflect-config.json,resource-config.json} ./app/src/config -# Re-build the executable binary -make: configure +# run the unit tests of the project +@test: + ./gradlew test + +# build the binary +@binary: ./gradlew nativeCompile - mkdir -p ./bin - cp -rf ./app/build/native/nativeCompile/netcdf ./bin/ + +# invoke the cli binary with the provided arguments (assuming its been built) +@cli *args='': + ./app/build/native/nativeCompile/netcdf $@