import os import tempfile from jw.pkg.lib.ProjectConf import ProjectConf # -- Helper: create temp file and read it -- _tmpfiles: list[str] = [] def _load(text: str) -> ProjectConf: with tempfile.NamedTemporaryFile(mode = 'w', suffix = '.conf', delete = False) as f: f.write(text) f.flush() _tmpfiles.append(f.name) return ProjectConf.read(f.name) def _cleanup(): for _p in _tmpfiles: os.unlink(_p) del _p _tmpfiles.clear() # -- Basic construction -- pc = _load('[section]\nkey = value\n') assert pc.get_str('section', 'key') == 'value' # -- get_section returns raw section text -- pc = _load('[meta]\n# header comment\nkey = val\n\n') section = pc.get_section('meta') assert section is not None assert '# header comment' in section assert 'key = val' in section # -- get_section returns None for missing section -- pc = _load('[section]\nkey = value\n') assert pc.get_section('nonexistent') is None # -- get_str returns unquoted value -- pc = _load('[section]\nkey = "hello world"\n') assert pc.get_str('section', 'key') == 'hello world' # -- get_str returns raw value when not quoted -- pc = _load('[section]\nkey = plain value\n') assert pc.get_str('section', 'key') == 'plain value' # -- get_str raises Error when key missing -- pc = _load('[section]\nother = val\n') try: pc.get_str('section', 'missing') assert False, 'Should have raised' except ProjectConf.Error: pass # -- get_str raises Error when section missing -- pc = _load('[section]\nkey = val\n') try: pc.get_str('nonexistent', 'key') assert False, 'Should have raised' except ProjectConf.Error: pass # -- get_str_or_none returns None for missing key -- pc = _load('[section]\nother = val\n') assert pc.get_str_or_none('section', 'missing') is None # -- get_str_or_none returns None for missing section -- pc = _load('[section]\nkey = val\n') assert pc.get_str_or_none('nonexistent', 'key') is None # -- get_str returns string with trailing whitespace stripped -- pc = _load('[section]\nkey = value \n') assert pc.get_str('section', 'key') == 'value' # -- Quoted value with spaces preserved -- pc = _load('[section]\nkey = " spaced "\n') assert pc.get_str('section', 'key') == ' spaced ' # -- Quoted hash inside quotes is preserved -- pc = _load('[section]\nkey = "value # not a comment"\n') assert pc.get_str('section', 'key') == 'value # not a comment' # -- Inline comment outside quotes -- pc = _load('[section]\nkey = value # this is a comment\n') assert pc.get_str('section', 'key') == 'value' # -- Quoted value containing an inline comment delimiter -- pc = _load('[section]\nkey = "has # inside"\n') assert pc.get_str('section', 'key') == 'has # inside' # -- get_list returns split values -- pc = _load('[section]\nlist = a, b, c\n') assert pc.get_list('section', 'list') == ['a', 'b', 'c'] # -- get_list returns single value -- pc = _load('[section]\nlist = single\n') assert pc.get_list('section', 'list') == ['single'] # -- get_list_or_none returns None for missing key -- pc = _load('[section]\nother = val\n') assert pc.get_list_or_none('section', 'missing') is None # -- get_list raises Error when missing key -- pc = _load('[section]\nother = val\n') try: pc.get_list('section', 'missing') assert False, 'Should have raised' except ProjectConf.Error: pass # -- get_list handles quoted commas -- pc = _load('[section]\nlist = "a, b", c, "d, e"\n') assert pc.get_list('section', 'list') == ['a, b', 'c', 'd, e'] # -- get_list returns empty list for empty value -- pc = _load('[section]\nlist = \n') assert pc.get_list('section', 'list') == [] # -- get_list_or_none returns empty list for empty value -- pc = _load('[section]\nlist = \n') assert pc.get_list_or_none('section', 'list') == [] # -- Unclosed quote in list raises -- pc = _load('[section]\nlist = "unclosed, b\n') try: pc.get_list('section', 'list') assert False, 'Should have raised' except ProjectConf.Error as exc: assert 'Unclosed quote' in str(exc) # -- Line continuation -- pc = _load('[section]\nkey = first \\\n second \\\n third\n') assert pc.get_str('section', 'key') == 'first second third' # -- Line continuation with inline comment at end -- pc = _load('[section]\nkey = first \\\n second # comment\n') assert pc.get_str('section', 'key') == 'first second' # -- get_section returns None for non-existent section -- pc = _load('[section]\nkey = val\n') assert pc.get_section('does_not_exist') is None # -- Multiple keys in same section -- pc = _load('[section]\nfirst = one\nsecond = two\nthird = three\n') assert pc.get_str('section', 'first') == 'one' assert pc.get_str('section', 'second') == 'two' assert pc.get_str('section', 'third') == 'three' # -- get_str_or_none does not raise for missing section -- pc = _load('[section]\nkey = val\n') assert pc.get_str_or_none('nope', 'key') is None # -- get_list_or_none does not raise for missing section -- pc = _load('[section]\nkey = val\n') assert pc.get_list_or_none('nope', 'key') is None # -- get_list_or_none does not raise for missing key -- pc = _load('[section]\nother = val\n') assert pc.get_list_or_none('section', 'nope') is None # -- Empty key raises -- pc = _load('[section]\n= value\n') try: pc.get_str('section', '') assert False, 'Should have raised' except ProjectConf.Error: pass # -- Not an assignment raises -- pc = _load('[section]\nnot an assignment\n') try: pc.get_str('section', 'not an assignment') assert False, 'Should have raised' except ProjectConf.Error: pass # -- Unfinished continuation raises -- pc = _load('[section]\nkey = value \\\n') try: pc.get_str('section', 'key') assert False, 'Should have raised' except ProjectConf.Error: pass # -- Error class is ValueError -- pc = _load('[section]\nkey = val\n') try: pc.get_str('missing', 'key') except ValueError as e: assert isinstance(e, ValueError) else: assert False, 'Should have raised ValueError' # -- Whitespace around key and value -- pc = _load('[section]\n key = value \n') assert pc.get_str('section', 'key') == 'value' # -- Whitespace around key in list -- pc = _load('[section]\nlist = a , b , c \n') assert pc.get_list('section', 'list') == ['a', 'b', 'c'] # -- Multiple sections -- pc = _load('[one]\nkey = val1\n[two]\nkey = val2\n') assert pc.get_str('one', 'key') == 'val1' assert pc.get_str('two', 'key') == 'val2' # -- Section with only comments and blanks -- pc = _load('[empty]\n# just a comment\n\n') section = pc.get_section('empty') assert section is not None assert pc.get_str_or_none('empty', 'key') is None # -- Section with trailing comment on section header line -- pc = _load('[section] # inline comment\nkey = val\n') assert pc.get_str('section', 'key') == 'val' # -- Value that looks like a section header -- pc = _load('[section]\nnot_a_key = [bracket]\n') assert pc.get_str('section', 'not_a_key') == '[bracket]' # -- Multiple values with trailing backslash comments -- pc = _load('[section]\nlist = "a", "b", "c"\n') assert pc.get_list('section', 'list') == ['a', 'b', 'c'] _cleanup() print('All ProjectConf tests passed')