##// END OF EJS Templates
Added implementation of Ordered Dict.
marcink -
r358:23e720be default
parent child Browse files
Show More
@@ -1,228 +1,329 b''
1 #!/usr/bin/env python
1 #!/usr/bin/env python
2 # encoding: utf-8
2 # encoding: utf-8
3 # Utilities for hg app
3 # Utilities for hg app
4 # Copyright (C) 2009-2010 Marcin Kuzminski <marcin@python-works.com>
4 # Copyright (C) 2009-2010 Marcin Kuzminski <marcin@python-works.com>
5 # This program is free software; you can redistribute it and/or
5 # This program is free software; you can redistribute it and/or
6 # modify it under the terms of the GNU General Public License
6 # modify it under the terms of the GNU General Public License
7 # as published by the Free Software Foundation; version 2
7 # as published by the Free Software Foundation; version 2
8 # of the License or (at your opinion) any later version of the license.
8 # of the License or (at your opinion) any later version of the license.
9 #
9 #
10 # This program is distributed in the hope that it will be useful,
10 # This program is distributed in the hope that it will be useful,
11 # but WITHOUT ANY WARRANTY; without even the implied warranty of
11 # but WITHOUT ANY WARRANTY; without even the implied warranty of
12 # MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
12 # MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
13 # GNU General Public License for more details.
13 # GNU General Public License for more details.
14 #
14 #
15 # You should have received a copy of the GNU General Public License
15 # You should have received a copy of the GNU General Public License
16 # along with this program; if not, write to the Free Software
16 # along with this program; if not, write to the Free Software
17 # Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston,
17 # Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston,
18 # MA 02110-1301, USA.
18 # MA 02110-1301, USA.
19
19
20 """
20 """
21 Created on April 18, 2010
21 Created on April 18, 2010
22 Utilities for hg app
22 Utilities for hg app
23 @author: marcink
23 @author: marcink
24 """
24 """
25 from beaker.cache import cache_region
25 from beaker.cache import cache_region
26 from mercurial import ui, config, hg
26 from mercurial import ui, config, hg
27 from mercurial.error import RepoError
27 from mercurial.error import RepoError
28 from pylons_app.model import meta
28 from pylons_app.model import meta
29 from pylons_app.model.db import Repository, User, HgAppUi, HgAppSettings
29 from pylons_app.model.db import Repository, User, HgAppUi, HgAppSettings
30 from vcs.backends.base import BaseChangeset
30 from vcs.backends.base import BaseChangeset
31 from vcs.utils.lazy import LazyProperty
31 from vcs.utils.lazy import LazyProperty
32 import logging
32 import logging
33 import os
33 import os
34 log = logging.getLogger(__name__)
34 log = logging.getLogger(__name__)
35
35
36
36
37 def get_repo_slug(request):
37 def get_repo_slug(request):
38 return request.environ['pylons.routes_dict'].get('repo_name')
38 return request.environ['pylons.routes_dict'].get('repo_name')
39
39
40 def is_mercurial(environ):
40 def is_mercurial(environ):
41 """
41 """
42 Returns True if request's target is mercurial server - header
42 Returns True if request's target is mercurial server - header
43 ``HTTP_ACCEPT`` of such request would start with ``application/mercurial``.
43 ``HTTP_ACCEPT`` of such request would start with ``application/mercurial``.
44 """
44 """
45 http_accept = environ.get('HTTP_ACCEPT')
45 http_accept = environ.get('HTTP_ACCEPT')
46 if http_accept and http_accept.startswith('application/mercurial'):
46 if http_accept and http_accept.startswith('application/mercurial'):
47 return True
47 return True
48 return False
48 return False
49
49
50 def check_repo_dir(paths):
50 def check_repo_dir(paths):
51 repos_path = paths[0][1].split('/')
51 repos_path = paths[0][1].split('/')
52 if repos_path[-1] in ['*', '**']:
52 if repos_path[-1] in ['*', '**']:
53 repos_path = repos_path[:-1]
53 repos_path = repos_path[:-1]
54 if repos_path[0] != '/':
54 if repos_path[0] != '/':
55 repos_path[0] = '/'
55 repos_path[0] = '/'
56 if not os.path.isdir(os.path.join(*repos_path)):
56 if not os.path.isdir(os.path.join(*repos_path)):
57 raise Exception('Not a valid repository in %s' % paths[0][1])
57 raise Exception('Not a valid repository in %s' % paths[0][1])
58
58
59 def check_repo_fast(repo_name, base_path):
59 def check_repo_fast(repo_name, base_path):
60 if os.path.isdir(os.path.join(base_path, repo_name)):return False
60 if os.path.isdir(os.path.join(base_path, repo_name)):return False
61 return True
61 return True
62
62
63 def check_repo(repo_name, base_path, verify=True):
63 def check_repo(repo_name, base_path, verify=True):
64
64
65 repo_path = os.path.join(base_path, repo_name)
65 repo_path = os.path.join(base_path, repo_name)
66
66
67 try:
67 try:
68 if not check_repo_fast(repo_name, base_path):
68 if not check_repo_fast(repo_name, base_path):
69 return False
69 return False
70 r = hg.repository(ui.ui(), repo_path)
70 r = hg.repository(ui.ui(), repo_path)
71 if verify:
71 if verify:
72 hg.verify(r)
72 hg.verify(r)
73 #here we hnow that repo exists it was verified
73 #here we hnow that repo exists it was verified
74 log.info('%s repo is already created', repo_name)
74 log.info('%s repo is already created', repo_name)
75 return False
75 return False
76 except RepoError:
76 except RepoError:
77 #it means that there is no valid repo there...
77 #it means that there is no valid repo there...
78 log.info('%s repo is free for creation', repo_name)
78 log.info('%s repo is free for creation', repo_name)
79 return True
79 return True
80
80
81 def ask_ok(prompt, retries=4, complaint='Yes or no, please!'):
81 def ask_ok(prompt, retries=4, complaint='Yes or no, please!'):
82 while True:
82 while True:
83 ok = raw_input(prompt)
83 ok = raw_input(prompt)
84 if ok in ('y', 'ye', 'yes'): return True
84 if ok in ('y', 'ye', 'yes'): return True
85 if ok in ('n', 'no', 'nop', 'nope'): return False
85 if ok in ('n', 'no', 'nop', 'nope'): return False
86 retries = retries - 1
86 retries = retries - 1
87 if retries < 0: raise IOError
87 if retries < 0: raise IOError
88 print complaint
88 print complaint
89
89
90 @cache_region('super_short_term', 'cached_hg_ui')
90 @cache_region('super_short_term', 'cached_hg_ui')
91 def get_hg_ui_cached():
91 def get_hg_ui_cached():
92 try:
92 try:
93 sa = meta.Session
93 sa = meta.Session
94 ret = sa.query(HgAppUi).all()
94 ret = sa.query(HgAppUi).all()
95 finally:
95 finally:
96 meta.Session.remove()
96 meta.Session.remove()
97 return ret
97 return ret
98
98
99
99
100 def get_hg_settings():
100 def get_hg_settings():
101 try:
101 try:
102 sa = meta.Session
102 sa = meta.Session
103 ret = sa.query(HgAppSettings).scalar()
103 ret = sa.query(HgAppSettings).scalar()
104 finally:
104 finally:
105 meta.Session.remove()
105 meta.Session.remove()
106
106
107 if not ret:
107 if not ret:
108 raise Exception('Could not get application settings !')
108 raise Exception('Could not get application settings !')
109 return ret
109 return ret
110
110
111 def make_ui(read_from='file', path=None, checkpaths=True):
111 def make_ui(read_from='file', path=None, checkpaths=True):
112 """
112 """
113 A function that will read python rc files or database
113 A function that will read python rc files or database
114 and make an mercurial ui object from read options
114 and make an mercurial ui object from read options
115
115
116 @param path: path to mercurial config file
116 @param path: path to mercurial config file
117 @param checkpaths: check the path
117 @param checkpaths: check the path
118 @param read_from: read from 'file' or 'db'
118 @param read_from: read from 'file' or 'db'
119 """
119 """
120 #propagated from mercurial documentation
120 #propagated from mercurial documentation
121 sections = ['alias', 'auth',
121 sections = ['alias', 'auth',
122 'decode/encode', 'defaults',
122 'decode/encode', 'defaults',
123 'diff', 'email',
123 'diff', 'email',
124 'extensions', 'format',
124 'extensions', 'format',
125 'merge-patterns', 'merge-tools',
125 'merge-patterns', 'merge-tools',
126 'hooks', 'http_proxy',
126 'hooks', 'http_proxy',
127 'smtp', 'patch',
127 'smtp', 'patch',
128 'paths', 'profiling',
128 'paths', 'profiling',
129 'server', 'trusted',
129 'server', 'trusted',
130 'ui', 'web', ]
130 'ui', 'web', ]
131 baseui = ui.ui()
131 baseui = ui.ui()
132
132
133
133
134 if read_from == 'file':
134 if read_from == 'file':
135 if not os.path.isfile(path):
135 if not os.path.isfile(path):
136 log.warning('Unable to read config file %s' % path)
136 log.warning('Unable to read config file %s' % path)
137 return False
137 return False
138
138
139 cfg = config.config()
139 cfg = config.config()
140 cfg.read(path)
140 cfg.read(path)
141 for section in sections:
141 for section in sections:
142 for k, v in cfg.items(section):
142 for k, v in cfg.items(section):
143 baseui.setconfig(section, k, v)
143 baseui.setconfig(section, k, v)
144 if checkpaths:check_repo_dir(cfg.items('paths'))
144 if checkpaths:check_repo_dir(cfg.items('paths'))
145
145
146
146
147 elif read_from == 'db':
147 elif read_from == 'db':
148 hg_ui = get_hg_ui_cached()
148 hg_ui = get_hg_ui_cached()
149 for ui_ in hg_ui:
149 for ui_ in hg_ui:
150 baseui.setconfig(ui_.ui_section, ui_.ui_key, ui_.ui_value)
150 baseui.setconfig(ui_.ui_section, ui_.ui_key, ui_.ui_value)
151
151
152
152
153 return baseui
153 return baseui
154
154
155
155
156 def set_hg_app_config(config):
156 def set_hg_app_config(config):
157 hgsettings = get_hg_settings()
157 hgsettings = get_hg_settings()
158 config['hg_app_auth_realm'] = hgsettings.app_auth_realm
158 config['hg_app_auth_realm'] = hgsettings.app_auth_realm
159 config['hg_app_name'] = hgsettings.app_title
159 config['hg_app_name'] = hgsettings.app_title
160
160
161 def invalidate_cache(name, *args):
161 def invalidate_cache(name, *args):
162 """Invalidates given name cache"""
162 """Invalidates given name cache"""
163
163
164 from beaker.cache import region_invalidate
164 from beaker.cache import region_invalidate
165 log.info('INVALIDATING CACHE FOR %s', name)
165 log.info('INVALIDATING CACHE FOR %s', name)
166
166
167 """propagate our arguments to make sure invalidation works. First
167 """propagate our arguments to make sure invalidation works. First
168 argument has to be the name of cached func name give to cache decorator
168 argument has to be the name of cached func name give to cache decorator
169 without that the invalidation would not work"""
169 without that the invalidation would not work"""
170 tmp = [name]
170 tmp = [name]
171 tmp.extend(args)
171 tmp.extend(args)
172 args = tuple(tmp)
172 args = tuple(tmp)
173
173
174 if name == 'cached_repo_list':
174 if name == 'cached_repo_list':
175 from pylons_app.model.hg_model import _get_repos_cached
175 from pylons_app.model.hg_model import _get_repos_cached
176 region_invalidate(_get_repos_cached, None, *args)
176 region_invalidate(_get_repos_cached, None, *args)
177
177
178 if name == 'full_changelog':
178 if name == 'full_changelog':
179 from pylons_app.model.hg_model import _full_changelog_cached
179 from pylons_app.model.hg_model import _full_changelog_cached
180 region_invalidate(_full_changelog_cached, None, *args)
180 region_invalidate(_full_changelog_cached, None, *args)
181
181
182 class EmptyChangeset(BaseChangeset):
182 class EmptyChangeset(BaseChangeset):
183
183
184 revision = -1
184 revision = -1
185 message = ''
185 message = ''
186
186
187 @LazyProperty
187 @LazyProperty
188 def raw_id(self):
188 def raw_id(self):
189 """
189 """
190 Returns raw string identifing this changeset, useful for web
190 Returns raw string identifing this changeset, useful for web
191 representation.
191 representation.
192 """
192 """
193 return '0' * 12
193 return '0' * 12
194
194
195
195
196 def repo2db_mapper(initial_repo_list, remove_obsolete=False):
196 def repo2db_mapper(initial_repo_list, remove_obsolete=False):
197 """
197 """
198 maps all found repositories into db
198 maps all found repositories into db
199 """
199 """
200 from pylons_app.model.repo_model import RepoModel
200 from pylons_app.model.repo_model import RepoModel
201
201
202 sa = meta.Session
202 sa = meta.Session
203 user = sa.query(User).filter(User.admin == True).first()
203 user = sa.query(User).filter(User.admin == True).first()
204
204
205 rm = RepoModel()
205 rm = RepoModel()
206
206
207 for name, repo in initial_repo_list.items():
207 for name, repo in initial_repo_list.items():
208 if not sa.query(Repository).get(name):
208 if not sa.query(Repository).get(name):
209 log.info('repository %s not found creating default', name)
209 log.info('repository %s not found creating default', name)
210
210
211 form_data = {
211 form_data = {
212 'repo_name':name,
212 'repo_name':name,
213 'description':repo.description if repo.description != 'unknown' else \
213 'description':repo.description if repo.description != 'unknown' else \
214 'auto description for %s' % name,
214 'auto description for %s' % name,
215 'private':False
215 'private':False
216 }
216 }
217 rm.create(form_data, user, just_db=True)
217 rm.create(form_data, user, just_db=True)
218
218
219
219
220 if remove_obsolete:
220 if remove_obsolete:
221 #remove from database those repositories that are not in the filesystem
221 #remove from database those repositories that are not in the filesystem
222 for repo in sa.query(Repository).all():
222 for repo in sa.query(Repository).all():
223 if repo.repo_name not in initial_repo_list.keys():
223 if repo.repo_name not in initial_repo_list.keys():
224 sa.delete(repo)
224 sa.delete(repo)
225 sa.commit()
225 sa.commit()
226
226
227
227
228 meta.Session.remove()
228 meta.Session.remove()
229
230 from UserDict import DictMixin
231
232 class OrderedDict(dict, DictMixin):
233
234 def __init__(self, *args, **kwds):
235 if len(args) > 1:
236 raise TypeError('expected at most 1 arguments, got %d' % len(args))
237 try:
238 self.__end
239 except AttributeError:
240 self.clear()
241 self.update(*args, **kwds)
242
243 def clear(self):
244 self.__end = end = []
245 end += [None, end, end] # sentinel node for doubly linked list
246 self.__map = {} # key --> [key, prev, next]
247 dict.clear(self)
248
249 def __setitem__(self, key, value):
250 if key not in self:
251 end = self.__end
252 curr = end[1]
253 curr[2] = end[1] = self.__map[key] = [key, curr, end]
254 dict.__setitem__(self, key, value)
255
256 def __delitem__(self, key):
257 dict.__delitem__(self, key)
258 key, prev, next = self.__map.pop(key)
259 prev[2] = next
260 next[1] = prev
261
262 def __iter__(self):
263 end = self.__end
264 curr = end[2]
265 while curr is not end:
266 yield curr[0]
267 curr = curr[2]
268
269 def __reversed__(self):
270 end = self.__end
271 curr = end[1]
272 while curr is not end:
273 yield curr[0]
274 curr = curr[1]
275
276 def popitem(self, last=True):
277 if not self:
278 raise KeyError('dictionary is empty')
279 if last:
280 key = reversed(self).next()
281 else:
282 key = iter(self).next()
283 value = self.pop(key)
284 return key, value
285
286 def __reduce__(self):
287 items = [[k, self[k]] for k in self]
288 tmp = self.__map, self.__end
289 del self.__map, self.__end
290 inst_dict = vars(self).copy()
291 self.__map, self.__end = tmp
292 if inst_dict:
293 return (self.__class__, (items,), inst_dict)
294 return self.__class__, (items,)
295
296 def keys(self):
297 return list(self)
298
299 setdefault = DictMixin.setdefault
300 update = DictMixin.update
301 pop = DictMixin.pop
302 values = DictMixin.values
303 items = DictMixin.items
304 iterkeys = DictMixin.iterkeys
305 itervalues = DictMixin.itervalues
306 iteritems = DictMixin.iteritems
307
308 def __repr__(self):
309 if not self:
310 return '%s()' % (self.__class__.__name__,)
311 return '%s(%r)' % (self.__class__.__name__, self.items())
312
313 def copy(self):
314 return self.__class__(self)
315
316 @classmethod
317 def fromkeys(cls, iterable, value=None):
318 d = cls()
319 for key in iterable:
320 d[key] = value
321 return d
322
323 def __eq__(self, other):
324 if isinstance(other, OrderedDict):
325 return len(self) == len(other) and self.items() == other.items()
326 return dict.__eq__(self, other)
327
328 def __ne__(self, other):
329 return not self == other
General Comments 0
You need to be logged in to leave comments. Login now