Skip to content

Commit f4fdd54

Browse files
authored
Merge pull request #35 from sqlcj-dev/feature/generate-config-path
feat(cli): add --config option to generate
2 parents da50266 + 3dfaf38 commit f4fdd54

5 files changed

Lines changed: 147 additions & 27 deletions

File tree

‎README.md‎

Lines changed: 13 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -72,9 +72,19 @@ java -jar sqlcj-cli-0.1.0-SNAPSHOT.jar generate # compile sqlcj.yaml into Java
7272
java -jar sqlcj-cli-0.1.0-SNAPSHOT.jar --help # list the commands
7373
```
7474

75-
`generate` reads `sqlcj.yaml` from the directory it is run in. It exits with
76-
status `0` on success, and with status `1` after printing one diagnostic for
77-
invalid configuration, an unreadable source, or a query it cannot compile.
75+
`generate` reads `sqlcj.yaml` from the directory it is run in.
76+
`--config <path>` names another configuration file, so generation can run from
77+
any working directory:
78+
79+
```bash
80+
java -jar sqlcj-cli-0.1.0-SNAPSHOT.jar generate --config ../project/sqlcj.yaml
81+
```
82+
83+
A relative `--config` value is resolved against the directory the command is
84+
run in, while the relative paths inside the file stay relative to the file's own
85+
directory. `generate` exits with status `0` on success, and with status `1`
86+
after printing one diagnostic for invalid configuration, an unreadable source,
87+
or a query it cannot compile.
7888

7989
## Getting started
8090

‎docs/configuration.md‎

Lines changed: 18 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -12,8 +12,21 @@ The `generate` command reads `sqlcj.yaml` from the directory it is run in:
1212
sqlcj generate
1313
```
1414

15-
There is no configuration-path option and no configuration discovery in parent
16-
directories.
15+
`--config <path>` selects another file, so generation can run from any working
16+
directory:
17+
18+
```bash
19+
sqlcj generate --config ../project/sqlcj.yaml
20+
```
21+
22+
The option defaults to `sqlcj.yaml`, and a relative value is resolved against
23+
the directory the command is run in. Relative paths inside the file are still
24+
resolved against the directory that contains the file, not against the working
25+
directory. A missing or invalid explicit file reports the usual configuration
26+
diagnostic and exits with status `1`; there is no fallback to `sqlcj.yaml`.
27+
28+
There is no short alias, no environment variable, and no configuration
29+
discovery in parent directories.
1730

1831
## Supported Version
1932

@@ -109,8 +122,9 @@ generated/dev/example/generated/AuthorRepository.java
109122
## Path Resolution
110123

111124
A relative `schema`, `queries`, or `java.out` path is resolved against the
112-
directory that contains `sqlcj.yaml`, not against the process working directory
113-
at a later point in time. An absolute path is used as-is.
125+
directory that contains the configuration file, whether that file is the
126+
default `sqlcj.yaml` or one named by `--config`, and not against the process
127+
working directory at a later point in time. An absolute path is used as-is.
114128

115129
Resolved paths are normalized lexically, so `../sql/schema.sql` is supported.
116130
Resolution does not require `java.out` to exist, does not expand `~`,

‎docs/quickstart.md‎

Lines changed: 6 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -153,6 +153,7 @@ query of `sql/queries.sql`.
153153

154154
`sqlcj generate` reads `sqlcj.yaml` from the directory it is run in, and the
155155
relative paths above are resolved against the directory that contains the file.
156+
`--config <path>` names another file when the command runs from elsewhere.
156157
Generated output therefore belongs in the build directory, where `mvn clean`
157158
removes it. See [Configuration](configuration.md) for the full file format.
158159

