Skip to content

Commit 0da1c98

Browse files
committed
fix: ensure options are retrieved from .ini file
Some boolean options, when not specified, default to "False", which overwrites "True" if set in the .INI file. Now they defaults to None (null), and won't overwrite previous setting.
1 parent 0205ea7 commit 0da1c98

File tree

4 files changed

+65
-18
lines changed

4 files changed

+65
-18
lines changed

src/api/config.py

Lines changed: 20 additions & 14 deletions
Original file line numberDiff line numberDiff line change
@@ -12,26 +12,32 @@
1212
import os
1313
import sys
1414
import configparser
15+
import enum
1516

16-
from src import api
17+
from enum import Enum
18+
from typing import Dict, Callable
19+
from src.api import errmsg
1720

1821
# The options container
19-
from . import options
20-
from . import global_
2122

22-
from .options import ANYTYPE, Action
23+
from src.api import options
24+
from src.api import global_
25+
26+
from src.api.options import ANYTYPE, Action
2327

2428

2529
# ------------------------------------------------------
2630
# Common setup and configuration for all tools
2731
# ------------------------------------------------------
28-
class ConfigSections:
32+
@enum.unique
33+
class ConfigSections(str, Enum):
2934
ZXBC = 'zxbc'
3035
ZXBASM = 'zxbasm'
3136
ZXBPP = 'zxbpp'
3237

3338

