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:
Martinez 2016-05-17 20:58:53 +02:00
parent 17eda81ad2
commit 5b983a3131
6 changed files with 81 additions and 19 deletions

View file

@ -9,6 +9,7 @@ import sys
import re
import os
import shlex
import hashlib
try:
import xdg.BaseDirectory as xdg_bd
@ -112,6 +113,7 @@ class Option:
self.abbr = abbr
self.in_cfg_file = in_cfg_file
self.show_in_help = show_in_help
self.hash = hashlib.sha1(name.encode("utf-8")).hexdigest()
if is_boolean is None:
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:
"""
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.path = os.path.join(self.dir, "main.cfg")
self.print = text.Output(app_name + ".ConfigFile")
self.seen_hashes = set()
self.read_config()
self.sync_config(app_name, app_version)
@ -259,20 +284,21 @@ class ConfigFile:
for line in f:
line = line.strip()
if line and line[0] != "#": # ignore comments and empty lines
L, R = (t.strip() for t in line.split("="))
try:
option = self.options[L]
except KeyError:
raise UnknownOption(L)
if option.count > 1:
args = shlex.split(R)
if line:
if line[0] == "#":
m = re.findall("# ---- ([0-9a-f]{40}) ----", line)
if m:
self.seen_hashes.add(m[0])
else:
args = [R]
option.set_value(args, Option_sources.config_file)
L, R = (t.strip() for t in line.split("="))
try:
option = self.options[L]
except KeyError:
raise UnknownOption(L)
args = shlex.split(R)
option.set_value(args, Option_sources.config_file)
def sync_config(self, app_name, app_version):
"""
@ -292,7 +318,21 @@ class ConfigFile:
# add new or otherwise missing options
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):
return "ConfigFile(%s)" %(self.path)

View file

@ -19,6 +19,8 @@ def capture_output(stream, fifo):
stream.close()
# --------------------------------------------------
def char_in_str(chars, string):
for char in chars:
if char in string:
@ -26,8 +28,14 @@ def char_in_str(chars, string):
return False
# --------------------------------------------------
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:
"""

View file

@ -31,3 +31,16 @@ config_file_header = """# Configuration file for %s-%s
# TODO :)
"""
config_file_item = """# --------------------------------------------------
# ---- %s ----
# Option %s
# callback: %s
# takes %s argument%s
# %s
#
%s
#
#%s = %s
"""

View file

@ -193,12 +193,13 @@ def rfind(text, C, start, length):
# 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)
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
int width required line length; if 0, current terminal width will be used
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)
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.
Correctly handles colors.
@ -236,7 +237,7 @@ def paragraph(text, width=0, indent=0, stamp=None):
lines[i] = indent_str + line
# join
return "\n".join(lines)
return "\n".join(lines) if join else lines
# --------------------------------------------------

View file

@ -4,5 +4,5 @@
major = 1 # VERSION_MAJOR_IDENTIFIER
minor = 99 # VERSION_MINOR_IDENTIFIER
# 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))

View file

@ -30,7 +30,7 @@ def int4(*args):
if __name__ == "__main__":
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
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("str-single", "", str, ["kek"], abbr="s", count=1)
main.add_option("str-quad", "", noop, ["ze", "kek", "is", "bek"], abbr="4", count=4)