generalized over.app.ConfigFile to be usable by other apps (e.g. for over-video's context storage, this closes #8 :)

options with no value return None instead of raising an exception
added a NOTE output tag to over.text.Output
This commit is contained in:
Martinez 2016-05-18 13:09:25 +02:00
parent ed947bf8e6
commit d690632ca2
4 changed files with 62 additions and 27 deletions

View file

@ -70,6 +70,13 @@ class OptionSyntaxError(Exception):
pass
class InternalError(Exception):
"""
A possible bug in over.
"""
pass
class IncompleteArguments(Exception):
@property
def description(self):
@ -101,7 +108,7 @@ class Option_sources(enum.Enum):
command_line = 3
class Option:
def __init__(self, name, description, callback, default=Option_sources.none, count=0, overwrite=True, abbr=None, in_cfg_file=True, show_in_help=True, is_boolean=None):
def __init__(self, name, description, callback, default=Option_sources.none, count=0, overwrite=True, abbr=None, in_cfg_file=True, in_help=True, is_boolean=None):
self.name = name
self.description = description
self.callback = callback
@ -110,7 +117,7 @@ class Option:
self.overwrite = overwrite
self.abbr = abbr
self.in_cfg_file = in_cfg_file
self.show_in_help = show_in_help
self.in_help = in_help
self.hash = hashlib.sha1(name.encode("utf-8")).hexdigest()
if is_boolean is None:
@ -165,7 +172,7 @@ class Option:
@property
def value(self):
if not self._value_list:
raise ReadingUnsetOption("option --%s has no value" %(self.name))
return None
else:
if self.overwrite:
return self._value_list[0]
@ -178,15 +185,20 @@ class OptionRouter:
def __init__(self, options):
self.options = options
def __getattr__(self, name):
def __getattr__(self, requested_name):
"""
@while retrieving an option's value
"""
try:
return self.options[name].value
except KeyError:
raise UnknownOption(name)
matches = [name for name in self.options if name.replace("-", "_") == requested_name]
if matches:
if len(matches) == 1:
return self.options[matches[0]].value
else:
raise InternalError("more than one option name matched")
else:
raise UnknownOption(requested_name)
# --------------------------------------------------
@ -277,21 +289,24 @@ class ConfigFile:
- missing options are appended to the file
"""
def __init__(self, options, app_name, app_version, force_path=None):
self.options = options
self.dir = get_xdg_paths(app_name)["config"]
self.path = force_path or os.path.join(self.dir, "main.cfg")
self.print = text.Output(app_name + ".ConfigFile")
self.seen_hashes = set()
def __init__(self, options, path):
"""
"""
self.read_config()
self.sync_config(app_name, app_version)
self.options = options
self.path = path
self.print = text.Output("over.app.ConfigFile")
self.seen_hashes = set()
def read_config(self):
"""
Reads the config file, updates self.options with values when available, and returns a set of updated options.
@while reading the config file
"""
updated = set()
if os.path.exists(self.path):
# read all options in the file
with open(self.path) as f:
@ -313,24 +328,32 @@ class ConfigFile:
args = shlex.split(R)
option.set_value(args, Option_sources.config_file)
updated.add(option)
return updated
def sync_config(self, app_name, app_version):
def update_config(self, header_template, header_args, new_options_commented=True):
"""
Creates the config file if necessary, adds new or missing options into it, and returns a set of newly added options.
@while updating the config file with new options
"""
# create the config dir
if not os.path.exists(self.dir):
self.print("created config directory <c>%s<.>" %(self.dir))
os.mkdir(self.dir)
config_dir = os.path.dirname(self.path)
if config_dir.strip() and not os.path.exists(config_dir):
os.mkdir(config_dir)
self.print("created config directory <c>%s<.>" %(config_dir))
# if the file doesn't exist, create it with a boilerplate header
if not os.path.exists(self.path):
with open(self.path, "w") as f:
f.write(docs.config_file_header %(app_name, app_version, version.str))
f.write(header_template %header_args)
self.print("created empty config file <c>%s<.>" %(self.path))
# add new or otherwise missing options
updated = set()
with open(self.path, "a") as f:
for option in self.options.values():
if option.hash not in self.seen_hashes and option.in_cfg_file:
@ -344,9 +367,14 @@ class ConfigFile:
"" if option.count == 1 else "s",
"the last instance counts" if option.overwrite else "can be specified multiple times",
format_description(option.description),
"#" if new_options_commented else "",
option.name,
serialize_default(option)
))
updated.add(option)
return updated
def __repr__(self):
return "ConfigFile(%s)" %(self.path)
@ -515,7 +543,7 @@ class Main:
print("[<W>Options<.>]")
for option in self.options.values():
if option.show_in_help:
if option.in_help:
# option name and type
full_names = ["<W>--<g>%s<.>" %(option.name)]
abbr_names = []
@ -568,7 +596,12 @@ class Main:
def parse_config(self, force_path=None):
if self.features.config_file:
self.config_file = ConfigFile(self.options, self.name, self.version, force_path)
config_dir = get_xdg_paths(self.name)["config"]
config_path = force_path or os.path.join(config_dir, "main.cfg")
self.config_file = ConfigFile(self.options, config_path)
self.config_file.read_config()
self.config_file.update_config(docs.config_file_header, (self.name, self.version, version.str))
else:
self.config_file = None