OK, here's where I leave this for while: input format, folder structure, and a code skeleton that works... for everything except the actual merge bit. You may want to expand the information passed through the functions to the merge logic, I kept it minimal so it doesn't seem to even hold folder variables.
Download link.
import os
import shutil
import filecmp
# path from script to mods folder
mods_folder = 'LNP/Mods/'
mod_folders_list = []
for mod in os.listdir(mods_folder):
if mod.startswith('df_'):
# there must be only one folder beginning 'df_', the basis of comparison
vanilla_folder = mods_folder + mod
vanilla_raw_folder = vanilla_folder + '/raw/'
elif mod == 'temp':
pass
else:
mod_folders_list.append(mod)
def simplify_mod_and_df_folders():
for mod in os.listdir(mods_folder):
files_removed = 0
folders_removed = 0
for item in os.listdir(mods_folder + mod):
# delete anything top-level not containing string 'raw', 'readme', or 'config'
if item == 'raw':
pass
elif 'readme' in item.lower():
pass
elif 'config' in item.lower():
pass
elif os.path.isfile(mods_folder + mod + '/' + item):
os.remove(mods_folder + mod + '/' + item)
files_removed += 1
else:
shutil.rmtree(mods_folder + mod + '/' + item)
folders_removed += 1
if not files_removed + folders_removed == 0:
print(mod, 'folder simplified! (removed', files_removed, 'files and', folders_removed, 'folders)')
def remove_vanilla_files_from_mod_raws_and_add_blanks_to_preserve_deletions_in_mod():
for mod in mod_folders_list:
mod_raw_folder = mods_folder + mod + '/raw/'
files_removed = 0
blank_files_added = 0
# add a blank file where the mod does not have a vanilla file
for file_tuple in os.walk(vanilla_raw_folder):
for item in file_tuple[2]:
item_path_str = os.path.join(file_tuple[0], item).replace('\\', '/').replace(vanilla_raw_folder, '')
if not os.path.isfile(mod_raw_folder + item_path_str):
open(mod_raw_folder + item_path_str, a).close()
blank_files_added += 1
# remove files identical to vanilla raws
for file_tuple in os.walk(mod_raw_folder):
for item in file_tuple[2]:
item_path_str = os.path.join(file_tuple[0], item).replace('\\', '/').replace(mod_raw_folder, '')
if os.path.isfile(vanilla_raw_folder + item_path_str): # if the file exists in the vanilla raws
if filecmp.cmp(mod_raw_folder + item_path_str, vanilla_raw_folder + item_path_str): # and it's the same file
os.remove(mod_raw_folder + item_path_str)
files_removed += 1
if blank_files_added > 0:
print('\n' + mod + ':\n removed', blank_files_added, 'to preserve files removed compared to vanilla raws')
if files_removed > 0:
print('\n' + mod + ':\n removed', files_removed, 'files identical to vanilla raws')
def make_raws_with_mods():
print('What mods do you want to load?')
for mod in mod_folders_list:
print(' ', mod_folders_list.index(mod), mod)
mods_to_load = input('Enter the indicies of the mods to load, in order, seperated by spaces:\n ')
mods_to_load = mods_to_load.split(' ')
mod_load_order = []
for index in mods_to_load:
mod_load_order.append(mod_folders_list[int(index)])
mixed_raws_folder = mods_folder + 'temp/raw/'
# remove an old folder if exists. Name reserved for this reason!
if os.path.exists(mixed_raws_folder):
if os.path.isfile(mixed_raws_folder):
os.remove(mixed_raws_folder)
else:
shutil.rmtree(mixed_raws_folder)
# create folder of vanilla raws to operate on
shutil.copytree(vanilla_raw_folder, mixed_raws_folder)
print('\nFolder for merging created - "'+mixed_raws_folder+'" - with vanilla raws.')
# start merging mods!
merge_next_mod('', mod_load_order, -1)
# get back after looping through
print('\nMod merging complete! The merged mods can be found in the mod folder as "temp".')
def merge_next_mod(next_mod, mod_load_order, mods_merged):
if not next_mod == '':
mod_merge_logic(next_mod, mods_merged)
if not mod_load_order == []:
next_mod = mod_load_order.pop(0)
mods_merged += 1
merge_next_mod(next_mod, mod_load_order, mods_merged)
def mod_merge_logic(mod, mods_merged):
print('\n(placeholder merge logic for', mod + '; no action taken;', mods_merged, 'mods merged already)')
# see eg vanilla file removal function to do per-file comparisons
simplify_mod_and_df_folders()
remove_vanilla_files_from_mod_raws_and_add_blanks_to_preserve_deletions_in_mod()
make_raws_with_mods()
I started working on a script to
A)Identify conflicting mod files based on a diff
B)Merge non-conflicting files.
The intent is to be two function that work on multiple versions of the same file.
A simple implementation of this would be to implement A as assuming any two non identical files are conflicting and B as moving the mod file to the generated mod. My script would be a drop in replacement for that.
If you start with the code above, you can just fill out the function "def mod_merge_logic(mod, mods_merged):" - which was the idea of posting it
However I think that passing arguments to these functions may actually be counterproductive, as you lose all the other variables that have been set up (like, eg, paths and number of mods already merged).
I plan on (sometime soonish I hope) fixing that, adding useful comments before that function explaining the variables you can use, and adding a flatten_raws function somewhere.
testbed mods
I just visited DFFD for a couple of mods, I think Rise of the Mushroom Kingdom as a major mod, Accelerated Modest Mod in the middle, and Eevee Fortress which just adds them as a playable civ. As noted above some mods require installation over vanilla before they're usable, but that's not much of a challenge.
manifest
Reading everything relevant to merge logic from the content is the plan, but an optional manifest is still nice for stuff like displaying the author and an update link, maybe a one-sentence summary of the mod, that kind of thing. Some fields can be autofilled from the raws / folder names / etc, but others need a human to write them.