Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
6 changes: 3 additions & 3 deletions README.md
Original file line number Diff line number Diff line change
Expand Up @@ -138,10 +138,10 @@ clink '((($i:)) (($i:)))' --changes

## Export database as LiNo

Use `--out` to write the complete database to a `.lino` file after the query is processed. The older `--lino-output` option is also accepted.
Use `--out` or `--export` to write the complete database to a `.lino` file after the query is processed. The older `--lino-output` option is also accepted.

```bash
clink '() ((child: father mother))' --out database.lino
clink '() ((child: father mother))' --export database.lino
```

`database.lino`:
Expand Down Expand Up @@ -319,7 +319,7 @@ clink '((1: 2 1) (2: 1 2)) ()' --changes --after
| `--before` | bool | `false` | `-b` | Print the state of the database before applying changes |
| `--changes` | bool | `false` | `-c` | Print the changes applied by the query |
| `--after` | bool | `false` | `--links`, `-a` | Print the state of the database after applying changes |
| `--out` | string | _None_ | `--lino-output` | Write the complete database as a LiNo file |
| `--out` | string | _None_ | `--export`, `--lino-output` | Write the complete database as a LiNo file |

## For developers and debugging

Expand Down
5 changes: 5 additions & 0 deletions csharp/.changeset/add-export-alias.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,5 @@
---
'Foundation.Data.Doublets.Cli': minor
---

Added `--export` as an alias for `--out` database export.
131 changes: 131 additions & 0 deletions csharp/Foundation.Data.Doublets.Cli.Tests/CliExportIntegrationTests.cs
Original file line number Diff line number Diff line change
@@ -0,0 +1,131 @@
using System.Diagnostics;

namespace Foundation.Data.Doublets.Cli.Tests;

public class CliExportIntegrationTests
{
[Fact]
public async Task ExportAlias_WritesNumberedReferences()
{
var tempDirectory = CreateTempDirectory();

try
{
var dbPath = Path.Combine(tempDirectory, "numbered.links");
var outputPath = Path.Combine(tempDirectory, "numbered.lino");

var result = await RunClinkAsync("--db", dbPath, "() ((1 1) (2 2))", "--export", outputPath);

AssertClinkSucceeded(result);
Assert.Equal(new[] { "(1: 1 1)", "(2: 2 2)" }, File.ReadAllLines(outputPath));
}
finally
{
Directory.Delete(tempDirectory, recursive: true);
}
}

[Fact]
public async Task ExportAlias_WritesNamedReferences()
{
var tempDirectory = CreateTempDirectory();

try
{
var dbPath = Path.Combine(tempDirectory, "named.links");
var outputPath = Path.Combine(tempDirectory, "named.lino");

var result = await RunClinkAsync(
"--db",
dbPath,
"--auto-create-missing-references",
"() ((child: father mother))",
"--export",
outputPath);

AssertClinkSucceeded(result);
Assert.Equal(
new[] { "(father: father father)", "(mother: mother mother)", "(child: father mother)" },
File.ReadAllLines(outputPath));
}
finally
{
Directory.Delete(tempDirectory, recursive: true);
}
}

private static async Task<CommandResult> RunClinkAsync(params string[] clinkArguments)
{
var csharpDirectory = FindCsharpDirectory();
var projectPath = Path.Combine(csharpDirectory, "Foundation.Data.Doublets.Cli", "Foundation.Data.Doublets.Cli.csproj");

using var process = new Process
{
StartInfo = new ProcessStartInfo
{
FileName = "dotnet",
WorkingDirectory = csharpDirectory,
RedirectStandardOutput = true,
RedirectStandardError = true
}
};

process.StartInfo.ArgumentList.Add("run");
process.StartInfo.ArgumentList.Add("--project");
process.StartInfo.ArgumentList.Add(projectPath);
process.StartInfo.ArgumentList.Add("--");
foreach (var argument in clinkArguments)
{
process.StartInfo.ArgumentList.Add(argument);
}

process.Start();
var stdout = process.StandardOutput.ReadToEndAsync();
var stderr = process.StandardError.ReadToEndAsync();

using var cts = new CancellationTokenSource(TimeSpan.FromSeconds(60));
try
{
await process.WaitForExitAsync(cts.Token);
}
catch (OperationCanceledException)
{
process.Kill(entireProcessTree: true);
throw new TimeoutException("Timed out while running the C# clink integration test.");
}

return new CommandResult(process.ExitCode, await stdout, await stderr);
}

private static void AssertClinkSucceeded(CommandResult result)
{
Assert.True(
result.ExitCode == 0,
$"clink exited with {result.ExitCode}\nstdout:\n{result.Stdout}\nstderr:\n{result.Stderr}");
}

private static string FindCsharpDirectory()
{
var directory = new DirectoryInfo(AppContext.BaseDirectory);
while (directory is not null)
{
if (File.Exists(Path.Combine(directory.FullName, "Foundation.Data.Doublets.Cli.sln")))
{
return directory.FullName;
}

directory = directory.Parent;
}

throw new DirectoryNotFoundException("Could not find the csharp directory containing Foundation.Data.Doublets.Cli.sln.");
}

private static string CreateTempDirectory()
{
var tempDirectory = Path.Combine(Path.GetTempPath(), $"clink-export-{Guid.NewGuid():N}");
Directory.CreateDirectory(tempDirectory);
return tempDirectory;
}

private sealed record CommandResult(int ExitCode, string Stdout, string Stderr);
}
2 changes: 1 addition & 1 deletion csharp/Foundation.Data.Doublets.Cli/Program.cs
Original file line number Diff line number Diff line change
Expand Up @@ -61,7 +61,7 @@
DefaultValueFactory = _ => false
};

