AetherDb ship-authoring product --not-offered writes the package product off the market

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
This commit is contained in:
soul 2026-10-21 20:28:41 +02:00
commit 9cedb4efe6
2 changed files with 34 additions and 8 deletions

View file

@ -272,6 +272,8 @@ public sealed class ShipProductTests : IDisposable
RefusedAndUntouched("product", path, "ACM");
RefusedAndUntouched("product", path, "ACM", "--name", " ");
RefusedAndUntouched("product", path, "ACM", "--remove");
RefusedAndUntouched("product", path, "ACM", "--not-offered");
RefusedAndUntouched("product", path, "ACM", "--name", "--not-offered");
var (code, _, error) = Run("product", path, "ACM", "--name", "Acme Skiff", "--description", "Reliable.");
Assert.True(code == 0, error);
@ -283,10 +285,29 @@ public sealed class ShipProductTests : IDisposable
Assert.Equal(ShipModCatalog.HullKey("mod.skiff"), product.Design.Key);
Assert.Equal(hull.Roles.Select(role => role.Name), product.Roles.Select(role => role.Role));
Assert.Equal("Reliable.", product.Description);
Assert.False(product.NotOffered);
// --not-offered writes the product off the market (ruling off-market-is-unoffered-product), with or without a description.
var (offCode, offOutput, offError) = Run("product", path, "ACM", "--name", "Acme Boss Skiff", "--not-offered");
Assert.True(offCode == 0, offError);
Assert.Contains("not offered", offOutput);
var off = Assert.Single(ShipAuthoringStore.Load(path).Products);
Assert.True(off.NotOffered);
Assert.Equal("Acme Boss Skiff", off.Name);
Assert.Null(off.Description);
Assert.Equal(0, Run("product", path, "ACM", "--name", "Acme Boss Skiff", "--description", "Unsold.", "--not-offered").Code);
off = Assert.Single(ShipAuthoringStore.Load(path).Products);
Assert.True(off.NotOffered);
Assert.Equal("Unsold.", off.Description);
// The maker by name replaces the product by short name: a maker has one product of the hull.
Assert.Equal(0, Run("product", path, "Acme", "--name", "Renamed").Code);
Assert.Equal("Renamed", Assert.Single(ShipAuthoringStore.Load(path).Products).Name);
// Written again without the flag, it is back on the market.
var (renamedCode, renamedOutput, _) = Run("product", path, "Acme", "--name", "Renamed");
Assert.Equal(0, renamedCode);
Assert.DoesNotContain("not offered", renamedOutput);
var renamed = Assert.Single(ShipAuthoringStore.Load(path).Products);
Assert.Equal("Renamed", renamed.Name);
Assert.False(renamed.NotOffered);
RefusedAndUntouched("price", path, "-1");
RefusedAndUntouched("price", path, "1.5");
@ -294,6 +315,7 @@ public sealed class ShipProductTests : IDisposable
Assert.Equal(0, Run("price", path, "250").Code);
Assert.Equal(250, ShipAuthoringStore.Read(path).Hull.Price);
RefusedAndUntouched("product", path, "ACM", "--remove", "--not-offered");
Assert.Equal(0, Run("product", path, "ACM", "--remove").Code);
Assert.Empty(ShipAuthoringStore.Load(path).Products);
RefusedAndUntouched("product", path, "ACM", "--remove");

View file

@ -8,7 +8,7 @@ using System.Linq;
// Uses the existing database executable; mod authoring does not earn another binary target.
public static class ShipAuthoringCommands
{
private const string Usage = "ship-authoring create <ship.cc> <stable-id> <name> [--like <shipped hull name>] | inspect <ship.cc> | centre <ship.cc> | validate <ship.cc> | product <ship.cc> <maker> (--name <name> [--description <text>] | --remove) | price <ship.cc> <price> | compose <shipped.cc> <derived.cc> <mods-dir>";
private const string Usage = "ship-authoring create <ship.cc> <stable-id> <name> [--like <shipped hull name>] | inspect <ship.cc> | centre <ship.cc> | validate <ship.cc> | product <ship.cc> <maker> (--name <name> [--description <text>] [--not-offered] | --remove) | price <ship.cc> <price> | compose <shipped.cc> <derived.cc> <mods-dir>";
public static int Run(string[] args)
{
@ -93,8 +93,11 @@ public static class ShipAuthoringCommands
}
if (args[0] == "product" && args.Length >= 4)
{
var remove = args.Length == 4 && args[3] == "--remove";
var named = args.Length >= 5 && args[3] == "--name" && (args.Length == 5 || args.Length == 7 && args[5] == "--description");
// A trailing --not-offered takes the product off the market (ruling off-market-is-unoffered-product); --remove takes none.
var notOffered = args[args.Length - 1] == "--not-offered";
var count = notOffered ? args.Length - 1 : args.Length;
var remove = !notOffered && count == 4 && args[3] == "--remove";
var named = count >= 5 && args[3] == "--name" && (count == 5 || count == 7 && args[5] == "--description");
if (!remove && !named) { Console.Error.WriteLine(Usage); return 1; }
var (hull, ship, products) = ShipAuthoringStore.Load(args[1]);
var (makerKey, makerName) = ShippedFaction(args[2]);
@ -112,14 +115,15 @@ public static class ShipAuthoringCommands
ShipAuthoringStore.WriteProduct(cache, ship.Id, new FactionProductData
{
Name = args[4],
Description = args.Length == 7 ? args[6] : null,
Description = count == 7 ? args[6] : null,
Design = new CultRecordRef<CraftedItemData>(ShipModCatalog.HullKey(ship.Id)),
Manufacturer = new CultRecordRef<Faction>(makerKey),
Roles = hull.Roles.Select(role => new ProductRole { Role = role.Name }).ToList()
Roles = hull.Roles.Select(role => new ProductRole { Role = role.Name }).ToList(),
NotOffered = notOffered
});
cache.FlushAsync().Wait();
}
Console.WriteLine($"{ship.Id}: {(remove ? "removed" : "wrote")} the product by {makerName}.");
Console.WriteLine($"{ship.Id}: {(remove ? "removed" : "wrote")} the product by {makerName}{(notOffered ? ", not offered" : "")}.");
return 0;
}
Console.Error.WriteLine(Usage);