-
Notifications
You must be signed in to change notification settings - Fork 126
Expand file tree
/
Copy pathSimpleIonCli.java
More file actions
159 lines (135 loc) · 6.86 KB
/
SimpleIonCli.java
File metadata and controls
159 lines (135 loc) · 6.86 KB
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
package com.amazon.tools.cli;
import com.amazon.ion.IonEncodingVersion;
import com.amazon.ion.IonReader;
import com.amazon.ion.IonWriter;
import com.amazon.ion.system.IonReaderBuilder;
import picocli.CommandLine;
import picocli.CommandLine.Command;
import picocli.CommandLine.HelpCommand;
import picocli.CommandLine.Option;
import picocli.CommandLine.Parameters;
import java.io.ByteArrayInputStream;
import java.io.File;
import java.io.FileDescriptor;
import java.io.FileInputStream;
import java.io.FileNotFoundException;
import java.io.FileOutputStream;
import java.io.IOException;
import java.io.InputStream;
import java.io.OutputStream;
import java.io.SequenceInputStream;
import java.util.Arrays;
@Command(
name = SimpleIonCli.NAME,
version = SimpleIonCli.VERSION,
subcommands = {HelpCommand.class},
mixinStandardHelpOptions = true
)
class SimpleIonCli {
public static final String NAME = "jion";
public static final String VERSION = "2024-10-31";
//TODO: Replace with InputStream.nullInputStream in JDK 11+
public static final InputStream EMPTY = new ByteArrayInputStream(new byte[0]);
public static void main(String[] args) {
CommandLine commandLine = new CommandLine(new SimpleIonCli())
.setCaseInsensitiveEnumValuesAllowed(true)
.setUsageHelpAutoWidth(true);
System.exit(commandLine.execute(args));
}
@Option(names={"-v", "--ion-version"}, description = "Output Ion version", defaultValue = "1.0",
converter = IonEncodingVersionConverter.class, scope = CommandLine.ScopeType.INHERIT)
IonEncodingVersion ionVersion;
@Option(names={"-f", "--format", "--output-format"}, defaultValue = "pretty",
description = "Output format, from the set (text | pretty | binary | debug | none).",
paramLabel = "<format>",
scope = CommandLine.ScopeType.INHERIT)
OutputFormat outputFormat;
@Option(names={"-o", "--output"}, paramLabel = "FILE", description = "Output file",
scope = CommandLine.ScopeType.INHERIT)
File outputFile;
@Command(name = "cat", aliases = {"process"},
description = "concatenate FILE(s) in the requested Ion output format",
mixinStandardHelpOptions = true)
int cat( @Parameters(paramLabel = "FILE") File... files) {
//TODO: Handle stream cutoff- java.io.IOException: Broken pipe
//TODO: This is not resilient to problems with a single file. Should it be?
try (InputStream in = getInputStream(files);
IonReader reader = IonReaderBuilder.standard().build(in);
OutputStream out = getOutputStream(outputFile);
IonWriter writer = getWriter(ionVersion, outputFormat, out)) {
// getInputStream will look for stdin if we don't supply
writer.writeValues(reader);
} catch (IOException e) {
System.err.println(e.getMessage());
return CommandLine.ExitCode.SOFTWARE;
}
// process files
return CommandLine.ExitCode.OK;
}
private static InputStream getInputStream(File... files) {
if (files == null || files.length == 0) return new FileInputStream(FileDescriptor.in);
// As convenient as this formulation is I'm not sure of the ordering guarantees here
// Revisit if that is ever problematic
return Arrays.stream(files)
.map(SimpleIonCli::getInputStream)
.reduce(EMPTY, SequenceInputStream::new);
}
private static InputStream getInputStream(File inputFile) {
try {
return new FileInputStream(inputFile);
} catch (FileNotFoundException e) {
throw cloak(e);
}
}
// Removing some boilerplate from checked-exception consuming paths, without RuntimeException wrapping
// JLS Section 18.4 covers type inference for generic methods,
// including the rule that `throws T` is inferred as RuntimeException if possible.
// See e.g. https://www.rainerhahnekamp.com/en/ignoring-exceptions-in-java/
private static <T extends Throwable> T cloak(Throwable t) throws T {
@SuppressWarnings("unchecked")
T result = (T) t;
return result;
}
private static FileOutputStream getOutputStream(File outputFile) throws IOException {
// non-line-buffered stdout, or the requested file output
return outputFile == null ? new FileOutputStream(FileDescriptor.out) : new FileOutputStream(outputFile);
}
private static IonWriter getWriter(IonEncodingVersion<?,?> version, OutputFormat format, OutputStream out) {
if (version == IonEncodingVersion.ION_1_0) return getWriter_1_0(format, out);
if (version == IonEncodingVersion.ION_1_1) return getWriter_1_1(format, out);
throw new IllegalArgumentException("Unrecognized IonEncodingVersion: " + version);
}
private static IonWriter getWriter_1_0(OutputFormat format, OutputStream out) {
switch (format) {
case Pretty: return IonEncodingVersion.ION_1_0.textWriterBuilder().withPrettyPrinting().build(out);
case Text: return IonEncodingVersion.ION_1_0.textWriterBuilder().build(out);
case Binary: return IonEncodingVersion.ION_1_0.binaryWriterBuilder().build(out);
case Debug: throw new UnsupportedOperationException("Not yet supported, pending ion-java #1005");
case None: return IonEncodingVersion.ION_1_0.textWriterBuilder().build(new NoOpOutputStream());
default: throw new IllegalArgumentException("Unrecognized or unsupported output format: " + format);
}
}
private static IonWriter getWriter_1_1(OutputFormat format, OutputStream out) {
switch (format) {
case Pretty: return IonEncodingVersion.ION_1_1.textWriterBuilder().withPrettyPrinting().build(out);
case Text: return IonEncodingVersion.ION_1_1.textWriterBuilder().build(out);
case Binary: return IonEncodingVersion.ION_1_1.binaryWriterBuilder().build(out);
case Debug: throw new UnsupportedOperationException("Not yet supported, pending ion-java #1005");
case None: return IonEncodingVersion.ION_1_1.textWriterBuilder().build(new NoOpOutputStream());
default: throw new IllegalArgumentException("Unrecognized or unsupported output format: " + format);
}
}
private enum OutputFormat {
Pretty, Text, Binary, Debug, None
}
private static class IonEncodingVersionConverter implements CommandLine.ITypeConverter<IonEncodingVersion<?,?>> {
@Override
public IonEncodingVersion<?,?> convert(String ionVersion) {
switch (ionVersion) {
case "1.0": return IonEncodingVersion.ION_1_0;
case "1.1": return IonEncodingVersion.ION_1_1;
default: throw new IllegalArgumentException("Unrecognized or unsupported Ion version: " + ionVersion);
}
}
}
}