#!/usr/bin/env python3 # Part of JoeyLib, Copyright (c) 2026 Scott Duensing, under the MIT License. # See LICENSE.md for license information. # SPDX-License-Identifier: MIT """winIni.py - edit a Windows 3.x .INI file in place, CRLF line ends kept. winIni.py FILE OP SECTION KEY VALUE [OP SECTION KEY VALUE ...] OP is one of: set KEY=VALUE in SECTION: the first KEY line there is replaced (keys and sections match without regard to case), else the line is added at the end of the section, the section at the end of the file if it is not there. add another KEY=VALUE line in SECTION unless that exact line is there already: for the keys Windows reads every line of (device= in [386Enh]). Used by the installer's win311 component (installer/lib/win16.sh) to set up Windows the way its Setup and Control Panel would, and by the win16 gates and joey-run to name the program Windows runs as its shell. """ import sys ENCODING = 'latin-1' NEWLINE = '\r\n' OPS = ('set', 'add') GROUP = 4 def sectionBounds(lines, section): # The index of SECTION's header and of the line after its last, or None. header = '[' + section.lower() + ']' for start, line in enumerate(lines): if line.strip().lower() == header: end = start + 1 while end < len(lines) and not lines[end].lstrip().startswith('['): end += 1 return start, end return None def insertAtEnd(lines, start, end, line): # Before the blank lines that close the section. at = end while at > start + 1 and lines[at - 1].strip() == '': at -= 1 lines.insert(at, line) def keyOf(line): return line.split('=', 1)[0].strip().lower() if '=' in line else None def apply(lines, op, section, key, value): line = key + '=' + value bounds = sectionBounds(lines, section) if bounds is None: while lines and lines[-1].strip() == '': lines.pop() lines.extend(['', '[' + section + ']', line, '']) return start, end = bounds for i in range(start + 1, end): if op == 'set' and keyOf(lines[i]) == key.lower(): lines[i] = line return if op == 'add' and lines[i].strip().lower() == line.lower(): return insertAtEnd(lines, start, end, line) def main(argv): if len(argv) < 2 + GROUP or (len(argv) - 2) % GROUP != 0: sys.exit(__doc__) path = argv[1] lines = open(path, 'rb').read().decode(ENCODING).split(NEWLINE) for at in range(2, len(argv), GROUP): op, section, key, value = argv[at:at + GROUP] if op not in OPS: sys.exit('winIni.py: unknown op ' + op) apply(lines, op, section, key, value) open(path, 'wb').write(NEWLINE.join(lines).encode(ENCODING)) if __name__ == '__main__': main(sys.argv)