logic overhaul; abstract inventory creation

each function was creating its own inventory; this is not necessary
also, if a user wants only to build a single document, it is not necessary to
scan the entire source and output directory (small, but perceptible speedup to
the end user)
better checking of the various different possible argument types, since an
argument can be a filename, a status_class or a stem
This commit is contained in:
Martin A. Brown 2016-02-29 20:33:33 -08:00
parent 16885508da
commit dfc9855991
1 changed files with 138 additions and 88 deletions

View File

@ -9,93 +9,67 @@ import logging
from argparse import Namespace from argparse import Namespace
import tldp import tldp
from tldp.inventory import status_classes, status_types
from tldp.utils import arg_isloglevel from tldp.utils import arg_isloglevel
from tldp.sources import arg_issourcedoc
logformat = '%(levelname)-9s %(name)s %(filename)s#%(lineno)s %(funcName)s %(message)s' logformat = '%(levelname)-9s %(name)s %(filename)s#%(lineno)s %(funcName)s %(message)s'
logging.basicConfig(stream=sys.stderr, format=logformat, level=logging.ERROR) logging.basicConfig(stream=sys.stderr, format=logformat, level=logging.ERROR)
logger = logging.getLogger(__name__) logger = logging.getLogger(__name__)
def detail(config, args): def summary(config, inv=None):
i = tldp.inventory.Inventory(config.pubdir, config.sourcedir) if inv is None:
inv = tldp.inventory.Inventory(config.pubdir, config.sourcedir)
width = Namespace() width = Namespace()
width.status = max([len(x) for x in tldp.inventory.status_types]) width.status = max([len(x) for x in status_types])
width.stem = max([len(x) for x in i.source.keys()]) width.count = len(str(len(inv.source.keys())))
# -- if user just said "list" with no args, then give the user something for status in status_types:
# sane, "all"; it would make sense for this to be "work", too, but
# "all" seems to be less surprising
#
if not args:
args.append('all')
for arg in args:
status_class = tldp.inventory.status_classes[arg]
for status in status_class:
s = getattr(i, status, None)
assert s is not None
for stem, doc in s.items():
# -- a 'stale' or 'broken' document is implicitly a 'published'
# document as well, but we only want to list each document
# once
#
if doc.status == status:
doc.detail(width, config.verbose, file=sys.stdout)
return 0
def status(config, args):
i = tldp.inventory.Inventory(config.pubdir, config.sourcedir)
width = Namespace()
width.status = max([len(x) for x in tldp.inventory.status_types])
width.count = len(str(len(i.source.keys())))
for status in tldp.inventory.status_types:
if status == 'all': if status == 'all':
continue continue
count = len(getattr(i, status, 0)) count = len(getattr(inv, status, 0))
s = '{0:{w.status}} {1:{w.count}} '.format(status, count, w=width) s = '{0:{w.status}} {1:{w.count}} '.format(status, count, w=width)
print(s, end="") print(s, end="")
if config.verbose: if config.verbose:
print(', '.join(getattr(i, status).keys())) print(', '.join(getattr(inv, status).keys()))
else: else:
abbrev = getattr(i, status).keys() abbrev = getattr(inv, status).keys()
s = '' s = ''
if abbrev: if abbrev:
s = s + abbrev.pop(0) s = s + abbrev.pop(0)
while abbrev and len(s) < 50: while abbrev and len(s) < 50:
s = s + ', ' + abbrev.pop() s = s + ', ' + abbrev.pop(0)
if abbrev: if abbrev:
s = s + ', and %d more ...' % (len(abbrev)) s = s + ', and %d more ...' % (len(abbrev))
print(s) print(s)
return 0 return 0
def build(config, args): def detail(config, docs, inv, **kwargs):
targets = list() if inv is None:
stems = list() inv = tldp.inventory.Inventory(config.pubdir, config.sourcedir)
args = set(args) if not docs:
if args: docs = inv.work.values()
for arg in args: width = Namespace()
if os.path.isfile(arg) or os.path.isdir(arg): width.status = max([len(x) for x in status_types])
source = tldp.sources.SourceDocument(arg) width.stem = max([len(x) for x in inv.source.keys()])
targets.append(source) # -- if user just said "list" with no args, then give the user something
else: # sane, "all"; it would make sense for this to be "work", too, but
stems.append(arg) # "all" seems to be less surprising
if stems or not args: #
i = tldp.inventory.Inventory(config.pubdir, config.sourcedir) for doc in docs:
if stems: stdout = kwargs.get('file', sys.stdout)
for source in i.source.values(): doc.detail(width, config.verbose, file=stdout)
if source.stem in stems: return 0
targets.append(source)
else:
targets.extend(i.new.values()) def build(config, docs, inv):
targets.extend(i.stale.values()) if inv is None:
targets.extend(i.broken.values()) inv = tldp.inventory.Inventory(config.pubdir, config.sourcedir)
if len(targets) != len(args): if not docs:
targets = [x.stem for x in targets] docs = inv.work.values()
missing = args.difference(set(targets)) for source in docs:
logger.error("Could not find matching file or stem for args: %s",
', '.join(missing))
return 1
for source in targets:
if source.stem in config.skip: if source.stem in config.skip:
logger.info("%s skipping build per request", source.stem) logger.info("%s skipping build per request", source.stem)
continue continue
@ -103,7 +77,7 @@ def build(config, args):
dirname = os.path.join(config.pubdir, source.stem) dirname = os.path.join(config.pubdir, source.stem)
source.output = tldp.outputs.OutputDirectory(dirname) source.output = tldp.outputs.OutputDirectory(dirname)
if not source.doctype: if not source.doctype:
logger.warning("%s skipping document of unknown doctype", logger.warning("%s skipping document of unknown doctype",
source.stem) source.stem)
continue continue
output = source.output output = source.output
@ -112,46 +86,122 @@ def build(config, args):
return 0 return 0
def run(): def script(config, docs, inv):
return 0
def getDocumentNames(args):
sought = set()
for arg in args:
doc = arg_issourcedoc(arg)
if doc is not None:
sought.add(doc)
remainder = set(args).difference(sought)
return sought, remainder
def getStatusNames(args):
found = set()
sought = set()
for arg in args:
stati = status_classes.get(arg, None)
if stati:
sought.update(stati)
found.add(arg)
remainder = set(args).difference(found)
return sought, remainder
def getStemNames(config, stati, args, inv=None):
if inv is None:
inv = tldp.inventory.Inventory(config.pubdir, config.sourcedir)
sought = set()
for stem, doc in inv.all.items():
if stem in args:
sought.add(doc)
if doc.status in stati:
sought.add(doc)
soughtstems = [x.stem for x in sought]
remainder = set(args).difference(soughtstems)
return sought, remainder, inv
def run(argv):
# -- may want to see option parsing, so set --loglevel as # -- may want to see option parsing, so set --loglevel as
# soon as possible # soon as possible
if '--loglevel' in sys.argv: if '--loglevel' in argv:
levelarg = 1 + sys.argv.index('--loglevel') levelarg = 1 + argv.index('--loglevel')
level = arg_isloglevel(sys.argv[levelarg]) level = arg_isloglevel(argv[levelarg])
# -- set the root logger's level # -- set the root logger's level
logging.getLogger().setLevel(level) logging.getLogger().setLevel(level)
# -- produce a configuration from CLI, ENV and CFG # -- produce a configuration from CLI, ENV and CFG
# #
tag = 'ldptool' tag = 'ldptool'
argv = sys.argv[1:]
config, args = tldp.config.collectconfiguration(tag, argv) config, args = tldp.config.collectconfiguration(tag, argv)
# -- check to see if the user wishes to --list things # -- summary does not require any args
# this function and friends is called 'detail', because if config.summary:
# Python reserves a special (fundamental) meaning for the word
# list; but for the end-user they are synonyms
#
if config.detail:
sys.exit(detail(config, args))
# -- check to see if the user wants --status output if args:
# return "Unknown args received for --summary: " + ' '.join(args)
if config.status: if not config.pubdir:
if config.pubdir is None: return "Option --pubdir (and --sourcedir) required for --summary."
sys.exit("Option --pubdir required for --status.")
if not config.sourcedir: if not config.sourcedir:
sys.exit("Option --sourcedir required for --status.") return "Option --sourcedir (and --pubdir) required for --summary."
sys.exit(status(config, args))
# -- our primary action is to try to build return summary(config)
if config.build is None:
config.all = True
sys.exit(build(config, args))
# -- args can be a mix of full paths to documents (file or directory)
# stem names (for operating on inventory) and status_type names
#
# -- sort them out into each of the different types
#
docs = list()
inv = None
if args:
rawdocs, remainder = getDocumentNames(args)
logger.debug("args included %d documents in filesystem: %r",
len(rawdocs), rawdocs)
if rawdocs:
for doc in rawdocs:
docs.append(tldp.sources.SourceDocument(doc))
if remainder:
stati, remainder = getStatusNames(remainder)
logger.debug("args included %d status type args: %r",
len(stati), stati)
if remainder or stati:
logger.debug("Checking inventory (%d stems, %d status_classes).",
len(remainder), len(stati))
if not config.pubdir:
return " --pubdir (and --sourcedir) required for inventory."
if not config.sourcedir:
return " --sourcedir (and --pubdir) required for inventory."
stems, remainder, inv = getStemNames(config, stati, remainder)
if stems:
for doc in stems:
docs.append(doc)
if remainder:
return "Unknown argument (not stem, file nor status_class): " \
+ ' '.join(remainder)
if config.detail:
return detail(config, docs, inv)
if config.script:
return script(config, docs, inv)
if not config.build:
logger.info("Assuming --build, since no other action was specified...")
return build(config, docs, inv)
if __name__ == '__main__': if __name__ == '__main__':
run() sys.exit(run(sys.argv[1:]))
# #
# -- end of file # -- end of file