diff --git a/diff-text/diff-text.csproj b/diff-text/diff-text.csproj
index 1eb1248..7461e1c 100644
--- a/diff-text/diff-text.csproj
+++ b/diff-text/diff-text.csproj
@@ -16,6 +16,6 @@
-
+
diff --git a/paradox-mod-merger/Diff.cs b/paradox-mod-merger/Diff.cs
index a8d4ef4..f4f01da 100644
--- a/paradox-mod-merger/Diff.cs
+++ b/paradox-mod-merger/Diff.cs
@@ -7,14 +7,13 @@ using DTLib.Ben.Demystifier;
namespace ParadoxModMerger;
-public record struct ConflictingModFile(string FilePath, string[] Mods);
-
public enum DiffState
{
Added, Equal, Removed, Changed
}
public record struct DiffPart(T Value, DiffState State);
+public record struct ConflictingModFile(string FilePath, string[] Mods);
static class Diff
{
@@ -23,7 +22,7 @@ static class Diff
public static void DiffCommandHandler(string connected_pathes)
{
- IOPath[] moddirs = Program.SplitStringToPaths(connected_pathes);
+ IOPath[] moddirs = Program.SplitArgToPaths(connected_pathes, false);
var conflicts = FindModConflicts(moddirs);
LogModConflicts(conflicts);
}
@@ -148,14 +147,14 @@ static class Diff
case ConsoleKey.LeftArrow:
{
ColoredConsole.Write("w", "enter left mod number: ");
- string answ = Console.ReadLine();
+ string answ = Console.ReadLine()!;
selected_mod0_i = answ.ToInt();
break;
}
case ConsoleKey.RightArrow:
{
ColoredConsole.Write("w", "enter right mod number: ");
- string answ = Console.ReadLine();
+ string answ = Console.ReadLine()!;
selected_mod1_i = answ.ToInt();
break;
}
@@ -194,7 +193,7 @@ static class Diff
public static IEnumerable> DiffCollections(ICollection col0, ICollection col1)
{
- foreach (T el in col0)
+ foreach (var el in col0)
yield return new DiffPart(el, col1.Contains(el) ? DiffState.Equal : DiffState.Removed);
foreach (var el in col1)
if (!col0.Contains(el))
diff --git a/paradox-mod-merger/Localisation.cs b/paradox-mod-merger/Localisation.cs
index 513bbf7..26e2212 100644
--- a/paradox-mod-merger/Localisation.cs
+++ b/paradox-mod-merger/Localisation.cs
@@ -23,4 +23,52 @@ static class Localisation
else Log("y", $"file {rusFileName} already exists");
}
}
+
+ // deletes all localisations except l_russian and l_english
+ public static void Clean(IOPath _loc_dir)
+ {
+ Log("g", $"deleted {RemoveUnneededDirs(_loc_dir)} dirs");
+ Log("g", $"deleted {RemoveUnneededFiles(_loc_dir)} files");
+
+
+ int RemoveUnneededDirs(IOPath loc_dir)
+ {
+ int count = 0;
+ foreach (var subdir in Directory.GetDirectories(loc_dir))
+ {
+ string dir_basename = subdir.LastName().Str;
+ if (dir_basename == "russian" || dir_basename == "english")
+ continue;
+
+ if (dir_basename == "replace")
+ {
+ RemoveUnneededDirs(subdir);
+ RemoveUnneededFiles(subdir);
+ continue;
+ }
+
+ if (dir_basename.Contains("rus"))
+ Log("y", $"unexpected dir: {subdir}");
+ Directory.Delete(subdir);
+ count++;
+ }
+
+ return count;
+ }
+
+ int RemoveUnneededFiles(IOPath loc_dir)
+ {
+ int count = 0;
+ foreach (var file in Directory.GetFiles(loc_dir))
+ {
+ if(file.EndsWith("l_russian") || file.EndsWith("l_enghish"))
+ continue;
+ if (!file.Contains("_l_") || !file.EndsWith(".yml"))
+ Log("y",$"unexpected file: {file}");
+ File.Delete(file);
+ count++;
+ }
+ return count;
+ }
+ }
}
\ No newline at end of file
diff --git a/paradox-mod-merger/Merge.cs b/paradox-mod-merger/Merge.cs
index 3b13225..2f9d19d 100644
--- a/paradox-mod-merger/Merge.cs
+++ b/paradox-mod-merger/Merge.cs
@@ -56,18 +56,22 @@ static class Merge
File.AppendAllText(out_modlist_file, $"{moddir.LastName()}\n");
}
- public static void ConsoleAskYN(string question, Action yes, Action no)
+ public static void ConsoleAskYN(string question, Action? yes, Action? no)
{
Log("y", question + " [y/n]");
- string answ = ColoredConsole.Read("w");
- if (answ == "y") yes();
- else no();
+ string answ = ColoredConsole.Read("w").ToLower();
+ if (answ == "y") yes?.Invoke();
+ else no?.Invoke();
}
static void HandleConflicts(IOPath[] moddirs)
{
var conflicts = Diff.FindModConflicts(moddirs);
- if (conflicts.Count <= 0) return;
+ conflicts = conflicts.Where(cfl =>
+ !cfl.FilePath.EndsWith("descriptor.mod") &&
+ !cfl.FilePath.EndsWith(modlist_filename)).ToList();
+ if (conflicts.Count < 1)
+ return;
Diff.LogModConflicts(conflicts);
ConsoleAskYN("continue merge?",
@@ -80,4 +84,117 @@ static class Merge
() => {});
});
}
+
+
+ public static void UpdateMods(IOPath updated_mods_dir, IOPath[] outdated_dirs, IOPath backup_dir)
+ {
+ var src_dir_mods = Directory.GetDirectories(updated_mods_dir).ToList();
+ List not_found_mods = new List();
+ List changed_mods = new List();
+ List unchanged_mods = new List();
+
+ foreach (IOPath outdated_mods_dir in outdated_dirs)
+ foreach (var mod in Directory.GetDirectories(outdated_mods_dir))
+ {
+ Log("b", "updating mod ", "c", mod.LastName().Str);
+ var mod_updated = mod.ReplaceBase(outdated_mods_dir, updated_mods_dir);
+ int updated_index = src_dir_mods.IndexOf(mod_updated); // IOPath comparison doesnt work?
+
+ if (updated_index == -1)
+ {
+ Log("m", $"mod {mod.LastName()} not found in {updated_mods_dir}");
+ not_found_mods.Add(mod);
+ continue;
+ }
+
+ var diff = Diff.DiffDirs(mod, mod_updated)
+ .Where(d => d.State != DiffState.Equal)
+ .ToList();
+ if (!diff.Any())
+ {
+ Log("gray","unchanged");
+ unchanged_mods.Add(mod);
+ continue;
+ }
+
+ Log("file difference:");
+ foreach (var fileD in diff)
+ {
+ string color = fileD.State switch
+ {
+ DiffState.Added => "g",
+ DiffState.Changed => "y",
+ DiffState.Removed => "m",
+ _ => throw new ArgumentOutOfRangeException()
+ };
+ Log(color, fileD.Value.Str);
+ }
+
+ ConsoleAskYN("replace mod with its updated version?",
+ () =>
+ {
+ var mod_backup = Path.Concat(backup_dir, mod.LastName());
+ Log($"moving {mod} to {backup_dir}");
+ Directory.Move(mod, mod_backup, false);
+ Log($"copying {mod_updated} to {outdated_mods_dir}");
+ Directory.Copy(mod_updated, mod, false);
+ Log("g", $"mod {mod.LastName()} updated");
+ changed_mods.Add(mod);
+ },
+ ()=>unchanged_mods.Add(mod));
+ }
+
+ List added_mods = new List(src_dir_mods.Count - changed_mods.Count);
+ var found_mods = Enumerable
+ .Concat(changed_mods, unchanged_mods)
+ .Select(m=>Path.Concat(updated_mods_dir, m.LastName()))
+ .ToList();
+ foreach (var modD in Diff.DiffCollections(found_mods, src_dir_mods))
+ {
+ if (modD.State == DiffState.Added)
+ added_mods.Add(modD.Value);
+ }
+
+ Log("b", "mod update summary:");
+ if (added_mods.Count > 0)
+ Log("w", $"added {added_mods.Count}:\n",
+ "g", added_mods.MergeToString('\n'));
+ if (changed_mods.Count > 0)
+ Log("w", $"changed {changed_mods.Count}:\n",
+ "y", changed_mods.MergeToString('\n'));
+ if (not_found_mods.Count > 0)
+ Log("w", $"not found {not_found_mods.Count}:\n",
+ "m", not_found_mods.MergeToString('\n'));
+ if (unchanged_mods.Count>0)
+ Log("w", $"unchanged {unchanged_mods.Count}");
+ }
+
+ public static void RenameModsCommandHandler(string dir_with_mods, string rename_pairs_str)
+ {
+ string[] split = Program.SplitArgToStrings(rename_pairs_str, false);
+ int rename_pairs_length = split.Length / 2;
+ if (split.Length % 2 != 0)
+ throw new Exception($"rename_pairs length is not even ({rename_pairs_length})");
+ var rename_pairs = new (string old_name, string new_name)[rename_pairs_length];
+ for (int i = 0; i < rename_pairs_length; i++)
+ {
+ rename_pairs[i] = (split[i * 2], split[i * 2 + 1]);
+ }
+
+ RenameMods(dir_with_mods, rename_pairs);
+ }
+
+ public static void RenameMods(IOPath dir_with_mods, (string old_name,string new_name)[] rename_pairs)
+ {
+ foreach (var re in rename_pairs)
+ {
+ var old_mod_path = Path.Concat(dir_with_mods, re.old_name);
+ var new_mod_path = Path.Concat(dir_with_mods, re.new_name);
+ if (Directory.Exists(old_mod_path))
+ {
+ Log("b","renaming ", "c", re.old_name, "b", " to ", "c", re.new_name);
+ Directory.Move(old_mod_path, new_mod_path, false);
+ }
+ }
+ }
}
\ No newline at end of file
diff --git a/paradox-mod-merger/Program.cs b/paradox-mod-merger/Program.cs
index 8e2b473..aa43e86 100644
--- a/paradox-mod-merger/Program.cs
+++ b/paradox-mod-merger/Program.cs
@@ -6,6 +6,8 @@ global using DTLib;
global using DTLib.Extensions;
global using DTLib.Filesystem;
global using DTLib.Logging;
+using System.Linq;
+using System.Security.Cryptography;
using DTLib.Console;
namespace ParadoxModMerger;
@@ -15,7 +17,7 @@ public static class Program
static ConsoleLogger logger = new($"logs", "main");
static void Log(params string[] msg) => logger.Log(msg);
- static void Main(string[] args)
+ static int Main(string[] args)
{
try
{
@@ -25,47 +27,62 @@ public static class Program
string outPath = "" ;
new LaunchArgumentParser(
- new LaunchArgument(new []{"o", "out"},
- "sets output path",
- p => outPath=p,
- "out_path",
- 0),
- new LaunchArgument(new []{"clear"},
- "Clear mod files and put them into separate dirs in output dir. Requires -o",
- wdir=>Workshop.ClearWorkshop(wdir, outPath),
- "workshop_dir",
- 1),
- new LaunchArgument(new []{"diff"},
- "Compares mod files by hash",
- p=>Diff.DiffCommandHandler(p),
- "first_mod_directory:second_mod_directory:...",
- 1),
- new LaunchArgument(new []{"diff-detailed"},
- "reads conflicts_XXX.dtsod file and shows text diff for each file",
- p=>Diff.DiffDetailedCommandHandler(p),
- "conflicts_dtsod_path",
- 1
- ),
- new LaunchArgument(new []{"merge-subdirs"},
- "Merges mods and shows conflicts. Requires -o",
- d => Merge.MergeAll(Directory.GetDirectories(d), outPath),
- "dir_with_mods",
- 1),
- new LaunchArgument(new []{"merge-into", "merge-single"},
- "Merges one mod into output dir and shows conflicts. Requires -o",
- mod=>Merge.MergeInto(mod, outPath),
- "mod_dir",
- 1),
- new LaunchArgument(new []{"gen-rus-locale"},
- "Creates l_russian copy of english locale in output directory. Requires -o",
- eng=>Localisation.GenerateRussian(eng, outPath),
- "english_locale_path",
- 1),
- new LaunchArgument(new []{"desc"},
- "Downloads mod description from steam to new file in outDir. Requires -o",
- id=>Workshop.CreateDescFile(id, outPath).GetAwaiter().GetResult(),
- "mod_id",
- 1)
+ new LaunchArgument(new []{"o", "out"},
+ "sets output path",
+ p => outPath=p,
+ "out_path",
+ 0),
+ new LaunchArgument(new []{"clear"},
+ "Clear mod files and put them into separate dirs in output dir. Requires -o",
+ wdir=>Workshop.ClearWorkshop(wdir, outPath),
+ "workshop_dir",
+ 1),
+ new LaunchArgument(new []{"diff"},
+ "Compares mod files by hash",
+ p=>Diff.DiffCommandHandler(p),
+ "first_mod_directory:second_mod_directory:...",
+ 1),
+ new LaunchArgument(new []{"diff-detailed"},
+ "reads conflicts_XXX.dtsod file and shows text diff for each file",
+ p=>Diff.DiffDetailedCommandHandler(p),
+ "conflicts_dtsod_path",
+ 1),
+ new LaunchArgument(new []{"merge-subdirs"},
+ "Merges mods and shows conflicts. Requires -o",
+ d => Merge.MergeAll(Directory.GetDirectories(d), outPath),
+ "dir_with_mods",
+ 1),
+ new LaunchArgument(new []{"merge-into", "merge-single"},
+ "Merges one mod into output dir and shows conflicts. Requires -o",
+ mod=>Merge.MergeInto(mod, outPath),
+ "mod_dir",
+ 1),
+ new LaunchArgument(new []{"gen-rus-locale"},
+ "Creates l_russian copy of english locale in output directory. Requires -o",
+ eng=>Localisation.GenerateRussian(eng, outPath),
+ "english_locale_path",
+ 1),
+ new LaunchArgument(new []{"desc"},
+ "Downloads mod description from steam to new file in outDir. Requires -o",
+ id=>Workshop.CreateDescFile(id, outPath).GetAwaiter().GetResult(),
+ "mod_id",
+ 1),
+ new LaunchArgument(new []{"rename"},
+ "Renames mods in directory",
+ (modsdir, replace_pairs)=>Merge.RenameModsCommandHandler(modsdir, replace_pairs),
+ "dir_with_mods", "replace_pairs (old_name:new_name:...)",
+ 1),
+ new LaunchArgument(new []{"update-mods"},
+ "Updates mods in [outdated_dir0...outdated_dirN] to new versions if found in updated_mods_dir. " +
+ "Moves old mods to backup_dir defined by -o.",
+ (updated, outdated)=>Merge.UpdateMods(updated, SplitArgToPaths(outdated, true), outPath),
+ "updated_mods_dir", "outdated_dir OR outdated_dir0:...:outdated_dirN",
+ 1),
+ new LaunchArgument(new []{"clean-locales"},
+ "Deletes all localisations except l_russian and l_english.",
+ locdir=> Localisation.Clean(locdir),
+ "localisation_dir",
+ 1)
).ParseAndHandle(args);
}
catch (LaunchArgumentParser.ExitAfterHelpException)
@@ -73,23 +90,27 @@ public static class Program
catch (Exception ex)
{
Log("r", DTLib.Ben.Demystifier.ExceptionExtensions.ToStringDemystified(ex));
+ Console.ResetColor();
+ return 1;
}
- Console.ResetColor();
+
+ return 0;
}
- public static IOPath[] SplitStringToPaths(string connected_paths)
+ public static string[] SplitArgToStrings(string connected_parts, bool allow_one_part)
{
char part_sep;
- if (connected_paths.Contains(':'))
+ if (connected_parts.Contains(':'))
part_sep = ':';
- else if (!connected_paths.Contains(':'))
+ else if (connected_parts.Contains(';'))
part_sep = ';';
- else throw new Exception($"<{connected_paths}> doesn't contain any separators (:/;)");
-
- string[] split = connected_paths.Split(part_sep);
- IOPath[] split_iop = new IOPath[split.Length];
- for (int i = 0; i < split.Length; i++)
- split_iop[i] = new IOPath(split[i]);
- return split_iop;
+ else if (allow_one_part)
+ return new []{connected_parts};
+ else throw new Exception($"<{connected_parts}> doesn't contain any separators (:/;)");
+
+ return connected_parts.Split(part_sep);
}
+
+ public static IOPath[] SplitArgToPaths(string connected_parts, bool allow_one_part) =>
+ IOPath.ArrayCast(SplitArgToStrings(connected_parts, allow_one_part));
}
\ No newline at end of file