var outputOption = new Option<string?>("--out", "--lino-output")
var outputOption = new Option<string?>("--out", "--lino-output", "--export")
{
Description = "Path to write the complete database as a LiNo file"
};
Expand Down
5 changes: 5 additions & 0 deletions rust/changelog.d/20260502_061000_export_alias.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,5 @@
---
bump: minor
---

Added `--export` as an alias for `--out` database export.
6 changes: 3 additions & 3 deletions rust/src/cli.rs
Original file line number Diff line number Diff line change
Expand Up @@ -97,7 +97,7 @@ impl Cli {
cli.after = parse_bool("--after", value)?;
continue;
}
if let Some(value) = inline_value(&arg, &["--out", "--lino-output"]) {
if let Some(value) = inline_value(&arg, &["--out", "--lino-output", "--export"]) {
cli.lino_output = Some(value.to_string());
continue;
}
Expand Down Expand Up @@ -130,7 +130,7 @@ impl Cli {
"-a" | "--after" | "--links" => {
cli.after = next_bool_value(&mut args, true)?;
}
"--out" | "--lino-output" => {
"--out" | "--lino-output" | "--export" => {
cli.lino_output = Some(next_value(&mut args, &arg)?);
}
"--" => {
Expand Down Expand Up @@ -178,7 +178,7 @@ impl Cli {
" Print the changes applied by the query\n",
" -a, --after, --links\n",
" Print the state of the database after applying changes\n",
" --out <OUT>, --lino-output <OUT>\n",
" --out <OUT>, --lino-output <OUT>, --export <OUT>\n",
" Write the complete database as a LiNo file\n",
" -h, --help\n",
" Print help\n",
Expand Down
14 changes: 14 additions & 0 deletions rust/tests/cli_arguments_tests.rs
Original file line number Diff line number Diff line change
Expand Up @@ -71,6 +71,20 @@ fn parses_inline_alias_values_and_boolean_values() {
assert_eq!(cli.lino_output.as_deref(), Some("links.lino"));
}

#[test]
fn parses_export_alias_as_lino_output_path() {
let cli = parse_run(&["clink", "--export", "database.lino"]);

assert_eq!(cli.lino_output.as_deref(), Some("database.lino"));
}

#[test]
fn parses_inline_export_alias_as_lino_output_path() {
let cli = parse_run(&["clink", "--export=database.lino"]);

assert_eq!(cli.lino_output.as_deref(), Some("database.lino"));
}

#[test]
fn returns_help_and_version_commands() {
assert_eq!(
Expand Down
68 changes: 68 additions & 0 deletions rust/tests/cli_export_tests.rs
Original file line number Diff line number Diff line change
@@ -0,0 +1,68 @@
use anyhow::{ensure, Result};
use std::path::Path;
use std::process::{Command, Output};
use tempfile::tempdir;

#[test]
fn export_alias_writes_numbered_references() -> Result<()> {
let temp_dir = tempdir()?;
let db_path = temp_dir.path().join("numbered.links");
let output_path = temp_dir.path().join("numbered.lino");

let output = run_clink(&db_path, "() ((1 1) (2 2))", false, &output_path)?;

ensure_success(&output)?;
assert_eq!(
std::fs::read_to_string(&output_path)?,
"(1: 1 1)\n(2: 2 2)\n"
);

Ok(())
}

#[test]
fn export_alias_writes_named_references() -> Result<()> {
let temp_dir = tempdir()?;
let db_path = temp_dir.path().join("named.links");
let output_path = temp_dir.path().join("named.lino");

let output = run_clink(&db_path, "() ((child: father mother))", true, &output_path)?;

ensure_success(&output)?;
assert_eq!(
std::fs::read_to_string(&output_path)?,
"(father: father father)\n(mother: mother mother)\n(child: father mother)\n"
);

Ok(())
}

fn run_clink(
db_path: &Path,
query: &str,
auto_create_missing_references: bool,
output_path: &Path,
) -> Result<Output> {
let mut command = Command::new(env!("CARGO_BIN_EXE_clink"));
command.arg("--db").arg(db_path);
if auto_create_missing_references {
command.arg("--auto-create-missing-references");
}

Ok(command
.arg(query)
.arg("--export")
.arg(output_path)
.output()?)
}

fn ensure_success(output: &Output) -> Result<()> {
ensure!(
output.status.success(),
"clink failed with status {:?}\nstdout:\n{}\nstderr:\n{}",
output.status.code(),
String::from_utf8_lossy(&output.stdout),
String::from_utf8_lossy(&output.stderr)
);
Ok(())
}
Loading