cubicweb/toolsutils.py
branch3.26
changeset 12422 ca3fe2f275eb
parent 12321 e116275bf1ac
child 12567 26744ad37953
equal deleted inserted replaced
12421:e460eac6e648 12422:ca3fe2f275eb
    17 # with CubicWeb.  If not, see <http://www.gnu.org/licenses/>.
    17 # with CubicWeb.  If not, see <http://www.gnu.org/licenses/>.
    18 """some utilities for cubicweb command line tools"""
    18 """some utilities for cubicweb command line tools"""
    19 from __future__ import print_function
    19 from __future__ import print_function
    20 
    20 
    21 
    21 
    22 
       
    23 # XXX move most of this in logilab.common (shellutils ?)
    22 # XXX move most of this in logilab.common (shellutils ?)
    24 
    23 
    25 import io
    24 import io
    26 import os, sys
    25 import os
       
    26 import sys
    27 import subprocess
    27 import subprocess
    28 from os import listdir, makedirs, environ, chmod, walk, remove
    28 from os import listdir, makedirs, chmod, walk, remove
    29 from os.path import exists, join, abspath, normpath
    29 from os.path import exists, join, normpath
    30 import re
    30 import re
    31 from rlcompleter import Completer
    31 from rlcompleter import Completer
    32 try:
    32 try:
    33     import readline
    33     import readline
    34 except ImportError: # readline not available, no completion
    34 except ImportError:  # readline not available, no completion
    35     pass
    35     pass
    36 try:
    36 try:
    37     from os import symlink
    37     from os import symlink
    38 except ImportError:
    38 except ImportError:
    39     def symlink(*args):
    39     def symlink(*args):
    42 from six import add_metaclass
    42 from six import add_metaclass
    43 
    43 
    44 from logilab.common.clcommands import Command as BaseCommand
    44 from logilab.common.clcommands import Command as BaseCommand
    45 from logilab.common.shellutils import ASK
    45 from logilab.common.shellutils import ASK
    46 
    46 
    47 from cubicweb import warning # pylint: disable=E0611
    47 from cubicweb import warning  # pylint: disable=E0611
    48 from cubicweb import ConfigurationError, ExecutionError
    48 from cubicweb import ConfigurationError, ExecutionError
    49 
    49 
       
    50 
    50 def underline_title(title, car='-'):
    51 def underline_title(title, car='-'):
    51     return title+'\n'+(car*len(title))
    52     return title + '\n' + (car * len(title))
       
    53 
    52 
    54 
    53 def iter_dir(directory, condition_file=None, ignore=()):
    55 def iter_dir(directory, condition_file=None, ignore=()):
    54     """iterate on a directory"""
    56     """iterate on a directory"""
    55     for sub in listdir(directory):
    57     for sub in listdir(directory):
    56         if sub in ('CVS', '.svn', '.hg'):
    58         if sub in ('CVS', '.svn', '.hg'):
    57             continue
    59             continue
    58         if condition_file is not None and \
    60         if condition_file is not None and \
    59                not exists(join(directory, sub, condition_file)):
    61                 not exists(join(directory, sub, condition_file)):
    60             continue
    62             continue
    61         if sub in ignore:
    63         if sub in ignore:
    62             continue
    64             continue
    63         yield sub
    65         yield sub
       
    66 
    64 
    67 
    65 def create_dir(directory):
    68 def create_dir(directory):
    66     """create a directory if it doesn't exist yet"""
    69     """create a directory if it doesn't exist yet"""
    67     try:
    70     try:
    68         makedirs(directory)
    71         makedirs(directory)
    71         import errno
    74         import errno
    72         if ex.errno != errno.EEXIST:
    75         if ex.errno != errno.EEXIST:
    73             raise
    76             raise
    74         print('-> no need to create existing directory %s' % directory)
    77         print('-> no need to create existing directory %s' % directory)
    75 
    78 
       
    79 
    76 def create_symlink(source, target):
    80 def create_symlink(source, target):
    77     """create a symbolic link"""
    81     """create a symbolic link"""
    78     if exists(target):
    82     if exists(target):
    79         remove(target)
    83         remove(target)
    80     symlink(source, target)
    84     symlink(source, target)
    81     print('[symlink] %s <-- %s' % (target, source))
    85     print('[symlink] %s <-- %s' % (target, source))
    82 
    86 
       
    87 
    83 def create_copy(source, target):
    88 def create_copy(source, target):
    84     import shutil
    89     import shutil
    85     print('[copy] %s <-- %s' % (target, source))
    90     print('[copy] %s <-- %s' % (target, source))
    86     shutil.copy2(source, target)
    91     shutil.copy2(source, target)
    87 
    92 
       
    93 
    88 def rm(whatever):
    94 def rm(whatever):
    89     import shutil
    95     import shutil
    90     shutil.rmtree(whatever)
    96     shutil.rmtree(whatever)
    91     print('-> removed %s' % whatever)
    97     print('-> removed %s' % whatever)
       
    98 
    92 
    99 
    93 def show_diffs(appl_file, ref_file, askconfirm=True):
   100 def show_diffs(appl_file, ref_file, askconfirm=True):
    94     """interactivly replace the old file with the new file according to
   101     """interactivly replace the old file with the new file according to
    95     user decision
   102     user decision
    96     """
   103     """
   120             copy.close()
   127             copy.close()
   121             print('keep current version, the new file has been written to', copy_file)
   128             print('keep current version, the new file has been written to', copy_file)
   122     else:
   129     else:
   123         print('no diff between %s and %s' % (appl_file, ref_file))
   130         print('no diff between %s and %s' % (appl_file, ref_file))
   124 
   131 
       
   132 
   125 SKEL_EXCLUDE = ('*.py[co]', '*.orig', '*~', '*_flymake.py')
   133 SKEL_EXCLUDE = ('*.py[co]', '*.orig', '*~', '*_flymake.py')
       
   134 
       
   135 
   126 def copy_skeleton(skeldir, targetdir, context,
   136 def copy_skeleton(skeldir, targetdir, context,
   127                   exclude=SKEL_EXCLUDE, askconfirm=False):
   137                   exclude=SKEL_EXCLUDE, askconfirm=False):
   128     import shutil
   138     import shutil
   129     from fnmatch import fnmatch
   139     from fnmatch import fnmatch
   130     skeldir = normpath(skeldir)
   140     skeldir = normpath(skeldir)
   146             else:
   156             else:
   147                 tfpath = join(tdirpath, fname)
   157                 tfpath = join(tdirpath, fname)
   148             if fname.endswith('.tmpl'):
   158             if fname.endswith('.tmpl'):
   149                 tfpath = tfpath[:-5]
   159                 tfpath = tfpath[:-5]
   150                 if not askconfirm or not exists(tfpath) or \
   160                 if not askconfirm or not exists(tfpath) or \
   151                        ASK.confirm('%s exists, overwrite?' % tfpath):
   161                         ASK.confirm('%s exists, overwrite?' % tfpath):
   152                     fill_templated_file(fpath, tfpath, context)
   162                     fill_templated_file(fpath, tfpath, context)
   153                     print('[generate] %s <-- %s' % (tfpath, fpath))
   163                     print('[generate] %s <-- %s' % (tfpath, fpath))
   154             elif exists(tfpath):
   164             elif exists(tfpath):
   155                 show_diffs(tfpath, fpath, askconfirm)
   165                 show_diffs(tfpath, fpath, askconfirm)
   156             else:
   166             else:
   157                 shutil.copyfile(fpath, tfpath)
   167                 shutil.copyfile(fpath, tfpath)
   158                 shutil.copymode(fpath, tfpath)
   168                 shutil.copymode(fpath, tfpath)
   159 
   169 
       
   170 
   160 def fill_templated_file(fpath, tfpath, context):
   171 def fill_templated_file(fpath, tfpath, context):
   161     with io.open(fpath, encoding='ascii') as fobj:
   172     with io.open(fpath, encoding='ascii') as fobj:
   162         template = fobj.read()
   173         template = fobj.read()
   163     with io.open(tfpath, 'w', encoding='ascii') as fobj:
   174     with io.open(tfpath, 'w', encoding='ascii') as fobj:
   164         fobj.write(template % context)
   175         fobj.write(template % context)
       
   176 
   165 
   177 
   166 def restrict_perms_to_user(filepath, log=None):
   178 def restrict_perms_to_user(filepath, log=None):
   167     """set -rw------- permission on the given file"""
   179     """set -rw------- permission on the given file"""
   168     if log:
   180     if log:
   169         log('set permissions to 0600 for %s', filepath)
   181         log('set permissions to 0600 for %s', filepath)
   196                 option = line.strip().lower()
   208                 option = line.strip().lower()
   197                 if option[0] == '[':
   209                 if option[0] == '[':
   198                     # start a section
   210                     # start a section
   199                     section = option[1:-1]
   211                     section = option[1:-1]
   200                     assert section not in config, \
   212                     assert section not in config, \
   201                            'Section %s is defined more than once' % section
   213                         'Section %s is defined more than once' % section
   202                     config[section] = current = {}
   214                     config[section] = current = {}
   203                     continue
   215                     continue
   204                 sys.stderr.write('ignoring malformed line\n%r\n' % line)
   216                 sys.stderr.write('ignoring malformed line\n%r\n' % line)
   205                 continue
   217                 continue
   206             option = option.strip().replace(' ', '_')
   218             option = option.strip().replace(' ', '_')
   216     return config
   228     return config
   217 
   229 
   218 
   230 
   219 _HDLRS = {}
   231 _HDLRS = {}
   220 
   232 
       
   233 
   221 class metacmdhandler(type):
   234 class metacmdhandler(type):
   222     def __new__(mcs, name, bases, classdict):
   235     def __new__(mcs, name, bases, classdict):
   223         cls = super(metacmdhandler, mcs).__new__(mcs, name, bases, classdict)
   236         cls = super(metacmdhandler, mcs).__new__(mcs, name, bases, classdict)
   224         if getattr(cls, 'cfgname', None) and getattr(cls, 'cmdname', None):
   237         if getattr(cls, 'cfgname', None) and getattr(cls, 'cmdname', None):
   225             _HDLRS.setdefault(cls.cmdname, []).append(cls)
   238             _HDLRS.setdefault(cls.cmdname, []).append(cls)
   227 
   240 
   228 
   241 
   229 @add_metaclass(metacmdhandler)
   242 @add_metaclass(metacmdhandler)
   230 class CommandHandler(object):
   243 class CommandHandler(object):
   231     """configuration specific helper for cubicweb-ctl commands"""
   244     """configuration specific helper for cubicweb-ctl commands"""
       
   245 
   232     def __init__(self, config):
   246     def __init__(self, config):
   233         self.config = config
   247         self.config = config
   234 
   248 
   235 
   249 
   236 class Command(BaseCommand):
   250 class Command(BaseCommand):
   256         sys.exit(1)
   270         sys.exit(1)
   257 
   271 
   258 
   272 
   259 CONNECT_OPTIONS = (
   273 CONNECT_OPTIONS = (
   260     ("user",
   274     ("user",
   261      {'short': 'u', 'type' : 'string', 'metavar': '<user>',
   275      {'short': 'u', 'type': 'string', 'metavar': '<user>',
   262       'help': 'connect as <user> instead of being prompted to give it.',
   276       'help': 'connect as <user> instead of being prompted to give it.',
   263       }
   277       }
   264      ),
   278      ),
   265     ("password",
   279     ("password",
   266      {'short': 'p', 'type' : 'password', 'metavar': '<password>',
   280      {'short': 'p', 'type': 'password', 'metavar': '<password>',
   267       'help': 'automatically give <password> for authentication instead of \
   281       'help': 'automatically give <password> for authentication instead of \
   268 being prompted to give it.',
   282 being prompted to give it.',
   269       }),
   283       }),
   270     ("host",
   284     ("host",
   271      {'short': 'H', 'type' : 'string', 'metavar': '<hostname>',
   285      {'short': 'H', 'type': 'string', 'metavar': '<hostname>',
   272       'default': None,
   286       'default': None,
   273       'help': 'specify the name server\'s host name. Will be detected by \
   287       'help': 'specify the name server\'s host name. Will be detected by \
   274 broadcast if not provided.',
   288 broadcast if not provided.',
   275       }),
   289       }),
   276     )
   290 )
   277 
   291 
   278 ## cwshell helpers #############################################################
   292 # cwshell helpers #############################################################
       
   293 
   279 
   294 
   280 class AbstractMatcher(object):
   295 class AbstractMatcher(object):
   281     """Abstract class for CWShellCompleter's matchers.
   296     """Abstract class for CWShellCompleter's matchers.
   282 
   297 
   283     A matcher should implement a ``possible_matches`` method. This
   298     A matcher should implement a ``possible_matches`` method. This
   348             'func_prefix': func_prefix,
   363             'func_prefix': func_prefix,
   349             # offset of rql query
   364             # offset of rql query
   350             'rql_offset': len(func_prefix) + 2,
   365             'rql_offset': len(func_prefix) + 2,
   351             # incomplete rql query
   366             # incomplete rql query
   352             'rql_query': parameters_text,
   367             'rql_query': parameters_text,
   353             }
   368         }
   354 
   369 
   355     def possible_matches(self, text):
   370     def possible_matches(self, text):
   356         """call ``rql.suggestions`` component to complete user's input.
   371         """call ``rql.suggestions`` component to complete user's input.
   357         """
   372         """
   358         # readline will only send last token, but we need the entire user's input
   373         # readline will only send last token, but we need the entire user's input
   370 
   385 
   371 
   386 
   372 class DefaultMatcher(AbstractMatcher):
   387 class DefaultMatcher(AbstractMatcher):
   373     """Default matcher: delegate to standard's `rlcompleter.Completer`` class
   388     """Default matcher: delegate to standard's `rlcompleter.Completer`` class
   374     """
   389     """
       
   390 
   375     def __init__(self, local_ctx):
   391     def __init__(self, local_ctx):
   376         self.completer = Completer(local_ctx)
   392         self.completer = Completer(local_ctx)
   377 
   393 
   378     def possible_matches(self, text):
   394     def possible_matches(self, text):
   379         if "." in text:
   395         if "." in text:
   419                 matches = matcher.possible_matches(text)
   435                 matches = matcher.possible_matches(text)
   420                 if matches:
   436                 if matches:
   421                     self.matches = matches
   437                     self.matches = matches
   422                     break
   438                     break
   423             else:
   439             else:
   424                 return None # no matcher able to handle `text`
   440                 return None  # no matcher able to handle `text`
   425         try:
   441         try:
   426             return self.matches[state]
   442             return self.matches[state]
   427         except IndexError:
   443         except IndexError:
   428             return None
   444             return None