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:
parent
ed947bf8e6
commit
d690632ca2
4 changed files with 62 additions and 27 deletions
79
over/app.py
79
over/app.py
|
@ -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
|
||||
|
||||
|
|
Loading…
Add table
Add a link
Reference in a new issue