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: |