diff options
Diffstat (limited to 'attoconf/core.py')
-rw-r--r-- | attoconf/core.py | 116 |
1 files changed, 112 insertions, 4 deletions
diff --git a/attoconf/core.py b/attoconf/core.py index 0c01d5e..8b53209 100644 --- a/attoconf/core.py +++ b/attoconf/core.py @@ -17,21 +17,48 @@ from __future__ import print_function, division, absolute_import +from collections import namedtuple +import os +import sys + +from .help import Help + +# Nothing to see here. Move along. +Option = namedtuple('Option', ['type', 'init']) +class ArgumentError(Exception): pass + +def as_var(name): + return name.lstrip('-').upper() + +def trim_trailing_slashes(path): + p, s = os.path.split(path) + if not s: + return p + return path + class Project(object): ''' A Project is a directory and the list of options that can be set. ''' __slots__ = ( + 'srcdir', 'aliases', 'options', 'help', + 'checks', ) def __init__(self, srcdir): ''' A Project is initially constructed from just the source directory ''' + self.srcdir = trim_trailing_slashes(srcdir) + self.aliases = {} + self.options = {} + self.help = Help() + self.checks = [] def add_help(self, text, hidden): ''' Directly add a line of text to the help. ''' + self.help.add_text(text, hidden) def add_alias(self, key, expansion, help, hidden): ''' Add an alias. @@ -40,8 +67,17 @@ class Project(object): The expansion is a list of other options, which may be aliases. ''' - - def add_option(self, name, init, type, check, help, hidden): + if key in self.aliases: + raise KeyError(key) + expansion = list(expansion) + self.aliases[key] = expansion + if help is None: + help = 'alias for ' + ' '.join(expansion) + self.help.add_option(key, help, hidden) + + def add_option(self, name, init, type, check, + help, hidden, + help_var=None, help_def=None): ''' Add an actual option. This must be passed with a =. @@ -56,27 +92,99 @@ class Project(object): Additionally, a line of help is added, with additional formatting. ''' + if name in self.options: + raise KeyError(name) + self.options[name] = Option(init=init, type=type) + self.checks.append(lambda bld: check(bld, bld.vars[as_var(name)][0])) + if help_var is None: + help_var = as_var(name) + if help_def is None: + help_def = init + if help_def is not None: + help = '%s [%s]' % (help, help_def) + + if help_var != name: + help_opt = '%s=%s' % (name, help_var) + else: + help_opt = name + self.help.add_option(help_opt, help, hidden) + + def do_help(self, opt): + ''' Pseudo type-hook to be registered for --help (calls sys.exit). + ''' + if opt == 'default': + hidden = False + elif opt == 'hidden': + hidden = True + else: + raise ValueError + self.help.print(sys.stdout, hidden) + sys.exit() class Build(object): ''' A Build is a directory and set of options applied to a Project. ''' __slots__ = ( + 'bindir', 'project', 'vars', ) def __init__(self, project, bindir): - ''' A Build is initially constructed from + ''' A Build is initially constructed from a project and a build dir. ''' + self.project = project + self.bindir = trim_trailing_slashes(bindir) + self.vars = {as_var(k): (o.init, 'default') + for k, o in project.options.iteritems()} def apply_arg(self, arg): ''' Parse a single argument, expanding aliases. ''' + alias = self.project.aliases.get(arg) + if alias is not None: + for e in alias: + # TODO: catch recursive aliases + # or should they be caught earlier? + self.apply_arg(e) + return + + if '=' not in arg: + if arg in self.project.options: + raise ArgumentError('Option %s requires an argument' % arg) + elif arg.startswith('-'): + raise ArgumentError('Unknown option %s' % arg) + else: + raise ArgumentError('Unknown environment variable %s' % arg) + + k, a = arg.split('=', 1) + opt = self.project.options.get(k) + if opt is None: + raise sys.exit('Unknown option %s' % k) + self.vars[as_var(k)] = (opt.type(a), 'command-line') def finish(self): ''' With the current set of variables, run all the checks and presumably produce some sort of output. ''' + for check in self.project.checks: + check(self) def configure(self, args, env): - ''' Automatically call apply_arg() a bunch of times, then finish(). + ''' First apply variables from the environment, + then call apply_arg() a bunch of times, then finish(). + ''' + for k in self.vars: + val = env.get(k) + if val is not None: + self.vars[k] = (self.project.options[k].type(val), 'environment') + + for arg in args: + self.apply_arg(arg) + + self.finish() + + def relative_source(self): + ''' Return a relative path from the build tree to the source tree. ''' + return os.path.relpath(os.path.realpath(self.project.srcdir), + os.path.realpath(self.bindir)) |