config file support is ready
- over.app.ConfigFile can read, create and amend config files - fix over.cmd.format_invocation - add join to over.text.paragraph
This commit is contained in:
parent
17eda81ad2
commit
5b983a3131
6 changed files with 81 additions and 19 deletions
52
over/app.py
52
over/app.py
|
@ -9,6 +9,7 @@ import sys
|
||||||
import re
|
import re
|
||||||
import os
|
import os
|
||||||
import shlex
|
import shlex
|
||||||
|
import hashlib
|
||||||
|
|
||||||
try:
|
try:
|
||||||
import xdg.BaseDirectory as xdg_bd
|
import xdg.BaseDirectory as xdg_bd
|
||||||
|
@ -112,6 +113,7 @@ class Option:
|
||||||
self.abbr = abbr
|
self.abbr = abbr
|
||||||
self.in_cfg_file = in_cfg_file
|
self.in_cfg_file = in_cfg_file
|
||||||
self.show_in_help = show_in_help
|
self.show_in_help = show_in_help
|
||||||
|
self.hash = hashlib.sha1(name.encode("utf-8")).hexdigest()
|
||||||
|
|
||||||
if is_boolean is None:
|
if is_boolean is None:
|
||||||
self.is_boolean = callback in (bool, callback_module.boolean, callback_module.booleans)
|
self.is_boolean = callback in (bool, callback_module.boolean, callback_module.booleans)
|
||||||
|
@ -231,6 +233,28 @@ def get_xdg_paths(appname):
|
||||||
|
|
||||||
# --------------------------------------------------
|
# --------------------------------------------------
|
||||||
|
|
||||||
|
def serialize_callback(f):
|
||||||
|
return "%s.%s" %(f.__module__, f.__name__)
|
||||||
|
|
||||||
|
# --------------------------------------------------
|
||||||
|
|
||||||
|
def format_description(description):
|
||||||
|
colorless = text.render(description, colors=False)
|
||||||
|
lines = text.paragraph(colorless, 55, 2, join=False)
|
||||||
|
|
||||||
|
# comment them out and assemble
|
||||||
|
return "\n".join("#" + line[1:] for line in lines)
|
||||||
|
|
||||||
|
# --------------------------------------------------
|
||||||
|
|
||||||
|
def serialize_default(option):
|
||||||
|
if option.default is Option_sources.none:
|
||||||
|
return ""
|
||||||
|
else:
|
||||||
|
return cmd.format_invocation(str(x) for x in option.default)
|
||||||
|
|
||||||
|
# --------------------------------------------------
|
||||||
|
|
||||||
class ConfigFile:
|
class ConfigFile:
|
||||||
"""
|
"""
|
||||||
Config file object. Takes a {name: Option} dictionary and a file path, and:
|
Config file object. Takes a {name: Option} dictionary and a file path, and:
|
||||||
|
@ -244,6 +268,7 @@ class ConfigFile:
|
||||||
self.dir = get_xdg_paths(app_name)["config"]
|
self.dir = get_xdg_paths(app_name)["config"]
|
||||||
self.path = os.path.join(self.dir, "main.cfg")
|
self.path = os.path.join(self.dir, "main.cfg")
|
||||||
self.print = text.Output(app_name + ".ConfigFile")
|
self.print = text.Output(app_name + ".ConfigFile")
|
||||||
|
self.seen_hashes = set()
|
||||||
|
|
||||||
self.read_config()
|
self.read_config()
|
||||||
self.sync_config(app_name, app_version)
|
self.sync_config(app_name, app_version)
|
||||||
|
@ -259,7 +284,12 @@ class ConfigFile:
|
||||||
for line in f:
|
for line in f:
|
||||||
line = line.strip()
|
line = line.strip()
|
||||||
|
|
||||||
if line and line[0] != "#": # ignore comments and empty lines
|
if line:
|
||||||
|
if line[0] == "#":
|
||||||
|
m = re.findall("# ---- ([0-9a-f]{40}) ----", line)
|
||||||
|
if m:
|
||||||
|
self.seen_hashes.add(m[0])
|
||||||
|
else:
|
||||||
L, R = (t.strip() for t in line.split("="))
|
L, R = (t.strip() for t in line.split("="))
|
||||||
|
|
||||||
try:
|
try:
|
||||||
|
@ -267,11 +297,7 @@ class ConfigFile:
|
||||||
except KeyError:
|
except KeyError:
|
||||||
raise UnknownOption(L)
|
raise UnknownOption(L)
|
||||||
|
|
||||||
if option.count > 1:
|
|
||||||
args = shlex.split(R)
|
args = shlex.split(R)
|
||||||
else:
|
|
||||||
args = [R]
|
|
||||||
|
|
||||||
option.set_value(args, Option_sources.config_file)
|
option.set_value(args, Option_sources.config_file)
|
||||||
|
|
||||||
def sync_config(self, app_name, app_version):
|
def sync_config(self, app_name, app_version):
|
||||||
|
@ -292,7 +318,21 @@ class ConfigFile:
|
||||||
|
|
||||||
# add new or otherwise missing options
|
# add new or otherwise missing options
|
||||||
with open(self.path, "a") as f:
|
with open(self.path, "a") as f:
|
||||||
...
|
for option in self.options.values():
|
||||||
|
if option.hash not in self.seen_hashes:
|
||||||
|
self.print("adding <W>--<G>%s<.> to config file" %(option.name))
|
||||||
|
|
||||||
|
f.write(docs.config_file_item %(
|
||||||
|
option.hash,
|
||||||
|
("--{0} or --no-{0}" if (option.is_boolean and option.count == 0) else "--{0}").format(option.name),
|
||||||
|
serialize_callback(option.callback),
|
||||||
|
option.count or "no",
|
||||||
|
"" if option.count == 1 else "s",
|
||||||
|
"the last instance counts" if option.overwrite else "can be specified multiple times",
|
||||||
|
format_description(option.description),
|
||||||
|
option.name,
|
||||||
|
serialize_default(option)
|
||||||
|
))
|
||||||
|
|
||||||
def __repr__(self):
|
def __repr__(self):
|
||||||
return "ConfigFile(%s)" %(self.path)
|
return "ConfigFile(%s)" %(self.path)
|
||||||
|
|
10
over/cmd.py
10
over/cmd.py
|
@ -19,6 +19,8 @@ def capture_output(stream, fifo):
|
||||||
|
|
||||||
stream.close()
|
stream.close()
|
||||||
|
|
||||||
|
# --------------------------------------------------
|
||||||
|
|
||||||
def char_in_str(chars, string):
|
def char_in_str(chars, string):
|
||||||
for char in chars:
|
for char in chars:
|
||||||
if char in string:
|
if char in string:
|
||||||
|
@ -26,8 +28,14 @@ def char_in_str(chars, string):
|
||||||
|
|
||||||
return False
|
return False
|
||||||
|
|
||||||
|
# --------------------------------------------------
|
||||||
|
|
||||||
def format_invocation(args):
|
def format_invocation(args):
|
||||||
return " ".join(('"%s"' %(a) if char_in_str(" $()[];\\", a) else a) for a in args)
|
escaped = (arg.replace('"', '\\"') for arg in args)
|
||||||
|
|
||||||
|
return " ".join(('"%s"' %(a) if char_in_str(' $()[];\\"', a) else a) for a in escaped)
|
||||||
|
|
||||||
|
# --------------------------------------------------
|
||||||
|
|
||||||
class Command:
|
class Command:
|
||||||
"""
|
"""
|
||||||
|
|
13
over/docs.py
13
over/docs.py
|
@ -31,3 +31,16 @@ config_file_header = """# Configuration file for %s-%s
|
||||||
# TODO :)
|
# TODO :)
|
||||||
|
|
||||||
"""
|
"""
|
||||||
|
|
||||||
|
config_file_item = """# --------------------------------------------------
|
||||||
|
# ---- %s ----
|
||||||
|
# Option %s
|
||||||
|
# callback: %s
|
||||||
|
# takes %s argument%s
|
||||||
|
# %s
|
||||||
|
#
|
||||||
|
%s
|
||||||
|
#
|
||||||
|
#%s = %s
|
||||||
|
|
||||||
|
"""
|
||||||
|
|
|
@ -193,12 +193,13 @@ def rfind(text, C, start, length):
|
||||||
# return the original index at the same position as the last in colorless
|
# return the original index at the same position as the last in colorless
|
||||||
return start + (indices[len(indices_colorless) - 1] if indices_colorless else 0)
|
return start + (indices[len(indices_colorless) - 1] if indices_colorless else 0)
|
||||||
|
|
||||||
def paragraph(text, width=0, indent=0, stamp=None):
|
def paragraph(text, width=0, indent=0, stamp=None, join=True):
|
||||||
"""
|
"""
|
||||||
str text text to format
|
str text text to format
|
||||||
int width required line length; if 0, current terminal width will be used
|
int width required line length; if 0, current terminal width will be used
|
||||||
int indent how many spaces to indent the text with
|
int indent how many spaces to indent the text with
|
||||||
str stamp placed into the first line's indent (whether it fits is your problem)
|
str stamp placed into the first line's indent (whether it fits is your problem)
|
||||||
|
bool join join lines into one string, otherwise return a list of individual lines
|
||||||
|
|
||||||
Formats text into an indented paragraph that fits the terminal and returns it.
|
Formats text into an indented paragraph that fits the terminal and returns it.
|
||||||
Correctly handles colors.
|
Correctly handles colors.
|
||||||
|
@ -236,7 +237,7 @@ def paragraph(text, width=0, indent=0, stamp=None):
|
||||||
lines[i] = indent_str + line
|
lines[i] = indent_str + line
|
||||||
|
|
||||||
# join
|
# join
|
||||||
return "\n".join(lines)
|
return "\n".join(lines) if join else lines
|
||||||
|
|
||||||
# --------------------------------------------------
|
# --------------------------------------------------
|
||||||
|
|
||||||
|
|
|
@ -4,5 +4,5 @@
|
||||||
major = 1 # VERSION_MAJOR_IDENTIFIER
|
major = 1 # VERSION_MAJOR_IDENTIFIER
|
||||||
minor = 99 # VERSION_MINOR_IDENTIFIER
|
minor = 99 # VERSION_MINOR_IDENTIFIER
|
||||||
# VERSION_LAST_MM 1.99
|
# VERSION_LAST_MM 1.99
|
||||||
patch = 1 # VERSION_PATCH_IDENTIFIER
|
patch = 2 # VERSION_PATCH_IDENTIFIER
|
||||||
str = ".".join(str(v) for v in (major, minor, patch))
|
str = ".".join(str(v) for v in (major, minor, patch))
|
||||||
|
|
2
test.py
2
test.py
|
@ -30,7 +30,7 @@ def int4(*args):
|
||||||
if __name__ == "__main__":
|
if __name__ == "__main__":
|
||||||
main = over.app.Main("new-over-test", version.str, "LICENSE", features={"config_file": True})
|
main = over.app.Main("new-over-test", version.str, "LICENSE", features={"config_file": True})
|
||||||
# name, description, callback, default=Option_sources.none, count=0, overwrite=True, abbr=None, in_cfg_file=True, show_in_help=True
|
# name, description, callback, default=Option_sources.none, count=0, overwrite=True, abbr=None, in_cfg_file=True, show_in_help=True
|
||||||
main.add_option("boolean-single", "", over.callback.boolean, [False], abbr="1")
|
main.add_option("boolean-single", "ISO 8601 date for a new transfer (valid with +EHMU), defaults to current date. It's also used for relative times with --analysis-timeframe. You can use a day-count relative to today here.", over.callback.boolean, [False], abbr="1")
|
||||||
main.add_option("boolean-triple", "", over.callback.booleans, [False, False, False], abbr="3", count=3)
|
main.add_option("boolean-triple", "", over.callback.booleans, [False, False, False], abbr="3", count=3)
|
||||||
main.add_option("str-single", "", str, ["kek"], abbr="s", count=1)
|
main.add_option("str-single", "", str, ["kek"], abbr="s", count=1)
|
||||||
main.add_option("str-quad", "", noop, ["ze", "kek", "is", "bek"], abbr="4", count=4)
|
main.add_option("str-quad", "", noop, ["ze", "kek", "is", "bek"], abbr="4", count=4)
|
||||||
|
|
Loading…
Add table
Add a link
Reference in a new issue