Sort the warehouse and import magazyn.ini

Preset sort by name or track; import the Pascal starter/magazyn.ini warehouse.
This commit is contained in:
maj00r
2026-08-22 23:15:35 +02:00
parent 785b8c7afa
commit 825c6f57ec
2 changed files with 112 additions and 2 deletions

View File

@@ -1,13 +1,22 @@
using System;
using System.Collections.Generic;
using System.Globalization;
using System.IO;
using System.Linq;
using System.Text;
using System.Text.Json;
using System.Text.Json.Serialization;
using System.Text.Json.Serialization.Metadata;
using System.Text.RegularExpressions;
namespace StarterNG.Classes;
public enum PresetSort
{
Name,
Track
}
/// <summary>One saved consist in the user's vehicle warehouse.</summary>
public sealed class TrainsetPreset
{
@@ -54,6 +63,9 @@ public static class PresetStore
public static string FilePath { get; } = ResolvePath();
/// <summary>Pascal miSortByVehicleName / miSortByTrackName for the warehouse menu.</summary>
public static PresetSort SortMode { get; set; } = PresetSort.Name;
// Source-generated metadata keeps (de)serialisation AOT/trim-safe, matching the
// approach used for the vehicle database.
private static readonly JsonSerializerOptions Options = new()
@@ -65,8 +77,16 @@ public static class PresetStore
private static JsonTypeInfo<PresetCollection> TypeInfo =>
(JsonTypeInfo<PresetCollection>)Options.GetTypeInfo(typeof(PresetCollection));
/// <summary>All saved presets, sorted by name. Empty when none / unreadable.</summary>
public static IReadOnlyList<TrainsetPreset> All() => Load().Presets;
/// <summary>All saved presets, sorted per <see cref="SortMode"/>.</summary>
public static IReadOnlyList<TrainsetPreset> All()
{
var list = Load().Presets.ToList();
if (SortMode == PresetSort.Track)
list.Sort((a, b) => string.Compare(TrackOf(a), TrackOf(b), StringComparison.OrdinalIgnoreCase));
else
list.Sort((a, b) => string.Compare(a.Name, b.Name, StringComparison.OrdinalIgnoreCase));
return list;
}
/// <summary>
/// Saves (or overwrites, by name) a consist preset. No-op on a blank name or
@@ -93,6 +113,77 @@ public static class PresetStore
Persist(col);
}
/// <summary>
/// Import Pascal <c>starter/magazyn.ini</c> (or legacy starter.ini / RAINSTED.INI)
/// into the JSON warehouse. Returns how many consists were added.
/// </summary>
public static int ImportMagazyn(string? path = null)
{
path ??= FindMagazynPath();
if (path is null || !File.Exists(path)) return 0;
int added = 0;
try
{
string text = File.ReadAllText(path, Encoding.GetEncoding(1250));
var section = Regex.Matches(text,
@"\[TRAINSET\d+\s*=\s*([^\]]*)\](.*?)(?=\[TRAINSET|\z)",
RegexOptions.IgnoreCase | RegexOptions.Singleline);
foreach (Match m in section)
{
string name = m.Groups[1].Value.Trim();
if (name.Length == 0) name = $"import_{added + 1}";
var nodes = new List<string>();
foreach (Match line in Regex.Matches(m.Groups[2].Value, @"(?m)^\s*\d+\s*=\s*(.+)$"))
{
string body = line.Groups[1].Value.Trim();
if (!body.Contains("enddynamic", StringComparison.OrdinalIgnoreCase))
body += " enddynamic";
nodes.Add(body);
}
if (nodes.Count == 0) continue;
var sb = new StringBuilder();
sb.Append("trainset ").Append(Sanitize(name)).Append(" none 0 0\n");
foreach (string n in nodes)
sb.Append(n).Append('\n');
sb.Append("endtrainset\n");
Save(name, sb.ToString());
added++;
}
}
catch { }
return added;
}
private static string? FindMagazynPath()
{
string cwd = Directory.GetCurrentDirectory();
foreach (string rel in new[]
{
Path.Combine("starter", "magazyn.ini"),
"starter.ini",
"RAINSTED.INI"
})
{
string p = Path.Combine(cwd, rel);
if (File.Exists(p)) return p;
}
return null;
}
private static string TrackOf(TrainsetPreset p)
{
var m = Regex.Match(p.Entry ?? "", @"^\s*trainset\s+\S+\s+(\S+)", RegexOptions.Multiline | RegexOptions.IgnoreCase);
return m.Success ? m.Groups[1].Value : "";
}
private static string Sanitize(string name) =>
string.IsNullOrWhiteSpace(name) ? "preset" : name.Replace(' ', '_');
private static PresetCollection Load()
{
try

View File

@@ -61,6 +61,25 @@ public static class ConsistContextMenu
}
menu.Items.Add(load);
var sort = new MenuItem { Header = App.Loc["PresetSort"] };
var sortName = new MenuItem
{
Header = (PresetStore.SortMode == PresetSort.Name ? "● " : "○ ") + App.Loc["PresetSortName"]
};
sortName.Click += (_, _) => PresetStore.SortMode = PresetSort.Name;
var sortTrack = new MenuItem
{
Header = (PresetStore.SortMode == PresetSort.Track ? "● " : "○ ") + App.Loc["PresetSortTrack"]
};
sortTrack.Click += (_, _) => PresetStore.SortMode = PresetSort.Track;
sort.Items.Add(sortName);
sort.Items.Add(sortTrack);
menu.Items.Add(sort);
var import = new MenuItem { Header = App.Loc["PresetImportMagazyn"] };
import.Click += (_, _) => PresetStore.ImportMagazyn();
menu.Items.Add(import);
menu.Items.Add(new Separator());
// ── Clipboard (full trainset text) ─────────────────────────────────────