34-
class OPTION:
39+
@enum.unique
40+
class OPTION(str, Enum):
3541
OUTPUT_FILENAME = 'output_filename'
3642
INPUT_FILENAME = 'input_filename'
3743
STDERR_FILENAME = 'stderr_filename'
@@ -96,23 +102,23 @@ def load_config_from_file(filename: str, section: str, options_: options.Options
96102
cfg = configparser.ConfigParser()
97103
cfg.read(filename, encoding='utf-8')
98104
except (configparser.DuplicateSectionError, configparser.DuplicateOptionError):
99-
api.errmsg.msg_output(f"Invalid config file '{filename}': it has duplicated fields")
105+
errmsg.msg_output(f"Invalid config file '{filename}': it has duplicated fields")
100106
if stop_on_error:
101107
sys.exit(1)
102108
return False
103109
except FileNotFoundError:
104-
api.errmsg.msg_output(f"Config file '{filename}' not found")
110+
errmsg.msg_output(f"Config file '{filename}' not found")
105111
if stop_on_error:
106112
sys.exit(1)
107113
return False
108114

109115
if section not in cfg.sections():
110-
api.errmsg.msg_output(f"Section '{section}' not found in config file '{filename}'")
116+
errmsg.msg_output(f"Section '{section}' not found in config file '{filename}'")
111117
if stop_on_error:
112118
sys.exit(1)
113119
return False
114120

115-
parsing = {
121+
parsing: Dict[type, Callable] = {
116122
int: cfg.getint,
117123
float: cfg.getfloat,
118124
bool: cfg.getboolean
@@ -137,7 +143,7 @@ def save_config_into_file(filename: str, section: str, options_: options.Options
137143
try:
138144
cfg.read(filename, encoding='utf-8')
139145
except (configparser.DuplicateSectionError, configparser.DuplicateOptionError):
140-
api.errmsg.msg_output(f"Invalid config file '{filename}': it has duplicated fields")
146+
errmsg.msg_output(f"Invalid config file '{filename}': it has duplicated fields")
141147
if stop_on_error:
142148
sys.exit(1)
143149
return False
@@ -157,7 +163,7 @@ def save_config_into_file(filename: str, section: str, options_: options.Options
157163
with open(filename, 'wt', encoding='utf-8') as f:
158164
cfg.write(f)
159165
except IOError:
160-
api.errmsg.msg_output(f"Can't write config file '{filename}'")
166+
errmsg.msg_output(f"Can't write config file '{filename}'")
161167
if stop_on_error:
162168
sys.exit(1)
163169
return False
@@ -189,10 +195,10 @@ def init():
189195
OPTIONS(Action.ADD, name=OPTION.MEMORY_MAP, type=str, default=None, ignore_none=True)
190196
OPTIONS(Action.ADD, name=OPTION.FORCE_ASM_BRACKET, type=bool, default=False, ignore_none=True)
191197

192-
OPTIONS(Action.ADD, name=OPTION.USE_BASIC_LOADER, type=bool, default=False) # Whether to use a loader
198+
OPTIONS(Action.ADD, name=OPTION.USE_BASIC_LOADER, type=bool, default=False, ignore_none=True)
193199

194200
# Whether to add autostart code (needs basic loader = true)
195-
OPTIONS(Action.ADD, name=OPTION.AUTORUN, type=bool, default=False)
201+
OPTIONS(Action.ADD, name=OPTION.AUTORUN, type=bool, default=False, ignore_none=True)
196202
OPTIONS(Action.ADD, name=OPTION.OUTPUT_FILE_TYPE, type=str, default='bin') # bin, tap, tzx etc...
197203
OPTIONS(Action.ADD, name=OPTION.INCLUDE_PATH, type=str, default='') # Include path, like '/var/lib:/var/include'
198204

src/zxbc/args_parser.py

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -42,9 +42,9 @@ def parser() -> argparse.ArgumentParser:
4242
output_file_type_group.add_argument('--parse-only', action='store_true',
4343
help='Only parses to check for syntax and semantic errors')
4444

45-
parser_.add_argument('-B', '--BASIC', action='store_true', dest='basic',
45+
parser_.add_argument('-B', '--BASIC', action='store_true', dest='basic', default=None,
4646
help="Creates a BASIC loader which loads the rest of the CODE. Requires -T ot -t")
47-
parser_.add_argument('-a', '--autorun', action='store_true',
47+
parser_.add_argument('-a', '--autorun', action='store_true', default=None,
4848
help="Sets the program to be run once loaded")
4949

5050
parser_.add_argument('-S', '--org', type=str,

tests/api/test_arg_parser.py

Lines changed: 26 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,26 @@
1+
import unittest
2+
3+
from src.zxbc.args_parser import parser
4+
5+
6+
class TestArgParser(unittest.TestCase):
7+
""" Test argument options from the cmdline
8+
"""
9+
def setUp(self):
10+
self.parser = parser()
11+
12+
def test_autorun_defaults_to_none(self):
13+
""" Some boolean options, when not specified from the command line
14+
must return None (null) instead of False to preserve .INI saved
15+
value.
16+
"""
17+
options = self.parser.parse_args(["test.bas"])
18+
self.assertIsNone(options.autorun)
19+
20+
def test_loader_defaults_to_none(self):
21+
""" Some boolean options, when not specified from the command line
22+
must return None (null) instead of False to preserve .INI saved
23+
value.
24+
"""
25+
options = self.parser.parse_args(["test.bas"])
26+
self.assertIsNone(options.basic)

tests/api/test_config.py

Lines changed: 17 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -12,9 +12,9 @@ class TestConfig(unittest.TestCase):
1212
"""
1313
def setUp(self):
1414
config.OPTIONS(config.Action.CLEAR)
15+
config.init()
1516

1617
def test_init(self):
17-
config.init()
1818
self.assertEqual(config.OPTIONS.debug_level, 0)
1919
self.assertEqual(config.OPTIONS.stdin, sys.stdin)
2020
self.assertEqual(config.OPTIONS.stdout, sys.stdout)
@@ -46,7 +46,6 @@ def test_init(self):
4646
self.assertEqual(config.OPTIONS.strict, False)
4747

4848
def test_initted_values(self):
49-
config.init()
5049
self.assertEqual(sorted(config.OPTIONS._options.keys()), [
5150
'__DEFINES',
5251
config.OPTION.ARCH,
@@ -82,3 +81,19 @@ def test_initted_values(self):
8281
config.OPTION.USE_BASIC_LOADER,
8382
config.OPTION.ASM_ZXNEXT
8483
])
84+
85+
def test_loader_ignore_none(self):
86+
""" Some settings must ignore "None" assignments, since
87+
this means the user didn't specify anything from the command line
88+
"""
89+
config.OPTIONS.use_basic_loader = True
90+
config.OPTIONS.use_basic_loader = None
91+
self.assertEqual(config.OPTIONS.use_basic_loader, True)
92+
93+
def test_autorun_ignore_none(self):
94+
""" Some settings must ignore "None" assignments, since
95+
this means the user didn't specify anything from the command line
96+
"""
97+
config.OPTIONS.autorun = True
98+
config.OPTIONS.autorun = None
99+
self.assertEqual(config.OPTIONS.autorun, True)

0 commit comments

Comments
 (0)