# -*- coding: utf-8 -*-
################################################################################
# #
# This file is part of the Bibolamazi Project. #
# Copyright (C) 2014 by Philippe Faist #
# philippe.faist@bluewin.ch #
# #
# Bibolamazi is free software: you can redistribute it and/or modify #
# it under the terms of the GNU General Public License as published by #
# the Free Software Foundation, either version 3 of the License, or #
# (at your option) any later version. #
# #
# Bibolamazi is distributed in the hope that it will be useful, #
# but WITHOUT ANY WARRANTY; without even the implied warranty of #
# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the #
# GNU General Public License for more details. #
# #
# You should have received a copy of the GNU General Public License #
# along with Bibolamazi. If not, see <http://www.gnu.org/licenses/>. #
# #
################################################################################
"""
This module defines callbacks and actions for parsing the command-line
arguments for bibolamazi. You're most probably not interested in this API. (Not
mentioning that it might change if I feel the need for it.)
"""
import re
import os
import sys
import os.path
import argparse
import logging
import bibolamazi.init
from .butils import getbool
from . import helppages
logger = logging.getLogger(__name__)
from .bibfilter.argtypes import LogLevel
[docs]class store_or_count(argparse.Action):
def __init__(self, option_strings, dest, nargs='?', **kwargs):
# some checks
if nargs != '?':
raise ValueError('store_or_const: nargs must be "?"')
if ('type' in kwargs):
raise TypeError("Can't enforce a type on a store_or_count option!")
super().__init__(option_strings, dest, nargs=nargs, const=None, **kwargs)
def __call__(self, parser, namespace, values, option_string):
try:
val = getattr(namespace, self.dest)
except AttributeError:
val = 0
# count -vv as -v -v
if (isinstance(values, str) and not option_string.startswith('--') and len(option_string) > 1):
optstr = option_string[1:]
while values.startswith(optstr):
# add an additional count for each additional specification of the option.
val += 1
values = values[len(optstr):] # strip that from the values
if not values:
values = None
# Note: I don't know how to fix situations like "prog.py -v some_prog_arg" which is taken
# as "-v some_prog_arg" ... we would need to interfere with the option parser to match the
# argument not to the option (because of the wrong type), but to the program...
# get the argument of -v (e.g., -v2 or --verbose 2 or --verbose=2 )
if (isinstance(values, str)):
try:
values = int(values)
except ValueError:
opt_name = ", ".join(self.option_strings)
parser.error(u"Invalid argument to %s: `%s' (maybe use %s option at the end of the command?)"
%(opt_name, values, opt_name))
if (values is not None):
# value provided
val = int(values)
else:
val += 1
setattr(namespace, self.dest, val)
rxkeyval = re.compile(r'^([\w.-]+)=(.*)$', re.DOTALL)
[docs]class store_key_val(argparse.Action):
"""
Handles an ghostscript-style option of the type '-sBoolKey=some-value'.
"""
def __init__(self, option_strings, dest, nargs=1, exception=ValueError, **kwargs):
# some checks
if nargs != 1:
raise ValueError('nargs for store_key_val actions must be == 1')
self.exception = exception
super().__init__(
option_strings=option_strings,
dest=dest,
nargs=nargs,
**kwargs)
def __call__(self, parser, namespace, values, option_string):
# parse key-value pair in values
if (isinstance(values, list)):
values = values[0]
m = rxkeyval.match(values)
if not m:
raise self.exception("cannot parse key=value pair: "+repr(values))
keyvalpair = (m.group(1), m.group(2),)
if (not self.dest):
(key, val) = keyvalpair
setattr(namespace, key, val)
else:
try:
d = getattr(namespace, self.dest)
except AttributeError:
pass
if not d:
d = []
d.append(keyvalpair)
setattr(namespace, self.dest, d)
[docs]class store_key_bool(argparse.Action):
"""
Handles an ghostscript-style option of the type '-dBoolKey' or '-dBoolKey=0'.
"""
def __init__(self, option_strings, dest, nargs=1, const=True,
exception=ValueError, **kwargs):
# some checks
if nargs != 1:
raise ValueError('nargs for store_key_bool actions must be == 1')
self.exception = exception
super().__init__(
option_strings=option_strings,
dest=dest,
nargs=nargs,
const=bool(const),
**kwargs)
def __call__(self, parser, namespace, values, option_string):
key = values[0]
storeval = self.const
eqindex = key.find('=')
if (eqindex != -1):
try:
storeval = getbool(key[eqindex+1:])
key = key[:eqindex]
except ValueError as e:
exc = self.exception(str(e))
exc.opt_dest = self.dest
raise exc
if (not self.dest):
setattr(namespace, key, self.const)
else:
try:
d = getattr(namespace, self.dest)
if d is None:
d = []
except AttributeError:
d = []
d.append(
(key, storeval,)
)
setattr(namespace, self.dest, d)
[docs]class store_key_const(argparse.Action):
def __init__(self, option_strings, dest, nargs=1, const=True, **kwargs):
# some checks
if nargs != 1:
raise ValueError('nargs for store_key_const actions must be == 1')
super().__init__(
option_strings=option_strings,
dest=dest,
nargs=nargs,
const=const,
**kwargs)
def __call__(self, parser, namespace, values, option_string):
key = values[0]
if (not self.dest):
setattr(namespace, key, self.const)
else:
try:
d = getattr(namespace, self.dest)
if d is None:
d = []
except AttributeError:
d = []
d.append(key)
setattr(namespace, self.dest, d)
[docs]class opt_action_help(argparse.Action):
def __call__(self, parser, namespace, values, option_string):
if not values or values == "elp": # in case of -help: seen as -h elp
helppages.cmdl_show_help('/general/cmdline', parser=parser)
parser.exit()
if values and len(values) and values[0] == '/':
path = values
helppages.cmdl_show_help(path, parser=parser)
parser.exit()
thefilter = values
helppages.cmdl_show_help('/filter/'+thefilter, parser=parser)
parser.exit()
[docs]class opt_action_helpwelcome(argparse.Action):
def __call__(self, parser, namespace, values, option_string):
helppages.cmdl_show_help('/general/welcome', parser=parser)
parser.exit()
[docs]class opt_action_version(argparse.Action):
def __call__(self, parser, namespace, values, option_string):
p = helppages.get_help_page('/general/cmdlversion')
sys.stdout.write(p.contentAsTxt())
parser.exit()
[docs]class opt_list_filters(argparse.Action):
def __init__(self, nargs=0, **kwargs):
if nargs != 0:
raise ValueError('nargs for opt_list_filters must be == 0')
super().__init__(nargs=0, **kwargs)
def __call__(self, parser, namespace, values, option_string):
helppages.cmdl_show_help('/filters')
parser.exit()
[docs]class opt_init_empty_template(argparse.Action):
def __init__(self, nargs=1, **kwargs):
if nargs != 1:
raise ValueError('nargs for init_empty_template must be == 1')
argparse.Action.__init__(self, nargs=1, **kwargs)
def __call__(self, parser, namespace, values, option_string):
from . import bibolamazifile
try:
newfilename = values[0]
except IndexError:
newfilename = values
if (os.path.exists(newfilename)):
logger.error("Cowardly refusing to overwrite existing file `%s'. Remove it first."
%(newfilename))
parser.exit(9)
bfile = bibolamazifile.BibolamaziFile(newfilename, create=True)
bfile.saveToFile()
parser.exit()
[docs]class opt_action_github_auth(argparse.Action):
def __call__(self, parser, namespace, values, option_string):
ga = GithubAuthSetup(parser)
if values:
ga.setup_from_arg(values[0].strip())
else:
ga.interactive_enter()
[docs]class GithubAuthSetup:
def __init__(self, parser):
self.parser = parser
from . import main
self.main = main
[docs] def setup_from_arg(self, arg):
#
# auth key directly specified as an option argument
#
if arg == '-':
# unset auth
self.unset_token_and_exit()
elif arg == '?' or not arg:
# empty argument, or '?' -- just run the interactive setup
self.interactive_enter()
else:
self.save_token_and_exit(token)
[docs] def print_status(self):
status = self.main.get_github_auth_status()
if status is None:
print("Github authentication has not yet been configured.")
elif status:
print("Github authentication token is set.")
else:
print("Github authentication token is NOT set.")
return status
[docs] def interactive_enter(self):
#
# First of all, print the current config status
#
print("") # empty line
status = self.print_status()
if status: # token already configured
# enter "manage token" menu
self.interactive_manage_token()
else:
# directly ask to set a token
self.interactive_setup_token()
[docs] def interactive_manage_token(self):
print("")
print("""\
Would you like to:
(1) unset the existing token,
(2) set a new token, or
(q) cancel and exit?
""")
while True:
action = input("Selection (1 or 2 or q): ").strip()
if action in ['1', '2', 'q']:
break
print("""Invalid input, please type "1", "2", or "q".""")
if action == '1':
self.unset_token_and_exit()
elif action == '2':
self.interactive_setup_token()
elif action == 'q':
self.parser.exit(0)
else:
raise RuntimeError("It's not possible to get here")
[docs] def interactive_setup_token(self):
print("""\
INSTRUCTIONS FOR GITHUB AUTHENTICATION
This process will generate a personal access token authorizing bibolamazi to
access your github account. This procedure ensures that bibolamazi never sees
your github password. The personal access token can be revoked in your github
settings at any time.
Please visit this URL in your browser and follow the instructions below:
https://github.com/settings/tokens
1. Click on the button “Generate new token”
2. Give a name to the access token such as “bibolamazi access”, and select the
“repo” scope
3. Scroll down and click on “Generate token”
4. Paste the access token below:
""")
token = input("Access token: ").strip()
self.save_token_and_exit(token)
[docs] def save_token_and_exit(self, token):
# set the given token
try:
self.main.save_github_auth_token(token)
except ValueError as e:
logger.error(str(e))
self.parser.exit(13)
self.parser.exit()
[docs] def unset_token_and_exit(self):
# set the given token
self.save_token_and_exit(None)
# Set up the logger according to the user's wishes
# ------------------------------------------------
[docs]class opt_set_verbosity(argparse.Action):
def __init__(self, nargs=1, **kwargs):
if nargs != 0 and nargs != 1:
raise ValueError('nargs for opt_set_verbosity must be 0 or 1')
argparse.Action.__init__(self, nargs=nargs, type=int, **kwargs)
def __call__(self, parser, namespace, values, option_string):
from . import main
if self.nargs == 1:
verbositylevel = int(values[0])
else:
verbositylevel = self.const
# act on the root logger
loglevel = main.verbosity_logger_level(verbositylevel)
rootlogger = logging.getLogger()
rootlogger.setLevel(loglevel)
logger.longdebug('Set verbosity: %d', verbositylevel)
# finally, see if we should display information about where messages originated
# from. Do this if the user specified a log level of severity less or equal to
# DEBUG.
if loglevel <= logging.DEBUG and hasattr(rootlogger, 'bibolamazi_formatter'):
# show log-record-position-info (`[module lineno]: function():') for all messages
rootlogger.bibolamazi_formatter.setShowPosInfoLevel(logging.CRITICAL)
# ------------------
[docs]class opt_set_fine_log_levels(argparse.Action):
def __init__(self, nargs=1, **kwargs):
if nargs != 1:
raise ValueError('nargs for opt_set_fine_log_levels must be == 1')
argparse.Action.__init__(self, nargs=nargs, **kwargs)
def __call__(self, parser, namespace, values, option_string):
#
# If there are some more fine-grained debug levels to set, go for it. Useful for
# debugging bibolamazi components.
#
rootlogger = logging.getLogger()
fine_log_levels = values[0]
logger.longdebug("fine_log_levels=%r", fine_log_levels)
lvlrx = re.compile(
r'^\s*((?P<modname>[A-Za-z0-9_.]+)=)?(?P<level>(LONG)?DEBUG|WARNING|INFO|ERROR|CRITICAL)\s*$'
)
has_set_fine_levels = False
for lvl in fine_log_levels.split(','):
m = lvlrx.match(lvl)
if not m:
logger.warning("Bad fine-grained log level setting: `%s'", lvl)
continue
modname = m.group('modname')
getloggerargs = {}
if modname:
getloggerargs['name'] = modname
thelogger = logging.getLogger(**getloggerargs)
try:
thelevel = LogLevel(m.group('level')).levelno
except ValueError as e:
logger.warning("Bad fine-grained log level setting: bad level `%s': %s", m.group('level'), e)
continue
#print("setting Logger: modname=%r, getloggerargs=%r, thelogger=%r; to level %d"%(
# modname, getloggerargs, thelogger, thelevel
#))
thelogger.setLevel(thelevel)
has_set_fine_levels = True
# finally, see if we should display information about where messages originated
# from. Do this if the user specified a nontrivial value to this option.
if has_set_fine_levels and hasattr(rootlogger, 'bibolamazi_formatter'):
# show log-record-position-info (`[module lineno]: function():') for all messages
rootlogger.bibolamazi_formatter.setShowPosInfoLevel(logging.CRITICAL)
logger.longdebug('Set fine log levels done.')