@@ -414,8 +415,11 @@ mvn exec:java
414415
generate, so a query that was renamed or removed would otherwise leave a stale
415416
class behind that still compiles.
416417
- `sqlcj generate` is a separate command. It is not bound to the Maven
417-
lifecycle, so it must run after `clean` and before `compile`. It must run from
418-
the project root, because it reads `sqlcj.yaml` from the current directory.
418+
lifecycle, so it must run after `clean` and before `compile`. The commands
419+
above run from the project root, because `generate` reads `sqlcj.yaml` from
420+
the current directory; from another directory, pass
421+
`--config <path to sqlcj.yaml>` instead, and the paths inside the file keep
422+
resolving against the project root.
419423
- `mvn compile` then compiles `src/main/java` together with
420424
`target/generated-sources/sqlcj`.
421425

‎sqlcj-cli/src/main/java/dev/sqlcj/cli/command/GenerateCommand.java‎

Lines changed: 15 additions & 7 deletions
Original file line numberDiff line numberDiff line change
@@ -9,6 +9,7 @@
99
import picocli.CommandLine;
1010
import picocli.CommandLine.Command;
1111
import picocli.CommandLine.Model.CommandSpec;
12+
import picocli.CommandLine.Option;
1213
import picocli.CommandLine.Spec;
1314

