Skip to content

Commit 669a5e3

Browse files
authored
Command-line tools to split and/or reassemble .fwdata files (#403)
These tools enable automated Send/Receive testing in LfMerge, and may allow future capabilities in Lexbox such as better history views.
1 parent 6f825b2 commit 669a5e3

File tree

3 files changed

+187
-0
lines changed

3 files changed

+187
-0
lines changed

FLExBridge.sln

Lines changed: 6 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -52,6 +52,8 @@ Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "LfMergeBridgeTestApp", "src
5252
EndProject
5353
Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "LfMergeBridgeTests", "src\LfMergeBridgeTests\LfMergeBridgeTests.csproj", "{6CB1246D-956A-4759-AA13-D434CBB383FE}"
5454
EndProject
55+
Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "MkFwData", "src\MkFwData\MkFwData.csproj", "{5CDB086A-79DE-4EF4-BB48-4AEAEEB0827B}"
56+
EndProject
5557
Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "LiftFileCheckerApp", "src\LiftFileCheckerApp\LiftFileCheckerApp.csproj", "{30AA046B-5E14-408C-89EF-8601BB27FB32}"
5658
EndProject
5759
Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "LibTriboroughBridge-ChorusPluginTests", "src\LibTriboroughBridge-ChorusPluginTests\LibTriboroughBridge-ChorusPluginTests.csproj", "{AA6CC4E2-6FD8-4B30-99EC-A446E9CAA176}"
@@ -124,6 +126,10 @@ Global
124126
{6CB1246D-956A-4759-AA13-D434CBB383FE}.Debug|Any CPU.Build.0 = Debug|Any CPU
125127
{6CB1246D-956A-4759-AA13-D434CBB383FE}.Release|Any CPU.ActiveCfg = Release|Any CPU
126128
{6CB1246D-956A-4759-AA13-D434CBB383FE}.Release|Any CPU.Build.0 = Release|Any CPU
129+
{5CDB086A-79DE-4EF4-BB48-4AEAEEB0827B}.Debug|Any CPU.ActiveCfg = Debug|Any CPU
130+
{5CDB086A-79DE-4EF4-BB48-4AEAEEB0827B}.Debug|Any CPU.Build.0 = Debug|Any CPU
131+
{5CDB086A-79DE-4EF4-BB48-4AEAEEB0827B}.Release|Any CPU.ActiveCfg = Release|Any CPU
132+
{5CDB086A-79DE-4EF4-BB48-4AEAEEB0827B}.Release|Any CPU.Build.0 = Release|Any CPU
127133
{30AA046B-5E14-408C-89EF-8601BB27FB32}.Debug|Any CPU.ActiveCfg = Debug|Any CPU
128134
{30AA046B-5E14-408C-89EF-8601BB27FB32}.Debug|Any CPU.Build.0 = Debug|Any CPU
129135
{30AA046B-5E14-408C-89EF-8601BB27FB32}.Release|Any CPU.ActiveCfg = Release|Any CPU

src/MkFwData/MkFwData.csproj

Lines changed: 30 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,30 @@
1+
<Project Sdk="Microsoft.NET.Sdk">
2+
3+
<PropertyGroup>
4+
<!-- We want to build fwdata.exe, but have a more explanatory project name -->
5+
<AssemblyName>fwdata</AssemblyName>
6+
<OutputType>Exe</OutputType>
7+
<TargetFrameworks>net8.0</TargetFrameworks>
8+
<TargetFramework>net8.0</TargetFramework>
9+
<ImplicitUsings>enable</ImplicitUsings>
10+
<Nullable>enable</Nullable>
11+
<GenerateResourceUsePreserializedResources>true</GenerateResourceUsePreserializedResources>
12+
<Mercurial4ChorusDestDir>$(MSBuildProjectDirectory)</Mercurial4ChorusDestDir>
13+
</PropertyGroup>
14+
15+
<ItemGroup>
16+
<ProjectReference Include="..\LfMergeBridge\LfMergeBridge.csproj" />
17+
</ItemGroup>
18+
19+
<ItemGroup>
20+
<PackageReference Include="System.CommandLine" Version="2.0.0-beta4.22272.1" />
21+
<PackageReference Include="SIL.Chorus.LibChorus" Version="$(ChorusVersion)" />
22+
<PackageReference Include="SIL.Chorus.Mercurial" Version="6.*" />
23+
</ItemGroup>
24+
25+
<ItemGroup>
26+
<Content Include="Mercurial\**" CopyToOutputDirectory="Always" />
27+
<Content Include="MercurialExtensions\**" CopyToOutputDirectory="Always" />
28+
</ItemGroup>
29+
30+
</Project>

src/MkFwData/Program.cs

Lines changed: 151 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,151 @@
1+
using Chorus.VcsDrivers.Mercurial;
2+
using SIL.Progress;
3+
using System.CommandLine;
4+
5+
class Program
6+
{
7+
static async Task<int> Main(string[] args)
8+
{
9+
var rootCommand = new RootCommand("Make or split .fwdata file");
10+
11+
var verboseOption = new Option<bool>(
12+
["--verbose", "-v"],
13+
"Display verbose output"
14+
);
15+
rootCommand.AddGlobalOption(verboseOption);
16+
17+
var quietOption = new Option<bool>(
18+
["--quiet", "-q"],
19+
"Suppress all output (overrides --verbose if present)"
20+
);
21+
rootCommand.AddGlobalOption(quietOption);
22+
23+
var splitCommand = new Command("split", "Split .fwdata file (push Humpty off the wall)");
24+
var buildCommand = new Command("build", "Rebuild .fwdata file (put Humpty together again)");
25+
26+
rootCommand.Add(splitCommand);
27+
rootCommand.Add(buildCommand);
28+
29+
var filename = new Argument<string>(
30+
"file",
31+
"Name of .fwdata file to create or split, or directory to create/split it in"
32+
);
33+
splitCommand.Add(filename);
34+
buildCommand.Add(filename);
35+
36+
var hgRevOption = new Option<string>(
37+
["--rev", "-r"],
38+
"Revision to check out (default \"tip\")"
39+
);
40+
hgRevOption.SetDefaultValue("tip");
41+
buildCommand.AddGlobalOption(hgRevOption);
42+
43+
var timeoutOption = new Option<int>(
44+
["--timeout", "-t"],
45+
"Timeout in seconds for Hg commands (default 600)"
46+
);
47+
timeoutOption.SetDefaultValue(600);
48+
buildCommand.AddGlobalOption(timeoutOption);
49+
50+
var cleanupOption = new Option<bool>(
51+
["--cleanup", "-c"],
52+
"Clean repository after creating .fwdata file (CAUTION: deletes every other file except .fwdata)"
53+
);
54+
buildCommand.Add(cleanupOption);
55+
56+
buildCommand.SetHandler(BuildFwData, filename, verboseOption, quietOption, hgRevOption, timeoutOption, cleanupOption);
57+
58+
var cleanupOptionForSplit = new Option<bool>(
59+
["--cleanup", "-c"],
60+
"Delete .fwdata file after splitting"
61+
);
62+
splitCommand.Add(cleanupOptionForSplit);
63+
64+
splitCommand.SetHandler(SplitFwData, filename, verboseOption, quietOption, cleanupOptionForSplit);
65+
66+
return await rootCommand.InvokeAsync(args);
67+
}
68+
69+
static FileInfo? MaybeLocateFwDataFile(string input)
70+
{
71+
if (Directory.Exists(input)) {
72+
var dirInfo = new DirectoryInfo(input);
73+
var fname = dirInfo.Name + ".fwdata";
74+
return new FileInfo(Path.Join(input, fname));
75+
} else if (File.Exists(input)) {
76+
return new FileInfo(input);
77+
} else if (File.Exists(input + ".fwdata")) {
78+
return new FileInfo(input + ".fwdata");
79+
} else {
80+
return null;
81+
}
82+
}
83+
84+
static FileInfo LocateFwDataFile(string input)
85+
{
86+
var result = MaybeLocateFwDataFile(input);
87+
if (result != null) return result;
88+
if (input.EndsWith(".fwdata")) return new FileInfo(input);
89+
return new FileInfo(input + ".fwdata");
90+
}
91+
92+
static Task<int> SplitFwData(string filename, bool verbose, bool quiet, bool cleanup)
93+
{
94+
IProgress progress = quiet ? new NullProgress() : new ConsoleProgress();
95+
progress.ShowVerbose = verbose;
96+
var file = MaybeLocateFwDataFile(filename);
97+
if (file == null || !file.Exists) {
98+
progress.WriteError("Could not find {0}", filename);
99+
return Task.FromResult(1);
100+
}
101+
string name = file.FullName;
102+
progress.WriteVerbose("Splitting {0} ...", name);
103+
LfMergeBridge.LfMergeBridge.DisassembleFwdataFile(progress, writeVerbose: true, name);
104+
progress.WriteMessage("Finished splitting {0}", name);
105+
if (cleanup)
106+
{
107+
progress.WriteVerbose("Cleaning up...");
108+
var fwdataFile = new FileInfo(name);
109+
if (fwdataFile.Exists) {
110+
fwdataFile.Delete();
111+
progress.WriteVerbose("Deleted {0}", fwdataFile.FullName);
112+
} else {
113+
progress.WriteVerbose("File not found, so not deleting: {0}", fwdataFile.FullName);
114+
}
115+
}
116+
return Task.FromResult(0);
117+
}
118+
119+
static Task<int> BuildFwData(string filename, bool verbose, bool quiet, string rev, int timeout, bool cleanup)
120+
{
121+
IProgress progress = quiet ? new NullProgress() : new ConsoleProgress();
122+
progress.ShowVerbose = verbose;
123+
var file = LocateFwDataFile(filename);
124+
if (file.Exists) {
125+
progress.WriteWarning("File {0} already exists and will be overwritten", file.FullName);
126+
}
127+
var dir = file.Directory;
128+
if (dir == null || !dir.Exists) {
129+
progress.WriteError("Could not find directory {0}. MkFwData needs a Mercurial repo to work with.", dir?.FullName ?? "(null)");
130+
return Task.FromResult(1);
131+
}
132+
string name = file.FullName;
133+
progress.WriteMessage("Checking out {0}", rev);
134+
var result = HgRunner.Run($"hg checkout {rev}", dir.FullName, timeout, progress);
135+
if (result.ExitCode != 0)
136+
{
137+
progress.WriteMessage("Could not find Mercurial repo in directory {0}. MkFwData needs a Mercurial repo to work with.", dir.FullName ?? "(null)");
138+
return Task.FromResult(result.ExitCode);
139+
}
140+
progress.WriteVerbose("Creating {0} ...", name);
141+
LfMergeBridge.LfMergeBridge.ReassembleFwdataFile(progress, writeVerbose: true, name);
142+
progress.WriteMessage("Created {0}", name);
143+
if (cleanup)
144+
{
145+
progress.WriteVerbose("Cleaning up...");
146+
HgRunner.Run($"hg checkout null", dir.FullName, timeout, progress);
147+
HgRunner.Run($"hg purge --no-confirm --exclude *.fwdata --exclude hgRunner.log", dir.FullName, timeout, progress);
148+
}
149+
return Task.FromResult(0);
150+
}
151+
}

0 commit comments

Comments
 (0)