|
1 """some utilities for cubicweb tools |
|
2 |
|
3 :organization: Logilab |
|
4 :copyright: 2001-2008 LOGILAB S.A. (Paris, FRANCE), all rights reserved. |
|
5 :contact: http://www.logilab.fr/ -- mailto:contact@logilab.fr |
|
6 """ |
|
7 __docformat__ = "restructuredtext en" |
|
8 |
|
9 import os, sys |
|
10 from os import listdir, makedirs, symlink, environ, chmod, walk, remove |
|
11 from os.path import exists, join, abspath, normpath |
|
12 |
|
13 from logilab.common.clcommands import Command as BaseCommand, \ |
|
14 main_run as base_main_run, register_commands, pop_arg, cmd_run |
|
15 from logilab.common.compat import any |
|
16 |
|
17 from cubicweb import warning |
|
18 from cubicweb import ConfigurationError, ExecutionError |
|
19 |
|
20 def iter_dir(directory, condition_file=None, ignore=()): |
|
21 """iterate on a directory""" |
|
22 for sub in listdir(directory): |
|
23 if sub in ('CVS', '.svn', '.hg'): |
|
24 continue |
|
25 if condition_file is not None and \ |
|
26 not exists(join(directory, sub, condition_file)): |
|
27 continue |
|
28 if sub in ignore: |
|
29 continue |
|
30 yield sub |
|
31 |
|
32 def create_dir(directory): |
|
33 """create a directory if it doesn't exist yet""" |
|
34 try: |
|
35 makedirs(directory) |
|
36 print 'created directory', directory |
|
37 except OSError, ex: |
|
38 import errno |
|
39 if ex.errno != errno.EEXIST: |
|
40 raise |
|
41 print 'directory %s already exists' % directory |
|
42 |
|
43 def create_symlink(source, target): |
|
44 """create a symbolic link""" |
|
45 if exists(target): |
|
46 remove(target) |
|
47 symlink(source, target) |
|
48 print '[symlink] %s <-- %s' % (target, source) |
|
49 |
|
50 def create_copy(source, target): |
|
51 import shutil |
|
52 print '[copy] %s <-- %s' % (target, source) |
|
53 shutil.copy2(source, target) |
|
54 |
|
55 def rm(whatever): |
|
56 import shutil |
|
57 shutil.rmtree(whatever) |
|
58 print 'removed %s' % whatever |
|
59 |
|
60 def show_diffs(appl_file, ref_file, askconfirm=True): |
|
61 """interactivly replace the old file with the new file according to |
|
62 user decision |
|
63 """ |
|
64 import shutil |
|
65 p_output = os.popen('diff -u %s %s' % (appl_file, ref_file), 'r') |
|
66 diffs = p_output.read() |
|
67 if diffs: |
|
68 if askconfirm: |
|
69 print |
|
70 print diffs |
|
71 action = raw_input('replace (N/y/q) ? ').lower() |
|
72 else: |
|
73 action = 'y' |
|
74 if action == 'y': |
|
75 try: |
|
76 shutil.copyfile(ref_file, appl_file) |
|
77 except IOError: |
|
78 os.system('chmod a+w %s' % appl_file) |
|
79 shutil.copyfile(ref_file, appl_file) |
|
80 print 'replaced' |
|
81 elif action == 'q': |
|
82 sys.exit(0) |
|
83 else: |
|
84 copy_file = appl_file + '.default' |
|
85 copy = file(copy_file, 'w') |
|
86 copy.write(open(ref_file).read()) |
|
87 copy.close() |
|
88 print 'keep current version, the new file has been written to', copy_file |
|
89 else: |
|
90 print 'no diff between %s and %s' % (appl_file, ref_file) |
|
91 |
|
92 |
|
93 def copy_skeleton(skeldir, targetdir, context, |
|
94 exclude=('*.py[co]', '*.orig', '*~', '*_flymake.py'), |
|
95 askconfirm=False): |
|
96 import shutil |
|
97 from fnmatch import fnmatch |
|
98 skeldir = normpath(skeldir) |
|
99 targetdir = normpath(targetdir) |
|
100 for dirpath, dirnames, filenames in walk(skeldir): |
|
101 tdirpath = dirpath.replace(skeldir, targetdir) |
|
102 create_dir(tdirpath) |
|
103 for fname in filenames: |
|
104 if any(fnmatch(fname, pat) for pat in exclude): |
|
105 continue |
|
106 fpath = join(dirpath, fname) |
|
107 if 'CUBENAME' in fname: |
|
108 tfpath = join(tdirpath, fname.replace('CUBENAME', context['cubename'])) |
|
109 elif 'DISTNAME' in fname: |
|
110 tfpath = join(tdirpath, fname.replace('DISTNAME', context['distname'])) |
|
111 else: |
|
112 tfpath = join(tdirpath, fname) |
|
113 if fname.endswith('.tmpl'): |
|
114 tfpath = tfpath[:-5] |
|
115 if not askconfirm or not exists(tfpath) or \ |
|
116 confirm('%s exists, overwrite?' % tfpath): |
|
117 fname = fill_templated_file(fpath, tfpath, context) |
|
118 print '[generate] %s <-- %s' % (tfpath, fpath) |
|
119 elif exists(tfpath): |
|
120 show_diffs(tfpath, fpath, askconfirm) |
|
121 else: |
|
122 shutil.copyfile(fpath, tfpath) |
|
123 |
|
124 def fill_templated_file(fpath, tfpath, context): |
|
125 fobj = file(tfpath, 'w') |
|
126 templated = file(fpath).read() |
|
127 fobj.write(templated % context) |
|
128 fobj.close() |
|
129 |
|
130 def restrict_perms_to_user(filepath, log=None): |
|
131 """set -rw------- permission on the given file""" |
|
132 if log: |
|
133 log('set %s permissions to 0600', filepath) |
|
134 else: |
|
135 print 'set %s permissions to 0600' % filepath |
|
136 chmod(filepath, 0600) |
|
137 |
|
138 def confirm(question, default_is_yes=True): |
|
139 """ask for confirmation and return true on positive answer""" |
|
140 if default_is_yes: |
|
141 input_str = '%s [Y/n]: ' |
|
142 else: |
|
143 input_str = '%s [y/N]: ' |
|
144 answer = raw_input(input_str % (question)).strip().lower() |
|
145 if default_is_yes: |
|
146 if answer in ('n', 'no'): |
|
147 return False |
|
148 return True |
|
149 if answer in ('y', 'yes'): |
|
150 return True |
|
151 return False |
|
152 |
|
153 def read_config(config_file): |
|
154 """read the application configuration from a file and return it as a |
|
155 dictionnary |
|
156 |
|
157 :type config_file: str |
|
158 :param config_file: path to the configuration file |
|
159 |
|
160 :rtype: dict |
|
161 :return: a dictionary with specified values associated to option names |
|
162 """ |
|
163 from logilab.common.fileutils import lines |
|
164 config = current = {} |
|
165 try: |
|
166 for line in lines(config_file, comments='#'): |
|
167 try: |
|
168 option, value = line.split('=', 1) |
|
169 except ValueError: |
|
170 option = line.strip().lower() |
|
171 if option[0] == '[': |
|
172 # start a section |
|
173 section = option[1:-1] |
|
174 assert not config.has_key(section), \ |
|
175 'Section %s is defined more than once' % section |
|
176 config[section] = current = {} |
|
177 continue |
|
178 print >> sys.stderr, 'ignoring malformed line\n%r' % line |
|
179 continue |
|
180 option = option.strip().replace(' ', '_') |
|
181 value = value.strip() |
|
182 current[option] = value or None |
|
183 except IOError, ex: |
|
184 warning('missing or non readable configuration file %s (%s)', |
|
185 config_file, ex) |
|
186 return config |
|
187 |
|
188 def env_path(env_var, default, name): |
|
189 """get a path specified in a variable or using the default value and return |
|
190 it. |
|
191 |
|
192 :type env_var: str |
|
193 :param env_var: name of an environment variable |
|
194 |
|
195 :type default: str |
|
196 :param default: default value if the environment variable is not defined |
|
197 |
|
198 :type name: str |
|
199 :param name: the informal name of the path, used for error message |
|
200 |
|
201 :rtype: str |
|
202 :return: the value of the environment variable or the default value |
|
203 |
|
204 :raise `ConfigurationError`: if the returned path does not exist |
|
205 """ |
|
206 path = environ.get(env_var, default) |
|
207 if not exists(path): |
|
208 raise ConfigurationError('%s path %s doesn\'t exist' % (name, path)) |
|
209 return abspath(path) |
|
210 |
|
211 |
|
212 |
|
213 _HDLRS = {} |
|
214 |
|
215 class metacmdhandler(type): |
|
216 def __new__(mcs, name, bases, classdict): |
|
217 cls = super(metacmdhandler, mcs).__new__(mcs, name, bases, classdict) |
|
218 if getattr(cls, 'cfgname', None) and getattr(cls, 'cmdname', None): |
|
219 _HDLRS.setdefault(cls.cmdname, []).append(cls) |
|
220 return cls |
|
221 |
|
222 |
|
223 class CommandHandler(object): |
|
224 """configuration specific helper for cubicweb-ctl commands""" |
|
225 __metaclass__ = metacmdhandler |
|
226 def __init__(self, config): |
|
227 self.config = config |
|
228 |
|
229 class Command(BaseCommand): |
|
230 """base class for cubicweb-ctl commands""" |
|
231 |
|
232 def config_helper(self, config, required=True, cmdname=None): |
|
233 if cmdname is None: |
|
234 cmdname = self.name |
|
235 for helpercls in _HDLRS.get(cmdname, ()): |
|
236 if helpercls.cfgname == config.name: |
|
237 return helpercls(config) |
|
238 if config.name == 'all-in-one': |
|
239 for helpercls in _HDLRS.get(cmdname, ()): |
|
240 if helpercls.cfgname == 'repository': |
|
241 return helpercls(config) |
|
242 if required: |
|
243 msg = 'No helper for command %s using %s configuration' % ( |
|
244 cmdname, config.name) |
|
245 raise ConfigurationError(msg) |
|
246 |
|
247 def fail(self, reason): |
|
248 print "command failed:", reason |
|
249 sys.exit(1) |
|
250 |
|
251 |
|
252 def main_run(args, doc): |
|
253 """command line tool""" |
|
254 try: |
|
255 base_main_run(args, doc) |
|
256 except ConfigurationError, err: |
|
257 print 'ERROR: ', err |
|
258 sys.exit(1) |
|
259 except ExecutionError, err: |
|
260 print err |
|
261 sys.exit(2) |
|
262 |
|
263 CONNECT_OPTIONS = ( |
|
264 ("user", |
|
265 {'short': 'u', 'type' : 'string', 'metavar': '<user>', |
|
266 'help': 'connect as <user> instead of being prompted to give it.', |
|
267 } |
|
268 ), |
|
269 ("password", |
|
270 {'short': 'p', 'type' : 'password', 'metavar': '<password>', |
|
271 'help': 'automatically give <password> for authentication instead of \ |
|
272 being prompted to give it.', |
|
273 }), |
|
274 ("host", |
|
275 {'short': 'H', 'type' : 'string', 'metavar': '<hostname>', |
|
276 'default': 'all-in-one', |
|
277 'help': 'specify the name server\'s host name. Will be detected by \ |
|
278 broadcast if not provided.', |
|
279 }), |
|
280 ) |
|
281 |
|
282 def config_connect(appid, optconfig): |
|
283 from cubicweb.dbapi import connect |
|
284 from getpass import getpass |
|
285 user = optconfig.user |
|
286 if not user: |
|
287 user = raw_input('login: ') |
|
288 password = optconfig.password |
|
289 if not password: |
|
290 password = getpass('password: ') |
|
291 return connect(user=user, password=password, host=optconfig.host, database=appid) |
|
292 |