1415
import java.nio.file.Path;
@@ -20,17 +21,28 @@
2021
)
2122
public class GenerateCommand implements Callable<Integer> {
2223

23-
private static final String CONFIG_FILE = "sqlcj.yaml";
24-
2524
private final ConfigLoader configLoader = new YamlConfigLoader();
2625

2726
@Spec
2827
CommandSpec spec;
2928

29+
@Option(
30+
names = "--config",
31+
paramLabel = "<path>",
32+
defaultValue = "sqlcj.yaml",
33+
description = """
34+
Configuration file to generate from. A relative path is \
35+
resolved against the current directory, while relative paths \
36+
inside the file are resolved against its own directory. \
37+
Default: ${DEFAULT-VALUE}
38+
"""
39+
)
40+
Path configFile;
41+
3042
@Override
3143
public Integer call() {
3244
try {
33-
Config config = configLoader.load(configPath());
45+
Config config = configLoader.load(configFile);
3446
SqlcjCompiler compiler = new SqlcjCompiler();
3547
compiler.compile(config);
3648
return CommandLine.ExitCode.OK;
@@ -42,8 +54,4 @@ public Integer call() {
4254
return CommandLine.ExitCode.SOFTWARE;
4355
}
4456
}
45-
46-
private Path configPath() {
47-
return Path.of(CONFIG_FILE);
48-
}
4957
}

‎sqlcj-cli/src/test/java/dev/sqlcj/cli/command/GenerateCommandTest.java‎

Lines changed: 95 additions & 11 deletions
Original file line numberDiff line numberDiff line change
@@ -6,15 +6,19 @@
66
import java.io.IOException;
77
import java.nio.file.Files;
88
import java.nio.file.Path;
9+
import java.util.ArrayList;
10+
import java.util.List;
911
import java.util.concurrent.TimeUnit;
12+
import java.util.stream.Stream;
1013

1114
import static org.junit.jupiter.api.Assertions.assertEquals;
1215
import static org.junit.jupiter.api.Assertions.assertFalse;
1316
import static org.junit.jupiter.api.Assertions.assertTrue;
1417

1518
/**
16-
* Exercises the {@code generate} command from its fixed {@code sqlcj.yaml}
17-
* entry point in an isolated working directory.
19+
* Exercises the {@code generate} command from its default {@code sqlcj.yaml}
20+
* entry point and from an explicit {@code --config} path, in an isolated
21+
* working directory.
1822
*/
1923
class GenerateCommandTest {
2024

@@ -69,6 +73,75 @@ void shouldGenerateFromConfigurationInWorkingDirectory() throws Exception {
6973
assertTrue(source.contains("public GetUserResult getUser(Long id)"));
7074
}
7175

76+
@Test
77+
void shouldGenerateFromExplicitConfigurationPathInAnotherDirectory() throws Exception {
78+
Path project = Files.createDirectory(workingDirectory.resolve("project"));
79+
Path elsewhere = Files.createDirectory(workingDirectory.resolve("elsewhere"));
80+
81+
Files.writeString(project.resolve("schema.sql"), SCHEMA);
82+
Files.writeString(project.resolve("queries.sql"), QUERIES);
83+
Files.writeString(
84+
project.resolve("sqlcj-users.yaml"),
85+
"""
86+
version: "1"
87+
sql:
88+
- name: Users
89+
schema: schema.sql
90+
queries: queries.sql
91+
java:
92+
package: dev.example.generated
93+
out: generated
94+
"""
95+
);
96+
97+
Result result = runGenerateIn(elsewhere, "--config", "../project/sqlcj-users.yaml");
98+
99+
assertEquals(0, result.exitCode(), result.error());
100+
101+
Path generated = project.resolve("generated/dev/example/generated/UsersRepository.java");
102+
103+
assertTrue(Files.exists(generated), result.error());
104+
assertTrue(
105+
Files.readString(generated).contains("public GetUserResult getUser(Long id)")
106+
);
107+
108+
try (Stream<Path> entries = Files.list(elsewhere)) {
109+
assertEquals(List.of(), entries.toList());
110+
}
111+
}
112+
113+
@Test
114+
void shouldFailWithoutFallbackWhenExplicitConfigurationIsMissing() throws Exception {
115+
Files.writeString(workingDirectory.resolve("schema.sql"), SCHEMA);
116+
Files.writeString(workingDirectory.resolve("queries.sql"), QUERIES);
117+
Files.writeString(
118+
workingDirectory.resolve("sqlcj.yaml"),
119+
"""
120+
version: "1"
121+
sql:
122+
- name: Users
123+
schema: schema.sql
124+
queries: queries.sql
125+
java:
126+
package: dev.example.generated
127+
out: generated
128+
"""
129+
);
130+
131+
Result result = runGenerate("--config", "missing.yaml");
132+
133+
assertEquals(1, result.exitCode(), result.error());
134+
assertTrue(
135+
result.error().contains(
136+
"sqlcj: Cannot read configuration file: "
137+
+ workingDirectory.resolve("missing.yaml")
138+
),
139+
result.error()
140+
);
141+
assertFalse(result.error().contains("\tat "));
142+
assertFalse(Files.exists(workingDirectory.resolve("generated")));
143+
}
144+
72145
@Test
73146
void shouldFailWithConciseDiagnosticForMalformedConfiguration() throws Exception {
74147
Files.writeString(
@@ -171,17 +244,28 @@ void shouldFailWithConciseDiagnosticForInvalidQuery() throws Exception {
171244
assertFalse(Files.exists(workingDirectory.resolve("generated")));
172245
}
173246

174-
private Result runGenerate() throws IOException, InterruptedException {
247+
private Result runGenerate(String... arguments) throws IOException, InterruptedException {
248+
return runGenerateIn(workingDirectory, arguments);
249+
}
250+
251+
private Result runGenerateIn(Path directory, String... arguments)
252+
throws IOException, InterruptedException {
175253
Path java = Path.of(System.getProperty("java.home"), "bin", "java");
176254

177-
Process process = new ProcessBuilder(
178-
java.toString(),
179-
"-classpath",
180-
System.getProperty("java.class.path"),
181-
"dev.sqlcj.Main",
182-
"generate"
183-
)
184-
.directory(workingDirectory.toFile())
255+
List<String> command = new ArrayList<>(
256+
List.of(
257+
java.toString(),
258+
"-classpath",
259+
System.getProperty("java.class.path"),
260+
"dev.sqlcj.Main",
261+
"generate"
262+
)
263+
);
264+
265+
command.addAll(List.of(arguments));
266+
267+
Process process = new ProcessBuilder(command)
268+
.directory(directory.toFile())
185269
.start();
186270

187271
String output = new String(process.getInputStream().readAllBytes());

0 commit comments

Comments
 (0)