##// END OF EJS Templates
indexers: get default filenames for indexing from lexer definitions
Takumi IINO -
r5558:130f8e17 default
parent child Browse files
Show More
@@ -1,73 +1,73 b''
1 # -*- coding: utf-8 -*-
1 # -*- coding: utf-8 -*-
2 # This program is free software: you can redistribute it and/or modify
2 # This program is free software: you can redistribute it and/or modify
3 # it under the terms of the GNU General Public License as published by
3 # it under the terms of the GNU General Public License as published by
4 # the Free Software Foundation, either version 3 of the License, or
4 # the Free Software Foundation, either version 3 of the License, or
5 # (at your option) any later version.
5 # (at your option) any later version.
6 #
6 #
7 # This program is distributed in the hope that it will be useful,
7 # This program is distributed in the hope that it will be useful,
8 # but WITHOUT ANY WARRANTY; without even the implied warranty of
8 # but WITHOUT ANY WARRANTY; without even the implied warranty of
9 # MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
9 # MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
10 # GNU General Public License for more details.
10 # GNU General Public License for more details.
11 #
11 #
12 # You should have received a copy of the GNU General Public License
12 # You should have received a copy of the GNU General Public License
13 # along with this program. If not, see <http://www.gnu.org/licenses/>.
13 # along with this program. If not, see <http://www.gnu.org/licenses/>.
14 """
14 """
15 kallithea.config.conf
15 kallithea.config.conf
16 ~~~~~~~~~~~~~~~~~~~~~
16 ~~~~~~~~~~~~~~~~~~~~~
17
17
18 Various config settings for Kallithea
18 Various config settings for Kallithea
19
19
20 This file was forked by the Kallithea project in July 2014.
20 This file was forked by the Kallithea project in July 2014.
21 Original author and date, and relevant copyright and licensing information is below:
21 Original author and date, and relevant copyright and licensing information is below:
22 :created_on: Mar 7, 2012
22 :created_on: Mar 7, 2012
23 :author: marcink
23 :author: marcink
24 :copyright: (c) 2013 RhodeCode GmbH, and others.
24 :copyright: (c) 2013 RhodeCode GmbH, and others.
25 :license: GPLv3, see LICENSE.md for more details.
25 :license: GPLv3, see LICENSE.md for more details.
26 """
26 """
27
27
28 from kallithea.lib.utils2 import __get_lem
28 from kallithea.lib.utils2 import __get_lem, __get_index_filenames
29
29
30
30
31 # language map is also used by whoosh indexer, which for those specified
31 # language map is also used by whoosh indexer, which for those specified
32 # extensions will index it's content
32 # extensions will index it's content
33 LANGUAGES_EXTENSIONS_MAP = __get_lem()
33 LANGUAGES_EXTENSIONS_MAP = __get_lem()
34
34
35 # Whoosh index targets
35 # Whoosh index targets
36
36
37 # Extensions we want to index content of using whoosh
37 # Extensions we want to index content of using whoosh
38 INDEX_EXTENSIONS = LANGUAGES_EXTENSIONS_MAP.keys()
38 INDEX_EXTENSIONS = LANGUAGES_EXTENSIONS_MAP.keys()
39
39
40 # Filenames we want to index content of using whoosh
40 # Filenames we want to index content of using whoosh
41 INDEX_FILENAMES = []
41 INDEX_FILENAMES = __get_index_filenames()
42
42
43 # list of readme files to search in file tree and display in summary
43 # list of readme files to search in file tree and display in summary
44 # attached weights defines the search order lower is first
44 # attached weights defines the search order lower is first
45 ALL_READMES = [
45 ALL_READMES = [
46 ('readme', 0), ('README', 0), ('Readme', 0),
46 ('readme', 0), ('README', 0), ('Readme', 0),
47 ('doc/readme', 1), ('doc/README', 1), ('doc/Readme', 1),
47 ('doc/readme', 1), ('doc/README', 1), ('doc/Readme', 1),
48 ('Docs/readme', 2), ('Docs/README', 2), ('Docs/Readme', 2),
48 ('Docs/readme', 2), ('Docs/README', 2), ('Docs/Readme', 2),
49 ('DOCS/readme', 2), ('DOCS/README', 2), ('DOCS/Readme', 2),
49 ('DOCS/readme', 2), ('DOCS/README', 2), ('DOCS/Readme', 2),
50 ('docs/readme', 2), ('docs/README', 2), ('docs/Readme', 2),
50 ('docs/readme', 2), ('docs/README', 2), ('docs/Readme', 2),
51 ]
51 ]
52
52
53 # extension together with weights to search lower is first
53 # extension together with weights to search lower is first
54 RST_EXTS = [
54 RST_EXTS = [
55 ('', 0), ('.rst', 1), ('.rest', 1),
55 ('', 0), ('.rst', 1), ('.rest', 1),
56 ('.RST', 2), ('.REST', 2),
56 ('.RST', 2), ('.REST', 2),
57 ('.txt', 3), ('.TXT', 3)
57 ('.txt', 3), ('.TXT', 3)
58 ]
58 ]
59
59
60 MARKDOWN_EXTS = [
60 MARKDOWN_EXTS = [
61 ('.md', 1), ('.MD', 1),
61 ('.md', 1), ('.MD', 1),
62 ('.mkdn', 2), ('.MKDN', 2),
62 ('.mkdn', 2), ('.MKDN', 2),
63 ('.mdown', 3), ('.MDOWN', 3),
63 ('.mdown', 3), ('.MDOWN', 3),
64 ('.markdown', 4), ('.MARKDOWN', 4)
64 ('.markdown', 4), ('.MARKDOWN', 4)
65 ]
65 ]
66
66
67 PLAIN_EXTS = [('.text', 2), ('.TEXT', 2)]
67 PLAIN_EXTS = [('.text', 2), ('.TEXT', 2)]
68
68
69 ALL_EXTS = MARKDOWN_EXTS + RST_EXTS + PLAIN_EXTS
69 ALL_EXTS = MARKDOWN_EXTS + RST_EXTS + PLAIN_EXTS
70
70
71 DATETIME_FORMAT = "%Y-%m-%d %H:%M:%S"
71 DATETIME_FORMAT = "%Y-%m-%d %H:%M:%S"
72
72
73 DATE_FORMAT = "%Y-%m-%d"
73 DATE_FORMAT = "%Y-%m-%d"
@@ -1,760 +1,779 b''
1 # -*- coding: utf-8 -*-
1 # -*- coding: utf-8 -*-
2 # This program is free software: you can redistribute it and/or modify
2 # This program is free software: you can redistribute it and/or modify
3 # it under the terms of the GNU General Public License as published by
3 # it under the terms of the GNU General Public License as published by
4 # the Free Software Foundation, either version 3 of the License, or
4 # the Free Software Foundation, either version 3 of the License, or
5 # (at your option) any later version.
5 # (at your option) any later version.
6 #
6 #
7 # This program is distributed in the hope that it will be useful,
7 # This program is distributed in the hope that it will be useful,
8 # but WITHOUT ANY WARRANTY; without even the implied warranty of
8 # but WITHOUT ANY WARRANTY; without even the implied warranty of
9 # MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
9 # MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
10 # GNU General Public License for more details.
10 # GNU General Public License for more details.
11 #
11 #
12 # You should have received a copy of the GNU General Public License
12 # You should have received a copy of the GNU General Public License
13 # along with this program. If not, see <http://www.gnu.org/licenses/>.
13 # along with this program. If not, see <http://www.gnu.org/licenses/>.
14 """
14 """
15 kallithea.lib.utils2
15 kallithea.lib.utils2
16 ~~~~~~~~~~~~~~~~~~~~
16 ~~~~~~~~~~~~~~~~~~~~
17
17
18 Some simple helper functions
18 Some simple helper functions
19
19
20 This file was forked by the Kallithea project in July 2014.
20 This file was forked by the Kallithea project in July 2014.
21 Original author and date, and relevant copyright and licensing information is below:
21 Original author and date, and relevant copyright and licensing information is below:
22 :created_on: Jan 5, 2011
22 :created_on: Jan 5, 2011
23 :author: marcink
23 :author: marcink
24 :copyright: (c) 2013 RhodeCode GmbH, and others.
24 :copyright: (c) 2013 RhodeCode GmbH, and others.
25 :license: GPLv3, see LICENSE.md for more details.
25 :license: GPLv3, see LICENSE.md for more details.
26 """
26 """
27
27
28
28
29 import os
29 import os
30 import re
30 import re
31 import sys
31 import sys
32 import time
32 import time
33 import uuid
33 import uuid
34 import datetime
34 import datetime
35 import urllib
35 import urllib
36 import binascii
36 import binascii
37
37
38 import webob
38 import webob
39 import urlobject
39 import urlobject
40
40
41 from pylons.i18n.translation import _, ungettext
41 from pylons.i18n.translation import _, ungettext
42 from kallithea.lib.vcs.utils.lazy import LazyProperty
42 from kallithea.lib.vcs.utils.lazy import LazyProperty
43 from kallithea.lib.compat import json
43 from kallithea.lib.compat import json
44
44
45
45
46 def __get_lem():
46 def __get_lem():
47 """
47 """
48 Get language extension map based on what's inside pygments lexers
48 Get language extension map based on what's inside pygments lexers
49 """
49 """
50 from pygments import lexers
50 from pygments import lexers
51 from string import lower
51 from string import lower
52 from collections import defaultdict
52 from collections import defaultdict
53
53
54 d = defaultdict(lambda: [])
54 d = defaultdict(lambda: [])
55
55
56 def __clean(s):
56 def __clean(s):
57 s = s.lstrip('*')
57 s = s.lstrip('*')
58 s = s.lstrip('.')
58 s = s.lstrip('.')
59
59
60 if s.find('[') != -1:
60 if s.find('[') != -1:
61 exts = []
61 exts = []
62 start, stop = s.find('['), s.find(']')
62 start, stop = s.find('['), s.find(']')
63
63
64 for suffix in s[start + 1:stop]:
64 for suffix in s[start + 1:stop]:
65 exts.append(s[:s.find('[')] + suffix)
65 exts.append(s[:s.find('[')] + suffix)
66 return map(lower, exts)
66 return map(lower, exts)
67 else:
67 else:
68 return map(lower, [s])
68 return map(lower, [s])
69
69
70 for lx, t in sorted(lexers.LEXERS.items()):
70 for lx, t in sorted(lexers.LEXERS.items()):
71 m = map(__clean, t[-2])
71 m = map(__clean, t[-2])
72 if m:
72 if m:
73 m = reduce(lambda x, y: x + y, m)
73 m = reduce(lambda x, y: x + y, m)
74 for ext in m:
74 for ext in m:
75 desc = lx.replace('Lexer', '')
75 desc = lx.replace('Lexer', '')
76 d[ext].append(desc)
76 d[ext].append(desc)
77
77
78 return dict(d)
78 return dict(d)
79
79
80
80
81 def __get_index_filenames():
82 """
83 Get list of known indexable filenames from pygment lexer internals
84 """
85 from pygments import lexers
86 from itertools import ifilter
87
88 filenames = []
89
90 def likely_filename(s):
91 return s.find('*') == -1 and s.find('[') == -1
92
93 for lx, t in sorted(lexers.LEXERS.items()):
94 for f in ifilter(likely_filename, t[-2]):
95 filenames.append(f)
96
97 return filenames
98
99
81 def str2bool(_str):
100 def str2bool(_str):
82 """
101 """
83 returs True/False value from given string, it tries to translate the
102 returs True/False value from given string, it tries to translate the
84 string into boolean
103 string into boolean
85
104
86 :param _str: string value to translate into boolean
105 :param _str: string value to translate into boolean
87 :rtype: boolean
106 :rtype: boolean
88 :returns: boolean from given string
107 :returns: boolean from given string
89 """
108 """
90 if _str is None:
109 if _str is None:
91 return False
110 return False
92 if _str in (True, False):
111 if _str in (True, False):
93 return _str
112 return _str
94 _str = str(_str).strip().lower()
113 _str = str(_str).strip().lower()
95 return _str in ('t', 'true', 'y', 'yes', 'on', '1')
114 return _str in ('t', 'true', 'y', 'yes', 'on', '1')
96
115
97
116
98 def aslist(obj, sep=None, strip=True):
117 def aslist(obj, sep=None, strip=True):
99 """
118 """
100 Returns given string separated by sep as list
119 Returns given string separated by sep as list
101
120
102 :param obj:
121 :param obj:
103 :param sep:
122 :param sep:
104 :param strip:
123 :param strip:
105 """
124 """
106 if isinstance(obj, (basestring)):
125 if isinstance(obj, (basestring)):
107 lst = obj.split(sep)
126 lst = obj.split(sep)
108 if strip:
127 if strip:
109 lst = [v.strip() for v in lst]
128 lst = [v.strip() for v in lst]
110 return lst
129 return lst
111 elif isinstance(obj, (list, tuple)):
130 elif isinstance(obj, (list, tuple)):
112 return obj
131 return obj
113 elif obj is None:
132 elif obj is None:
114 return []
133 return []
115 else:
134 else:
116 return [obj]
135 return [obj]
117
136
118
137
119 def convert_line_endings(line, mode):
138 def convert_line_endings(line, mode):
120 """
139 """
121 Converts a given line "line end" according to given mode
140 Converts a given line "line end" according to given mode
122
141
123 Available modes are::
142 Available modes are::
124 0 - Unix
143 0 - Unix
125 1 - Mac
144 1 - Mac
126 2 - DOS
145 2 - DOS
127
146
128 :param line: given line to convert
147 :param line: given line to convert
129 :param mode: mode to convert to
148 :param mode: mode to convert to
130 :rtype: str
149 :rtype: str
131 :return: converted line according to mode
150 :return: converted line according to mode
132 """
151 """
133 from string import replace
152 from string import replace
134
153
135 if mode == 0:
154 if mode == 0:
136 line = replace(line, '\r\n', '\n')
155 line = replace(line, '\r\n', '\n')
137 line = replace(line, '\r', '\n')
156 line = replace(line, '\r', '\n')
138 elif mode == 1:
157 elif mode == 1:
139 line = replace(line, '\r\n', '\r')
158 line = replace(line, '\r\n', '\r')
140 line = replace(line, '\n', '\r')
159 line = replace(line, '\n', '\r')
141 elif mode == 2:
160 elif mode == 2:
142 line = re.sub("\r(?!\n)|(?<!\r)\n", "\r\n", line)
161 line = re.sub("\r(?!\n)|(?<!\r)\n", "\r\n", line)
143 return line
162 return line
144
163
145
164
146 def detect_mode(line, default):
165 def detect_mode(line, default):
147 """
166 """
148 Detects line break for given line, if line break couldn't be found
167 Detects line break for given line, if line break couldn't be found
149 given default value is returned
168 given default value is returned
150
169
151 :param line: str line
170 :param line: str line
152 :param default: default
171 :param default: default
153 :rtype: int
172 :rtype: int
154 :return: value of line end on of 0 - Unix, 1 - Mac, 2 - DOS
173 :return: value of line end on of 0 - Unix, 1 - Mac, 2 - DOS
155 """
174 """
156 if line.endswith('\r\n'):
175 if line.endswith('\r\n'):
157 return 2
176 return 2
158 elif line.endswith('\n'):
177 elif line.endswith('\n'):
159 return 0
178 return 0
160 elif line.endswith('\r'):
179 elif line.endswith('\r'):
161 return 1
180 return 1
162 else:
181 else:
163 return default
182 return default
164
183
165
184
166 def generate_api_key():
185 def generate_api_key():
167 """
186 """
168 Generates a random (presumably unique) API key.
187 Generates a random (presumably unique) API key.
169 """
188 """
170 return binascii.hexlify(os.urandom(20))
189 return binascii.hexlify(os.urandom(20))
171
190
172
191
173 def safe_int(val, default=None):
192 def safe_int(val, default=None):
174 """
193 """
175 Returns int() of val if val is not convertable to int use default
194 Returns int() of val if val is not convertable to int use default
176 instead
195 instead
177
196
178 :param val:
197 :param val:
179 :param default:
198 :param default:
180 """
199 """
181
200
182 try:
201 try:
183 val = int(val)
202 val = int(val)
184 except (ValueError, TypeError):
203 except (ValueError, TypeError):
185 val = default
204 val = default
186
205
187 return val
206 return val
188
207
189
208
190 def safe_unicode(str_, from_encoding=None):
209 def safe_unicode(str_, from_encoding=None):
191 """
210 """
192 safe unicode function. Does few trick to turn str_ into unicode
211 safe unicode function. Does few trick to turn str_ into unicode
193
212
194 In case of UnicodeDecode error we try to return it with encoding detected
213 In case of UnicodeDecode error we try to return it with encoding detected
195 by chardet library if it fails fallback to unicode with errors replaced
214 by chardet library if it fails fallback to unicode with errors replaced
196
215
197 :param str_: string to decode
216 :param str_: string to decode
198 :rtype: unicode
217 :rtype: unicode
199 :returns: unicode object
218 :returns: unicode object
200 """
219 """
201 if isinstance(str_, unicode):
220 if isinstance(str_, unicode):
202 return str_
221 return str_
203
222
204 if not from_encoding:
223 if not from_encoding:
205 import kallithea
224 import kallithea
206 DEFAULT_ENCODINGS = aslist(kallithea.CONFIG.get('default_encoding',
225 DEFAULT_ENCODINGS = aslist(kallithea.CONFIG.get('default_encoding',
207 'utf8'), sep=',')
226 'utf8'), sep=',')
208 from_encoding = DEFAULT_ENCODINGS
227 from_encoding = DEFAULT_ENCODINGS
209
228
210 if not isinstance(from_encoding, (list, tuple)):
229 if not isinstance(from_encoding, (list, tuple)):
211 from_encoding = [from_encoding]
230 from_encoding = [from_encoding]
212
231
213 try:
232 try:
214 return unicode(str_)
233 return unicode(str_)
215 except UnicodeDecodeError:
234 except UnicodeDecodeError:
216 pass
235 pass
217
236
218 for enc in from_encoding:
237 for enc in from_encoding:
219 try:
238 try:
220 return unicode(str_, enc)
239 return unicode(str_, enc)
221 except UnicodeDecodeError:
240 except UnicodeDecodeError:
222 pass
241 pass
223
242
224 try:
243 try:
225 import chardet
244 import chardet
226 encoding = chardet.detect(str_)['encoding']
245 encoding = chardet.detect(str_)['encoding']
227 if encoding is None:
246 if encoding is None:
228 raise Exception()
247 raise Exception()
229 return str_.decode(encoding)
248 return str_.decode(encoding)
230 except (ImportError, UnicodeDecodeError, Exception):
249 except (ImportError, UnicodeDecodeError, Exception):
231 return unicode(str_, from_encoding[0], 'replace')
250 return unicode(str_, from_encoding[0], 'replace')
232
251
233
252
234 def safe_str(unicode_, to_encoding=None):
253 def safe_str(unicode_, to_encoding=None):
235 """
254 """
236 safe str function. Does few trick to turn unicode_ into string
255 safe str function. Does few trick to turn unicode_ into string
237
256
238 In case of UnicodeEncodeError we try to return it with encoding detected
257 In case of UnicodeEncodeError we try to return it with encoding detected
239 by chardet library if it fails fallback to string with errors replaced
258 by chardet library if it fails fallback to string with errors replaced
240
259
241 :param unicode_: unicode to encode
260 :param unicode_: unicode to encode
242 :rtype: str
261 :rtype: str
243 :returns: str object
262 :returns: str object
244 """
263 """
245
264
246 # if it's not basestr cast to str
265 # if it's not basestr cast to str
247 if not isinstance(unicode_, basestring):
266 if not isinstance(unicode_, basestring):
248 return str(unicode_)
267 return str(unicode_)
249
268
250 if isinstance(unicode_, str):
269 if isinstance(unicode_, str):
251 return unicode_
270 return unicode_
252
271
253 if not to_encoding:
272 if not to_encoding:
254 import kallithea
273 import kallithea
255 DEFAULT_ENCODINGS = aslist(kallithea.CONFIG.get('default_encoding',
274 DEFAULT_ENCODINGS = aslist(kallithea.CONFIG.get('default_encoding',
256 'utf8'), sep=',')
275 'utf8'), sep=',')
257 to_encoding = DEFAULT_ENCODINGS
276 to_encoding = DEFAULT_ENCODINGS
258
277
259 if not isinstance(to_encoding, (list, tuple)):
278 if not isinstance(to_encoding, (list, tuple)):
260 to_encoding = [to_encoding]
279 to_encoding = [to_encoding]
261
280
262 for enc in to_encoding:
281 for enc in to_encoding:
263 try:
282 try:
264 return unicode_.encode(enc)
283 return unicode_.encode(enc)
265 except UnicodeEncodeError:
284 except UnicodeEncodeError:
266 pass
285 pass
267
286
268 try:
287 try:
269 import chardet
288 import chardet
270 encoding = chardet.detect(unicode_)['encoding']
289 encoding = chardet.detect(unicode_)['encoding']
271 if encoding is None:
290 if encoding is None:
272 raise UnicodeEncodeError()
291 raise UnicodeEncodeError()
273
292
274 return unicode_.encode(encoding)
293 return unicode_.encode(encoding)
275 except (ImportError, UnicodeEncodeError):
294 except (ImportError, UnicodeEncodeError):
276 return unicode_.encode(to_encoding[0], 'replace')
295 return unicode_.encode(to_encoding[0], 'replace')
277
296
278
297
279 def remove_suffix(s, suffix):
298 def remove_suffix(s, suffix):
280 if s.endswith(suffix):
299 if s.endswith(suffix):
281 s = s[:-1 * len(suffix)]
300 s = s[:-1 * len(suffix)]
282 return s
301 return s
283
302
284
303
285 def remove_prefix(s, prefix):
304 def remove_prefix(s, prefix):
286 if s.startswith(prefix):
305 if s.startswith(prefix):
287 s = s[len(prefix):]
306 s = s[len(prefix):]
288 return s
307 return s
289
308
290
309
291 def engine_from_config(configuration, prefix='sqlalchemy.', **kwargs):
310 def engine_from_config(configuration, prefix='sqlalchemy.', **kwargs):
292 """
311 """
293 Custom engine_from_config functions that makes sure we use NullPool for
312 Custom engine_from_config functions that makes sure we use NullPool for
294 file based sqlite databases. This prevents errors on sqlite. This only
313 file based sqlite databases. This prevents errors on sqlite. This only
295 applies to sqlalchemy versions < 0.7.0
314 applies to sqlalchemy versions < 0.7.0
296
315
297 """
316 """
298 import sqlalchemy
317 import sqlalchemy
299 from sqlalchemy import engine_from_config as efc
318 from sqlalchemy import engine_from_config as efc
300 import logging
319 import logging
301
320
302 if int(sqlalchemy.__version__.split('.')[1]) < 7:
321 if int(sqlalchemy.__version__.split('.')[1]) < 7:
303
322
304 # This solution should work for sqlalchemy < 0.7.0, and should use
323 # This solution should work for sqlalchemy < 0.7.0, and should use
305 # proxy=TimerProxy() for execution time profiling
324 # proxy=TimerProxy() for execution time profiling
306
325
307 from sqlalchemy.pool import NullPool
326 from sqlalchemy.pool import NullPool
308 url = configuration[prefix + 'url']
327 url = configuration[prefix + 'url']
309
328
310 if url.startswith('sqlite'):
329 if url.startswith('sqlite'):
311 kwargs.update({'poolclass': NullPool})
330 kwargs.update({'poolclass': NullPool})
312 return efc(configuration, prefix, **kwargs)
331 return efc(configuration, prefix, **kwargs)
313 else:
332 else:
314 import time
333 import time
315 from sqlalchemy import event
334 from sqlalchemy import event
316
335
317 log = logging.getLogger('sqlalchemy.engine')
336 log = logging.getLogger('sqlalchemy.engine')
318 BLACK, RED, GREEN, YELLOW, BLUE, MAGENTA, CYAN, WHITE = xrange(30, 38)
337 BLACK, RED, GREEN, YELLOW, BLUE, MAGENTA, CYAN, WHITE = xrange(30, 38)
319 engine = efc(configuration, prefix, **kwargs)
338 engine = efc(configuration, prefix, **kwargs)
320
339
321 def color_sql(sql):
340 def color_sql(sql):
322 COLOR_SEQ = "\033[1;%dm"
341 COLOR_SEQ = "\033[1;%dm"
323 COLOR_SQL = YELLOW
342 COLOR_SQL = YELLOW
324 normal = '\x1b[0m'
343 normal = '\x1b[0m'
325 return ''.join([COLOR_SEQ % COLOR_SQL, sql, normal])
344 return ''.join([COLOR_SEQ % COLOR_SQL, sql, normal])
326
345
327 if configuration['debug']:
346 if configuration['debug']:
328 #attach events only for debug configuration
347 #attach events only for debug configuration
329
348
330 def before_cursor_execute(conn, cursor, statement,
349 def before_cursor_execute(conn, cursor, statement,
331 parameters, context, executemany):
350 parameters, context, executemany):
332 context._query_start_time = time.time()
351 context._query_start_time = time.time()
333 log.info(color_sql(">>>>> STARTING QUERY >>>>>"))
352 log.info(color_sql(">>>>> STARTING QUERY >>>>>"))
334
353
335 def after_cursor_execute(conn, cursor, statement,
354 def after_cursor_execute(conn, cursor, statement,
336 parameters, context, executemany):
355 parameters, context, executemany):
337 total = time.time() - context._query_start_time
356 total = time.time() - context._query_start_time
338 log.info(color_sql("<<<<< TOTAL TIME: %f <<<<<" % total))
357 log.info(color_sql("<<<<< TOTAL TIME: %f <<<<<" % total))
339
358
340 event.listen(engine, "before_cursor_execute",
359 event.listen(engine, "before_cursor_execute",
341 before_cursor_execute)
360 before_cursor_execute)
342 event.listen(engine, "after_cursor_execute",
361 event.listen(engine, "after_cursor_execute",
343 after_cursor_execute)
362 after_cursor_execute)
344
363
345 return engine
364 return engine
346
365
347
366
348 def age(prevdate, show_short_version=False, now=None):
367 def age(prevdate, show_short_version=False, now=None):
349 """
368 """
350 turns a datetime into an age string.
369 turns a datetime into an age string.
351 If show_short_version is True, then it will generate a not so accurate but shorter string,
370 If show_short_version is True, then it will generate a not so accurate but shorter string,
352 example: 2days ago, instead of 2 days and 23 hours ago.
371 example: 2days ago, instead of 2 days and 23 hours ago.
353
372
354 :param prevdate: datetime object
373 :param prevdate: datetime object
355 :param show_short_version: if it should aproximate the date and return a shorter string
374 :param show_short_version: if it should aproximate the date and return a shorter string
356 :rtype: unicode
375 :rtype: unicode
357 :returns: unicode words describing age
376 :returns: unicode words describing age
358 """
377 """
359 now = now or datetime.datetime.now()
378 now = now or datetime.datetime.now()
360 order = ['year', 'month', 'day', 'hour', 'minute', 'second']
379 order = ['year', 'month', 'day', 'hour', 'minute', 'second']
361 deltas = {}
380 deltas = {}
362 future = False
381 future = False
363
382
364 if prevdate > now:
383 if prevdate > now:
365 now, prevdate = prevdate, now
384 now, prevdate = prevdate, now
366 future = True
385 future = True
367 if future:
386 if future:
368 prevdate = prevdate.replace(microsecond=0)
387 prevdate = prevdate.replace(microsecond=0)
369 # Get date parts deltas
388 # Get date parts deltas
370 from dateutil import relativedelta
389 from dateutil import relativedelta
371 for part in order:
390 for part in order:
372 d = relativedelta.relativedelta(now, prevdate)
391 d = relativedelta.relativedelta(now, prevdate)
373 deltas[part] = getattr(d, part + 's')
392 deltas[part] = getattr(d, part + 's')
374
393
375 # Fix negative offsets (there is 1 second between 10:59:59 and 11:00:00,
394 # Fix negative offsets (there is 1 second between 10:59:59 and 11:00:00,
376 # not 1 hour, -59 minutes and -59 seconds)
395 # not 1 hour, -59 minutes and -59 seconds)
377 for num, length in [(5, 60), (4, 60), (3, 24)]: # seconds, minutes, hours
396 for num, length in [(5, 60), (4, 60), (3, 24)]: # seconds, minutes, hours
378 part = order[num]
397 part = order[num]
379 carry_part = order[num - 1]
398 carry_part = order[num - 1]
380
399
381 if deltas[part] < 0:
400 if deltas[part] < 0:
382 deltas[part] += length
401 deltas[part] += length
383 deltas[carry_part] -= 1
402 deltas[carry_part] -= 1
384
403
385 # Same thing for days except that the increment depends on the (variable)
404 # Same thing for days except that the increment depends on the (variable)
386 # number of days in the month
405 # number of days in the month
387 month_lengths = [31, 28, 31, 30, 31, 30, 31, 31, 30, 31, 30, 31]
406 month_lengths = [31, 28, 31, 30, 31, 30, 31, 31, 30, 31, 30, 31]
388 if deltas['day'] < 0:
407 if deltas['day'] < 0:
389 if prevdate.month == 2 and (prevdate.year % 4 == 0 and
408 if prevdate.month == 2 and (prevdate.year % 4 == 0 and
390 (prevdate.year % 100 != 0 or prevdate.year % 400 == 0)):
409 (prevdate.year % 100 != 0 or prevdate.year % 400 == 0)):
391 deltas['day'] += 29
410 deltas['day'] += 29
392 else:
411 else:
393 deltas['day'] += month_lengths[prevdate.month - 1]
412 deltas['day'] += month_lengths[prevdate.month - 1]
394
413
395 deltas['month'] -= 1
414 deltas['month'] -= 1
396
415
397 if deltas['month'] < 0:
416 if deltas['month'] < 0:
398 deltas['month'] += 12
417 deltas['month'] += 12
399 deltas['year'] -= 1
418 deltas['year'] -= 1
400
419
401 # In short version, we want nicer handling of ages of more than a year
420 # In short version, we want nicer handling of ages of more than a year
402 if show_short_version:
421 if show_short_version:
403 if deltas['year'] == 1:
422 if deltas['year'] == 1:
404 # ages between 1 and 2 years: show as months
423 # ages between 1 and 2 years: show as months
405 deltas['month'] += 12
424 deltas['month'] += 12
406 deltas['year'] = 0
425 deltas['year'] = 0
407 if deltas['year'] >= 2:
426 if deltas['year'] >= 2:
408 # ages 2+ years: round
427 # ages 2+ years: round
409 if deltas['month'] > 6:
428 if deltas['month'] > 6:
410 deltas['year'] += 1
429 deltas['year'] += 1
411 deltas['month'] = 0
430 deltas['month'] = 0
412
431
413 # Format the result
432 # Format the result
414 fmt_funcs = {
433 fmt_funcs = {
415 'year': lambda d: ungettext(u'%d year', '%d years', d) % d,
434 'year': lambda d: ungettext(u'%d year', '%d years', d) % d,
416 'month': lambda d: ungettext(u'%d month', '%d months', d) % d,
435 'month': lambda d: ungettext(u'%d month', '%d months', d) % d,
417 'day': lambda d: ungettext(u'%d day', '%d days', d) % d,
436 'day': lambda d: ungettext(u'%d day', '%d days', d) % d,
418 'hour': lambda d: ungettext(u'%d hour', '%d hours', d) % d,
437 'hour': lambda d: ungettext(u'%d hour', '%d hours', d) % d,
419 'minute': lambda d: ungettext(u'%d minute', '%d minutes', d) % d,
438 'minute': lambda d: ungettext(u'%d minute', '%d minutes', d) % d,
420 'second': lambda d: ungettext(u'%d second', '%d seconds', d) % d,
439 'second': lambda d: ungettext(u'%d second', '%d seconds', d) % d,
421 }
440 }
422
441
423 for i, part in enumerate(order):
442 for i, part in enumerate(order):
424 value = deltas[part]
443 value = deltas[part]
425 if value == 0:
444 if value == 0:
426 continue
445 continue
427
446
428 if i < 5:
447 if i < 5:
429 sub_part = order[i + 1]
448 sub_part = order[i + 1]
430 sub_value = deltas[sub_part]
449 sub_value = deltas[sub_part]
431 else:
450 else:
432 sub_value = 0
451 sub_value = 0
433
452
434 if sub_value == 0 or show_short_version:
453 if sub_value == 0 or show_short_version:
435 if future:
454 if future:
436 return _('in %s') % fmt_funcs[part](value)
455 return _('in %s') % fmt_funcs[part](value)
437 else:
456 else:
438 return _('%s ago') % fmt_funcs[part](value)
457 return _('%s ago') % fmt_funcs[part](value)
439 if future:
458 if future:
440 return _('in %s and %s') % (fmt_funcs[part](value),
459 return _('in %s and %s') % (fmt_funcs[part](value),
441 fmt_funcs[sub_part](sub_value))
460 fmt_funcs[sub_part](sub_value))
442 else:
461 else:
443 return _('%s and %s ago') % (fmt_funcs[part](value),
462 return _('%s and %s ago') % (fmt_funcs[part](value),
444 fmt_funcs[sub_part](sub_value))
463 fmt_funcs[sub_part](sub_value))
445
464
446 return _('just now')
465 return _('just now')
447
466
448
467
449 def uri_filter(uri):
468 def uri_filter(uri):
450 """
469 """
451 Removes user:password from given url string
470 Removes user:password from given url string
452
471
453 :param uri:
472 :param uri:
454 :rtype: unicode
473 :rtype: unicode
455 :returns: filtered list of strings
474 :returns: filtered list of strings
456 """
475 """
457 if not uri:
476 if not uri:
458 return ''
477 return ''
459
478
460 proto = ''
479 proto = ''
461
480
462 for pat in ('https://', 'http://', 'git://'):
481 for pat in ('https://', 'http://', 'git://'):
463 if uri.startswith(pat):
482 if uri.startswith(pat):
464 uri = uri[len(pat):]
483 uri = uri[len(pat):]
465 proto = pat
484 proto = pat
466 break
485 break
467
486
468 # remove passwords and username
487 # remove passwords and username
469 uri = uri[uri.find('@') + 1:]
488 uri = uri[uri.find('@') + 1:]
470
489
471 # get the port
490 # get the port
472 cred_pos = uri.find(':')
491 cred_pos = uri.find(':')
473 if cred_pos == -1:
492 if cred_pos == -1:
474 host, port = uri, None
493 host, port = uri, None
475 else:
494 else:
476 host, port = uri[:cred_pos], uri[cred_pos + 1:]
495 host, port = uri[:cred_pos], uri[cred_pos + 1:]
477
496
478 return filter(None, [proto, host, port])
497 return filter(None, [proto, host, port])
479
498
480
499
481 def credentials_filter(uri):
500 def credentials_filter(uri):
482 """
501 """
483 Returns a url with removed credentials
502 Returns a url with removed credentials
484
503
485 :param uri:
504 :param uri:
486 """
505 """
487
506
488 uri = uri_filter(uri)
507 uri = uri_filter(uri)
489 #check if we have port
508 #check if we have port
490 if len(uri) > 2 and uri[2]:
509 if len(uri) > 2 and uri[2]:
491 uri[2] = ':' + uri[2]
510 uri[2] = ':' + uri[2]
492
511
493 return ''.join(uri)
512 return ''.join(uri)
494
513
495
514
496 def get_clone_url(uri_tmpl, qualified_home_url, repo_name, repo_id, **override):
515 def get_clone_url(uri_tmpl, qualified_home_url, repo_name, repo_id, **override):
497 parsed_url = urlobject.URLObject(qualified_home_url)
516 parsed_url = urlobject.URLObject(qualified_home_url)
498 decoded_path = safe_unicode(urllib.unquote(parsed_url.path.rstrip('/')))
517 decoded_path = safe_unicode(urllib.unquote(parsed_url.path.rstrip('/')))
499 args = {
518 args = {
500 'scheme': parsed_url.scheme,
519 'scheme': parsed_url.scheme,
501 'user': '',
520 'user': '',
502 'netloc': parsed_url.netloc+decoded_path, # path if we use proxy-prefix
521 'netloc': parsed_url.netloc+decoded_path, # path if we use proxy-prefix
503 'prefix': decoded_path,
522 'prefix': decoded_path,
504 'repo': repo_name,
523 'repo': repo_name,
505 'repoid': str(repo_id)
524 'repoid': str(repo_id)
506 }
525 }
507 args.update(override)
526 args.update(override)
508 args['user'] = urllib.quote(safe_str(args['user']))
527 args['user'] = urllib.quote(safe_str(args['user']))
509
528
510 for k, v in args.items():
529 for k, v in args.items():
511 uri_tmpl = uri_tmpl.replace('{%s}' % k, v)
530 uri_tmpl = uri_tmpl.replace('{%s}' % k, v)
512
531
513 # remove leading @ sign if it's present. Case of empty user
532 # remove leading @ sign if it's present. Case of empty user
514 url_obj = urlobject.URLObject(uri_tmpl)
533 url_obj = urlobject.URLObject(uri_tmpl)
515 url = url_obj.with_netloc(url_obj.netloc.lstrip('@'))
534 url = url_obj.with_netloc(url_obj.netloc.lstrip('@'))
516
535
517 return safe_unicode(url)
536 return safe_unicode(url)
518
537
519
538
520 def get_changeset_safe(repo, rev):
539 def get_changeset_safe(repo, rev):
521 """
540 """
522 Safe version of get_changeset if this changeset doesn't exists for a
541 Safe version of get_changeset if this changeset doesn't exists for a
523 repo it returns a Dummy one instead
542 repo it returns a Dummy one instead
524
543
525 :param repo:
544 :param repo:
526 :param rev:
545 :param rev:
527 """
546 """
528 from kallithea.lib.vcs.backends.base import BaseRepository
547 from kallithea.lib.vcs.backends.base import BaseRepository
529 from kallithea.lib.vcs.exceptions import RepositoryError
548 from kallithea.lib.vcs.exceptions import RepositoryError
530 from kallithea.lib.vcs.backends.base import EmptyChangeset
549 from kallithea.lib.vcs.backends.base import EmptyChangeset
531 if not isinstance(repo, BaseRepository):
550 if not isinstance(repo, BaseRepository):
532 raise Exception('You must pass an Repository '
551 raise Exception('You must pass an Repository '
533 'object as first argument got %s', type(repo))
552 'object as first argument got %s', type(repo))
534
553
535 try:
554 try:
536 cs = repo.get_changeset(rev)
555 cs = repo.get_changeset(rev)
537 except (RepositoryError, LookupError):
556 except (RepositoryError, LookupError):
538 cs = EmptyChangeset(requested_revision=rev)
557 cs = EmptyChangeset(requested_revision=rev)
539 return cs
558 return cs
540
559
541
560
542 def datetime_to_time(dt):
561 def datetime_to_time(dt):
543 if dt:
562 if dt:
544 return time.mktime(dt.timetuple())
563 return time.mktime(dt.timetuple())
545
564
546
565
547 def time_to_datetime(tm):
566 def time_to_datetime(tm):
548 if tm:
567 if tm:
549 if isinstance(tm, basestring):
568 if isinstance(tm, basestring):
550 try:
569 try:
551 tm = float(tm)
570 tm = float(tm)
552 except ValueError:
571 except ValueError:
553 return
572 return
554 return datetime.datetime.fromtimestamp(tm)
573 return datetime.datetime.fromtimestamp(tm)
555
574
556 # Must match regexp in kallithea/public/js/base.js MentionsAutoComplete()
575 # Must match regexp in kallithea/public/js/base.js MentionsAutoComplete()
557 # Check char before @ - it must not look like we are in an email addresses.
576 # Check char before @ - it must not look like we are in an email addresses.
558 # Matching is gready so we don't have to look beyond the end.
577 # Matching is gready so we don't have to look beyond the end.
559 MENTIONS_REGEX = re.compile(r'(?:^|(?<=[^a-zA-Z0-9]))@([a-zA-Z0-9][-_.a-zA-Z0-9]*[a-zA-Z0-9])')
578 MENTIONS_REGEX = re.compile(r'(?:^|(?<=[^a-zA-Z0-9]))@([a-zA-Z0-9][-_.a-zA-Z0-9]*[a-zA-Z0-9])')
560
579
561 def extract_mentioned_users(s):
580 def extract_mentioned_users(s):
562 r"""
581 r"""
563 Returns unique usernames from given string s that have @mention
582 Returns unique usernames from given string s that have @mention
564
583
565 :param s: string to get mentions
584 :param s: string to get mentions
566
585
567 >>> extract_mentioned_users('@1-2.a_X,@1234 not@not @ddd@not @n @ee @ff @gg, @gg;@hh @n\n@zz,')
586 >>> extract_mentioned_users('@1-2.a_X,@1234 not@not @ddd@not @n @ee @ff @gg, @gg;@hh @n\n@zz,')
568 ['1-2.a_X', '1234', 'ddd', 'ee', 'ff', 'gg', 'hh', 'zz']
587 ['1-2.a_X', '1234', 'ddd', 'ee', 'ff', 'gg', 'hh', 'zz']
569 """
588 """
570 usrs = set()
589 usrs = set()
571 for username in MENTIONS_REGEX.findall(s):
590 for username in MENTIONS_REGEX.findall(s):
572 usrs.add(username)
591 usrs.add(username)
573
592
574 return sorted(list(usrs), key=lambda k: k.lower())
593 return sorted(list(usrs), key=lambda k: k.lower())
575
594
576
595
577 class AttributeDict(dict):
596 class AttributeDict(dict):
578 def __getattr__(self, attr):
597 def __getattr__(self, attr):
579 return self.get(attr, None)
598 return self.get(attr, None)
580 __setattr__ = dict.__setitem__
599 __setattr__ = dict.__setitem__
581 __delattr__ = dict.__delitem__
600 __delattr__ = dict.__delitem__
582
601
583
602
584 def fix_PATH(os_=None):
603 def fix_PATH(os_=None):
585 """
604 """
586 Get current active python path, and append it to PATH variable to fix issues
605 Get current active python path, and append it to PATH variable to fix issues
587 of subprocess calls and different python versions
606 of subprocess calls and different python versions
588 """
607 """
589 if os_ is None:
608 if os_ is None:
590 import os
609 import os
591 else:
610 else:
592 os = os_
611 os = os_
593
612
594 cur_path = os.path.split(sys.executable)[0]
613 cur_path = os.path.split(sys.executable)[0]
595 if not os.environ['PATH'].startswith(cur_path):
614 if not os.environ['PATH'].startswith(cur_path):
596 os.environ['PATH'] = '%s:%s' % (cur_path, os.environ['PATH'])
615 os.environ['PATH'] = '%s:%s' % (cur_path, os.environ['PATH'])
597
616
598
617
599 def obfuscate_url_pw(engine):
618 def obfuscate_url_pw(engine):
600 from sqlalchemy.engine import url as sa_url
619 from sqlalchemy.engine import url as sa_url
601 from sqlalchemy.exc import ArgumentError
620 from sqlalchemy.exc import ArgumentError
602 try:
621 try:
603 _url = sa_url.make_url(engine or '')
622 _url = sa_url.make_url(engine or '')
604 except ArgumentError:
623 except ArgumentError:
605 return engine
624 return engine
606 if _url.password:
625 if _url.password:
607 _url.password = 'XXXXX'
626 _url.password = 'XXXXX'
608 return str(_url)
627 return str(_url)
609
628
610
629
611 def get_server_url(environ):
630 def get_server_url(environ):
612 req = webob.Request(environ)
631 req = webob.Request(environ)
613 return req.host_url + req.script_name
632 return req.host_url + req.script_name
614
633
615
634
616 def _extract_extras(env=None):
635 def _extract_extras(env=None):
617 """
636 """
618 Extracts the Kallithea extras data from os.environ, and wraps it into named
637 Extracts the Kallithea extras data from os.environ, and wraps it into named
619 AttributeDict object
638 AttributeDict object
620 """
639 """
621 if not env:
640 if not env:
622 env = os.environ
641 env = os.environ
623
642
624 try:
643 try:
625 extras = json.loads(env['KALLITHEA_EXTRAS'])
644 extras = json.loads(env['KALLITHEA_EXTRAS'])
626 except KeyError:
645 except KeyError:
627 extras = {}
646 extras = {}
628
647
629 try:
648 try:
630 for k in ['username', 'repository', 'locked_by', 'scm', 'make_lock',
649 for k in ['username', 'repository', 'locked_by', 'scm', 'make_lock',
631 'action', 'ip']:
650 'action', 'ip']:
632 extras[k]
651 extras[k]
633 except KeyError as e:
652 except KeyError as e:
634 raise Exception('Missing key %s in os.environ %s' % (e, extras))
653 raise Exception('Missing key %s in os.environ %s' % (e, extras))
635
654
636 return AttributeDict(extras)
655 return AttributeDict(extras)
637
656
638
657
639 def _set_extras(extras):
658 def _set_extras(extras):
640 # RC_SCM_DATA can probably be removed in the future, but for compatibilty now...
659 # RC_SCM_DATA can probably be removed in the future, but for compatibilty now...
641 os.environ['KALLITHEA_EXTRAS'] = os.environ['RC_SCM_DATA'] = json.dumps(extras)
660 os.environ['KALLITHEA_EXTRAS'] = os.environ['RC_SCM_DATA'] = json.dumps(extras)
642
661
643
662
644 def unique_id(hexlen=32):
663 def unique_id(hexlen=32):
645 alphabet = "23456789ABCDEFGHJKLMNPQRSTUVWXYZabcdefghjklmnpqrstuvwxyz"
664 alphabet = "23456789ABCDEFGHJKLMNPQRSTUVWXYZabcdefghjklmnpqrstuvwxyz"
646 return suuid(truncate_to=hexlen, alphabet=alphabet)
665 return suuid(truncate_to=hexlen, alphabet=alphabet)
647
666
648
667
649 def suuid(url=None, truncate_to=22, alphabet=None):
668 def suuid(url=None, truncate_to=22, alphabet=None):
650 """
669 """
651 Generate and return a short URL safe UUID.
670 Generate and return a short URL safe UUID.
652
671
653 If the url parameter is provided, set the namespace to the provided
672 If the url parameter is provided, set the namespace to the provided
654 URL and generate a UUID.
673 URL and generate a UUID.
655
674
656 :param url to get the uuid for
675 :param url to get the uuid for
657 :truncate_to: truncate the basic 22 UUID to shorter version
676 :truncate_to: truncate the basic 22 UUID to shorter version
658
677
659 The IDs won't be universally unique any longer, but the probability of
678 The IDs won't be universally unique any longer, but the probability of
660 a collision will still be very low.
679 a collision will still be very low.
661 """
680 """
662 # Define our alphabet.
681 # Define our alphabet.
663 _ALPHABET = alphabet or "23456789ABCDEFGHJKLMNPQRSTUVWXYZ"
682 _ALPHABET = alphabet or "23456789ABCDEFGHJKLMNPQRSTUVWXYZ"
664
683
665 # If no URL is given, generate a random UUID.
684 # If no URL is given, generate a random UUID.
666 if url is None:
685 if url is None:
667 unique_id = uuid.uuid4().int
686 unique_id = uuid.uuid4().int
668 else:
687 else:
669 unique_id = uuid.uuid3(uuid.NAMESPACE_URL, url).int
688 unique_id = uuid.uuid3(uuid.NAMESPACE_URL, url).int
670
689
671 alphabet_length = len(_ALPHABET)
690 alphabet_length = len(_ALPHABET)
672 output = []
691 output = []
673 while unique_id > 0:
692 while unique_id > 0:
674 digit = unique_id % alphabet_length
693 digit = unique_id % alphabet_length
675 output.append(_ALPHABET[digit])
694 output.append(_ALPHABET[digit])
676 unique_id = int(unique_id / alphabet_length)
695 unique_id = int(unique_id / alphabet_length)
677 return "".join(output)[:truncate_to]
696 return "".join(output)[:truncate_to]
678
697
679
698
680 def get_current_authuser():
699 def get_current_authuser():
681 """
700 """
682 Gets kallithea user from threadlocal tmpl_context variable if it's
701 Gets kallithea user from threadlocal tmpl_context variable if it's
683 defined, else returns None.
702 defined, else returns None.
684 """
703 """
685 from pylons import tmpl_context
704 from pylons import tmpl_context
686 if hasattr(tmpl_context, 'authuser'):
705 if hasattr(tmpl_context, 'authuser'):
687 return tmpl_context.authuser
706 return tmpl_context.authuser
688
707
689 return None
708 return None
690
709
691
710
692 class OptionalAttr(object):
711 class OptionalAttr(object):
693 """
712 """
694 Special Optional Option that defines other attribute. Example::
713 Special Optional Option that defines other attribute. Example::
695
714
696 def test(apiuser, userid=Optional(OAttr('apiuser')):
715 def test(apiuser, userid=Optional(OAttr('apiuser')):
697 user = Optional.extract(userid)
716 user = Optional.extract(userid)
698 # calls
717 # calls
699
718
700 """
719 """
701
720
702 def __init__(self, attr_name):
721 def __init__(self, attr_name):
703 self.attr_name = attr_name
722 self.attr_name = attr_name
704
723
705 def __repr__(self):
724 def __repr__(self):
706 return '<OptionalAttr:%s>' % self.attr_name
725 return '<OptionalAttr:%s>' % self.attr_name
707
726
708 def __call__(self):
727 def __call__(self):
709 return self
728 return self
710
729
711 #alias
730 #alias
712 OAttr = OptionalAttr
731 OAttr = OptionalAttr
713
732
714
733
715 class Optional(object):
734 class Optional(object):
716 """
735 """
717 Defines an optional parameter::
736 Defines an optional parameter::
718
737
719 param = param.getval() if isinstance(param, Optional) else param
738 param = param.getval() if isinstance(param, Optional) else param
720 param = param() if isinstance(param, Optional) else param
739 param = param() if isinstance(param, Optional) else param
721
740
722 is equivalent of::
741 is equivalent of::
723
742
724 param = Optional.extract(param)
743 param = Optional.extract(param)
725
744
726 """
745 """
727
746
728 def __init__(self, type_):
747 def __init__(self, type_):
729 self.type_ = type_
748 self.type_ = type_
730
749
731 def __repr__(self):
750 def __repr__(self):
732 return '<Optional:%s>' % self.type_.__repr__()
751 return '<Optional:%s>' % self.type_.__repr__()
733
752
734 def __call__(self):
753 def __call__(self):
735 return self.getval()
754 return self.getval()
736
755
737 def getval(self):
756 def getval(self):
738 """
757 """
739 returns value from this Optional instance
758 returns value from this Optional instance
740 """
759 """
741 if isinstance(self.type_, OAttr):
760 if isinstance(self.type_, OAttr):
742 # use params name
761 # use params name
743 return self.type_.attr_name
762 return self.type_.attr_name
744 return self.type_
763 return self.type_
745
764
746 @classmethod
765 @classmethod
747 def extract(cls, val):
766 def extract(cls, val):
748 """
767 """
749 Extracts value from Optional() instance
768 Extracts value from Optional() instance
750
769
751 :param val:
770 :param val:
752 :return: original value if it's not Optional instance else
771 :return: original value if it's not Optional instance else
753 value of instance
772 value of instance
754 """
773 """
755 if isinstance(val, cls):
774 if isinstance(val, cls):
756 return val.getval()
775 return val.getval()
757 return val
776 return val
758
777
759 def urlreadable(s, _cleanstringsub=re.compile('[^-a-zA-Z0-9./]+').sub):
778 def urlreadable(s, _cleanstringsub=re.compile('[^-a-zA-Z0-9./]+').sub):
760 return _cleanstringsub('_', safe_str(s)).rstrip('_')
779 return _cleanstringsub('_', safe_str(s)).rstrip('_')
General Comments 0
You need to be logged in to leave comments. Login now