##// END OF EJS Templates
moved make-index command to paster_commands module...
marcink -
r3915:a42bfe8a beta
parent child Browse files
Show More
@@ -1,175 +1,175 b''
1 # -*- coding: utf-8 -*-
1 # -*- coding: utf-8 -*-
2 """
2 """
3 rhodecode.controllers.changelog
3 rhodecode.controllers.changelog
4 ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
4 ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
5
5
6 changelog controller for rhodecode
6 changelog controller for rhodecode
7
7
8 :created_on: Apr 21, 2010
8 :created_on: Apr 21, 2010
9 :author: marcink
9 :author: marcink
10 :copyright: (C) 2010-2012 Marcin Kuzminski <marcin@python-works.com>
10 :copyright: (C) 2010-2012 Marcin Kuzminski <marcin@python-works.com>
11 :license: GPLv3, see COPYING for more details.
11 :license: GPLv3, see COPYING for more details.
12 """
12 """
13 # This program is free software: you can redistribute it and/or modify
13 # This program is free software: you can redistribute it and/or modify
14 # it under the terms of the GNU General Public License as published by
14 # it under the terms of the GNU General Public License as published by
15 # the Free Software Foundation, either version 3 of the License, or
15 # the Free Software Foundation, either version 3 of the License, or
16 # (at your option) any later version.
16 # (at your option) any later version.
17 #
17 #
18 # This program is distributed in the hope that it will be useful,
18 # This program is distributed in the hope that it will be useful,
19 # but WITHOUT ANY WARRANTY; without even the implied warranty of
19 # but WITHOUT ANY WARRANTY; without even the implied warranty of
20 # MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
20 # MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
21 # GNU General Public License for more details.
21 # GNU General Public License for more details.
22 #
22 #
23 # You should have received a copy of the GNU General Public License
23 # You should have received a copy of the GNU General Public License
24 # along with this program. If not, see <http://www.gnu.org/licenses/>.
24 # along with this program. If not, see <http://www.gnu.org/licenses/>.
25
25
26 import logging
26 import logging
27 import traceback
27 import traceback
28
28
29 from pylons import request, url, session, tmpl_context as c
29 from pylons import request, url, session, tmpl_context as c
30 from pylons.controllers.util import redirect
30 from pylons.controllers.util import redirect
31 from pylons.i18n.translation import _
31 from pylons.i18n.translation import _
32
32
33 import rhodecode.lib.helpers as h
33 import rhodecode.lib.helpers as h
34 from rhodecode.lib.auth import LoginRequired, HasRepoPermissionAnyDecorator
34 from rhodecode.lib.auth import LoginRequired, HasRepoPermissionAnyDecorator
35 from rhodecode.lib.base import BaseRepoController, render
35 from rhodecode.lib.base import BaseRepoController, render
36 from rhodecode.lib.helpers import RepoPage
36 from rhodecode.lib.helpers import RepoPage
37 from rhodecode.lib.compat import json
37 from rhodecode.lib.compat import json
38 from rhodecode.lib.graphmod import _colored, _dagwalker
38 from rhodecode.lib.graphmod import _colored, _dagwalker
39 from rhodecode.lib.vcs.exceptions import RepositoryError, ChangesetDoesNotExistError,\
39 from rhodecode.lib.vcs.exceptions import RepositoryError, ChangesetDoesNotExistError,\
40 ChangesetError, NodeDoesNotExistError, EmptyRepositoryError
40 ChangesetError, NodeDoesNotExistError, EmptyRepositoryError
41 from rhodecode.lib.utils2 import safe_int
41 from rhodecode.lib.utils2 import safe_int
42 from webob.exc import HTTPNotFound
42 from webob.exc import HTTPNotFound
43
43
44 log = logging.getLogger(__name__)
44 log = logging.getLogger(__name__)
45
45
46
46
47 def _load_changelog_summary():
47 def _load_changelog_summary():
48 p = safe_int(request.GET.get('page'), 1)
48 p = safe_int(request.GET.get('page'), 1)
49 size = safe_int(request.GET.get('size'), 10)
49 size = safe_int(request.GET.get('size'), 10)
50
50
51 def url_generator(**kw):
51 def url_generator(**kw):
52 return url('changelog_summary_home',
52 return url('changelog_summary_home',
53 repo_name=c.rhodecode_db_repo.repo_name, size=size, **kw)
53 repo_name=c.rhodecode_db_repo.repo_name, size=size, **kw)
54
54
55 collection = c.rhodecode_repo
55 collection = c.rhodecode_repo
56
56
57 c.repo_changesets = RepoPage(collection, page=p,
57 c.repo_changesets = RepoPage(collection, page=p,
58 items_per_page=size,
58 items_per_page=size,
59 url=url_generator)
59 url=url_generator)
60 page_revisions = [x.raw_id for x in list(c.repo_changesets)]
60 page_revisions = [x.raw_id for x in list(c.repo_changesets)]
61 c.comments = c.rhodecode_db_repo.get_comments(page_revisions)
61 c.comments = c.rhodecode_db_repo.get_comments(page_revisions)
62 c.statuses = c.rhodecode_db_repo.statuses(page_revisions)
62 c.statuses = c.rhodecode_db_repo.statuses(page_revisions)
63
63
64
64
65 class ChangelogController(BaseRepoController):
65 class ChangelogController(BaseRepoController):
66
66
67 def __before__(self):
67 def __before__(self):
68 super(ChangelogController, self).__before__()
68 super(ChangelogController, self).__before__()
69 c.affected_files_cut_off = 60
69 c.affected_files_cut_off = 60
70
70
71 def _graph(self, repo, revs_int, repo_size, size, p):
71 def _graph(self, repo, revs_int, repo_size, size, p):
72 """
72 """
73 Generates a DAG graph for repo
73 Generates a DAG graph for repo
74
74
75 :param repo:
75 :param repo:
76 :param revs_int:
76 :param revs_int:
77 :param repo_size:
77 :param repo_size:
78 :param size:
78 :param size:
79 :param p:
79 :param p:
80 """
80 """
81 if not revs_int:
81 if not revs_int:
82 c.jsdata = json.dumps([])
82 c.jsdata = json.dumps([])
83 return
83 return
84
84
85 data = []
85 data = []
86 revs = revs_int
86 revs = revs_int
87
87
88 dag = _dagwalker(repo, revs, repo.alias)
88 dag = _dagwalker(repo, revs, repo.alias)
89 dag = _colored(dag)
89 dag = _colored(dag)
90 for (id, type, ctx, vtx, edges) in dag:
90 for (id, type, ctx, vtx, edges) in dag:
91 data.append(['', vtx, edges])
91 data.append(['', vtx, edges])
92
92
93 c.jsdata = json.dumps(data)
93 c.jsdata = json.dumps(data)
94
94
95 @LoginRequired()
95 @LoginRequired()
96 @HasRepoPermissionAnyDecorator('repository.read', 'repository.write',
96 @HasRepoPermissionAnyDecorator('repository.read', 'repository.write',
97 'repository.admin')
97 'repository.admin')
98 def index(self, repo_name, revision=None, f_path=None):
98 def index(self, repo_name, revision=None, f_path=None):
99 limit = 100
99 limit = 100
100 default = 20
100 default = 20
101 if request.GET.get('size'):
101 if request.GET.get('size'):
102 c.size = max(min(safe_int(request.GET.get('size')), limit), 1)
102 c.size = max(min(safe_int(request.GET.get('size')), limit), 1)
103 session['changelog_size'] = c.size
103 session['changelog_size'] = c.size
104 session.save()
104 session.save()
105 else:
105 else:
106 c.size = int(session.get('changelog_size', default))
106 c.size = int(session.get('changelog_size', default))
107 # min size must be 1
107 # min size must be 1
108 c.size = max(c.size, 1)
108 c.size = max(c.size, 1)
109 p = safe_int(request.GET.get('page', 1), 1)
109 p = safe_int(request.GET.get('page', 1), 1)
110 branch_name = request.GET.get('branch', None)
110 branch_name = request.GET.get('branch', None)
111 c.changelog_for_path = f_path
111 c.changelog_for_path = f_path
112 try:
112 try:
113
113
114 if f_path:
114 if f_path:
115 log.debug('generating changelog for path %s' % f_path)
115 log.debug('generating changelog for path %s' % f_path)
116 # get the history for the file !
116 # get the history for the file !
117 tip_cs = c.rhodecode_repo.get_changeset()
117 tip_cs = c.rhodecode_repo.get_changeset()
118 try:
118 try:
119 collection = tip_cs.get_file_history(f_path)
119 collection = tip_cs.get_file_history(f_path)
120 except (NodeDoesNotExistError, ChangesetError):
120 except (NodeDoesNotExistError, ChangesetError):
121 #this node is not present at tip !
121 #this node is not present at tip !
122 try:
122 try:
123 cs = self.__get_cs_or_redirect(revision, repo_name)
123 cs = self.__get_cs_or_redirect(revision, repo_name)
124 collection = cs.get_file_history(f_path)
124 collection = cs.get_file_history(f_path)
125 except RepositoryError, e:
125 except RepositoryError, e:
126 h.flash(str(e), category='warning')
126 h.flash(str(e), category='warning')
127 redirect(h.url('changelog_home', repo_name=repo_name))
127 redirect(h.url('changelog_home', repo_name=repo_name))
128 collection = list(reversed(collection))
128 collection = list(reversed(collection))
129 else:
129 else:
130 collection = c.rhodecode_repo.get_changesets(start=0,
130 collection = c.rhodecode_repo.get_changesets(start=0,
131 branch_name=branch_name)
131 branch_name=branch_name)
132 c.total_cs = len(collection)
132 c.total_cs = len(collection)
133
133
134 c.pagination = RepoPage(collection, page=p, item_count=c.total_cs,
134 c.pagination = RepoPage(collection, page=p, item_count=c.total_cs,
135 items_per_page=c.size, branch=branch_name)
135 items_per_page=c.size, branch=branch_name,)
136 collection = list(c.pagination)
136 collection = list(c.pagination)
137 page_revisions = [x.raw_id for x in c.pagination]
137 page_revisions = [x.raw_id for x in c.pagination]
138 c.comments = c.rhodecode_db_repo.get_comments(page_revisions)
138 c.comments = c.rhodecode_db_repo.get_comments(page_revisions)
139 c.statuses = c.rhodecode_db_repo.statuses(page_revisions)
139 c.statuses = c.rhodecode_db_repo.statuses(page_revisions)
140 except (EmptyRepositoryError), e:
140 except (EmptyRepositoryError), e:
141 h.flash(str(e), category='warning')
141 h.flash(str(e), category='warning')
142 return redirect(url('summary_home', repo_name=c.repo_name))
142 return redirect(url('summary_home', repo_name=c.repo_name))
143 except (RepositoryError, ChangesetDoesNotExistError, Exception), e:
143 except (RepositoryError, ChangesetDoesNotExistError, Exception), e:
144 log.error(traceback.format_exc())
144 log.error(traceback.format_exc())
145 h.flash(str(e), category='error')
145 h.flash(str(e), category='error')
146 return redirect(url('changelog_home', repo_name=c.repo_name))
146 return redirect(url('changelog_home', repo_name=c.repo_name))
147
147
148 c.branch_name = branch_name
148 c.branch_name = branch_name
149 c.branch_filters = [('', _('All Branches'))] + \
149 c.branch_filters = [('', _('All Branches'))] + \
150 [(k, k) for k in c.rhodecode_repo.branches.keys()]
150 [(k, k) for k in c.rhodecode_repo.branches.keys()]
151 _revs = []
151 _revs = []
152 if not f_path:
152 if not f_path:
153 _revs = [x.revision for x in c.pagination]
153 _revs = [x.revision for x in c.pagination]
154 self._graph(c.rhodecode_repo, _revs, c.total_cs, c.size, p)
154 self._graph(c.rhodecode_repo, _revs, c.total_cs, c.size, p)
155
155
156 return render('changelog/changelog.html')
156 return render('changelog/changelog.html')
157
157
158 @LoginRequired()
158 @LoginRequired()
159 @HasRepoPermissionAnyDecorator('repository.read', 'repository.write',
159 @HasRepoPermissionAnyDecorator('repository.read', 'repository.write',
160 'repository.admin')
160 'repository.admin')
161 def changelog_details(self, cs):
161 def changelog_details(self, cs):
162 if request.environ.get('HTTP_X_PARTIAL_XHR'):
162 if request.environ.get('HTTP_X_PARTIAL_XHR'):
163 c.cs = c.rhodecode_repo.get_changeset(cs)
163 c.cs = c.rhodecode_repo.get_changeset(cs)
164 return render('changelog/changelog_details.html')
164 return render('changelog/changelog_details.html')
165 raise HTTPNotFound()
165 raise HTTPNotFound()
166
166
167 @LoginRequired()
167 @LoginRequired()
168 @HasRepoPermissionAnyDecorator('repository.read', 'repository.write',
168 @HasRepoPermissionAnyDecorator('repository.read', 'repository.write',
169 'repository.admin')
169 'repository.admin')
170 def changelog_summary(self, repo_name):
170 def changelog_summary(self, repo_name):
171 if request.environ.get('HTTP_X_PARTIAL_XHR'):
171 if request.environ.get('HTTP_X_PARTIAL_XHR'):
172 _load_changelog_summary()
172 _load_changelog_summary()
173
173
174 return render('changelog/changelog_summary_data.html')
174 return render('changelog/changelog_summary_data.html')
175 raise HTTPNotFound()
175 raise HTTPNotFound()
@@ -1,1377 +1,1379 b''
1 """Helper functions
1 """Helper functions
2
2
3 Consists of functions to typically be used within templates, but also
3 Consists of functions to typically be used within templates, but also
4 available to Controllers. This module is available to both as 'h'.
4 available to Controllers. This module is available to both as 'h'.
5 """
5 """
6 import random
6 import random
7 import hashlib
7 import hashlib
8 import StringIO
8 import StringIO
9 import urllib
9 import urllib
10 import math
10 import math
11 import logging
11 import logging
12 import re
12 import re
13 import urlparse
13 import urlparse
14 import textwrap
14 import textwrap
15
15
16 from datetime import datetime
16 from datetime import datetime
17 from pygments.formatters.html import HtmlFormatter
17 from pygments.formatters.html import HtmlFormatter
18 from pygments import highlight as code_highlight
18 from pygments import highlight as code_highlight
19 from pylons import url
19 from pylons import url
20 from pylons.i18n.translation import _, ungettext
20 from pylons.i18n.translation import _, ungettext
21 from hashlib import md5
21 from hashlib import md5
22
22
23 from webhelpers.html import literal, HTML, escape
23 from webhelpers.html import literal, HTML, escape
24 from webhelpers.html.tools import *
24 from webhelpers.html.tools import *
25 from webhelpers.html.builder import make_tag
25 from webhelpers.html.builder import make_tag
26 from webhelpers.html.tags import auto_discovery_link, checkbox, css_classes, \
26 from webhelpers.html.tags import auto_discovery_link, checkbox, css_classes, \
27 end_form, file, form, hidden, image, javascript_link, link_to, \
27 end_form, file, form, hidden, image, javascript_link, link_to, \
28 link_to_if, link_to_unless, ol, required_legend, select, stylesheet_link, \
28 link_to_if, link_to_unless, ol, required_legend, select, stylesheet_link, \
29 submit, text, password, textarea, title, ul, xml_declaration, radio
29 submit, text, password, textarea, title, ul, xml_declaration, radio
30 from webhelpers.html.tools import auto_link, button_to, highlight, \
30 from webhelpers.html.tools import auto_link, button_to, highlight, \
31 js_obfuscate, mail_to, strip_links, strip_tags, tag_re
31 js_obfuscate, mail_to, strip_links, strip_tags, tag_re
32 from webhelpers.number import format_byte_size, format_bit_size
32 from webhelpers.number import format_byte_size, format_bit_size
33 from webhelpers.pylonslib import Flash as _Flash
33 from webhelpers.pylonslib import Flash as _Flash
34 from webhelpers.pylonslib.secure_form import secure_form
34 from webhelpers.pylonslib.secure_form import secure_form
35 from webhelpers.text import chop_at, collapse, convert_accented_entities, \
35 from webhelpers.text import chop_at, collapse, convert_accented_entities, \
36 convert_misc_entities, lchop, plural, rchop, remove_formatting, \
36 convert_misc_entities, lchop, plural, rchop, remove_formatting, \
37 replace_whitespace, urlify, truncate, wrap_paragraphs
37 replace_whitespace, urlify, truncate, wrap_paragraphs
38 from webhelpers.date import time_ago_in_words
38 from webhelpers.date import time_ago_in_words
39 from webhelpers.paginate import Page as _Page
39 from webhelpers.paginate import Page as _Page
40 from webhelpers.html.tags import _set_input_attrs, _set_id_attr, \
40 from webhelpers.html.tags import _set_input_attrs, _set_id_attr, \
41 convert_boolean_attrs, NotGiven, _make_safe_id_component
41 convert_boolean_attrs, NotGiven, _make_safe_id_component
42
42
43 from rhodecode.lib.annotate import annotate_highlight
43 from rhodecode.lib.annotate import annotate_highlight
44 from rhodecode.lib.utils import repo_name_slug, get_custom_lexer
44 from rhodecode.lib.utils import repo_name_slug, get_custom_lexer
45 from rhodecode.lib.utils2 import str2bool, safe_unicode, safe_str, \
45 from rhodecode.lib.utils2 import str2bool, safe_unicode, safe_str, \
46 get_changeset_safe, datetime_to_time, time_to_datetime, AttributeDict,\
46 get_changeset_safe, datetime_to_time, time_to_datetime, AttributeDict,\
47 safe_int
47 safe_int
48 from rhodecode.lib.markup_renderer import MarkupRenderer
48 from rhodecode.lib.markup_renderer import MarkupRenderer
49 from rhodecode.lib.vcs.exceptions import ChangesetDoesNotExistError
49 from rhodecode.lib.vcs.exceptions import ChangesetDoesNotExistError
50 from rhodecode.lib.vcs.backends.base import BaseChangeset, EmptyChangeset
50 from rhodecode.lib.vcs.backends.base import BaseChangeset, EmptyChangeset
51 from rhodecode.config.conf import DATE_FORMAT, DATETIME_FORMAT
51 from rhodecode.config.conf import DATE_FORMAT, DATETIME_FORMAT
52 from rhodecode.model.changeset_status import ChangesetStatusModel
52 from rhodecode.model.changeset_status import ChangesetStatusModel
53 from rhodecode.model.db import URL_SEP, Permission
53 from rhodecode.model.db import URL_SEP, Permission
54
54
55 log = logging.getLogger(__name__)
55 log = logging.getLogger(__name__)
56
56
57
57
58 html_escape_table = {
58 html_escape_table = {
59 "&": "&amp;",
59 "&": "&amp;",
60 '"': "&quot;",
60 '"': "&quot;",
61 "'": "&apos;",
61 "'": "&apos;",
62 ">": "&gt;",
62 ">": "&gt;",
63 "<": "&lt;",
63 "<": "&lt;",
64 }
64 }
65
65
66
66
67 def html_escape(text):
67 def html_escape(text):
68 """Produce entities within text."""
68 """Produce entities within text."""
69 return "".join(html_escape_table.get(c, c) for c in text)
69 return "".join(html_escape_table.get(c, c) for c in text)
70
70
71
71
72 def shorter(text, size=20):
72 def shorter(text, size=20):
73 postfix = '...'
73 postfix = '...'
74 if len(text) > size:
74 if len(text) > size:
75 return text[:size - len(postfix)] + postfix
75 return text[:size - len(postfix)] + postfix
76 return text
76 return text
77
77
78
78
79 def _reset(name, value=None, id=NotGiven, type="reset", **attrs):
79 def _reset(name, value=None, id=NotGiven, type="reset", **attrs):
80 """
80 """
81 Reset button
81 Reset button
82 """
82 """
83 _set_input_attrs(attrs, type, name, value)
83 _set_input_attrs(attrs, type, name, value)
84 _set_id_attr(attrs, id, name)
84 _set_id_attr(attrs, id, name)
85 convert_boolean_attrs(attrs, ["disabled"])
85 convert_boolean_attrs(attrs, ["disabled"])
86 return HTML.input(**attrs)
86 return HTML.input(**attrs)
87
87
88 reset = _reset
88 reset = _reset
89 safeid = _make_safe_id_component
89 safeid = _make_safe_id_component
90
90
91
91
92 def FID(raw_id, path):
92 def FID(raw_id, path):
93 """
93 """
94 Creates a uniqe ID for filenode based on it's hash of path and revision
94 Creates a uniqe ID for filenode based on it's hash of path and revision
95 it's safe to use in urls
95 it's safe to use in urls
96
96
97 :param raw_id:
97 :param raw_id:
98 :param path:
98 :param path:
99 """
99 """
100
100
101 return 'C-%s-%s' % (short_id(raw_id), md5(safe_str(path)).hexdigest()[:12])
101 return 'C-%s-%s' % (short_id(raw_id), md5(safe_str(path)).hexdigest()[:12])
102
102
103
103
104 def get_token():
104 def get_token():
105 """Return the current authentication token, creating one if one doesn't
105 """Return the current authentication token, creating one if one doesn't
106 already exist.
106 already exist.
107 """
107 """
108 token_key = "_authentication_token"
108 token_key = "_authentication_token"
109 from pylons import session
109 from pylons import session
110 if not token_key in session:
110 if not token_key in session:
111 try:
111 try:
112 token = hashlib.sha1(str(random.getrandbits(128))).hexdigest()
112 token = hashlib.sha1(str(random.getrandbits(128))).hexdigest()
113 except AttributeError: # Python < 2.4
113 except AttributeError: # Python < 2.4
114 token = hashlib.sha1(str(random.randrange(2 ** 128))).hexdigest()
114 token = hashlib.sha1(str(random.randrange(2 ** 128))).hexdigest()
115 session[token_key] = token
115 session[token_key] = token
116 if hasattr(session, 'save'):
116 if hasattr(session, 'save'):
117 session.save()
117 session.save()
118 return session[token_key]
118 return session[token_key]
119
119
120
120
121 class _GetError(object):
121 class _GetError(object):
122 """Get error from form_errors, and represent it as span wrapped error
122 """Get error from form_errors, and represent it as span wrapped error
123 message
123 message
124
124
125 :param field_name: field to fetch errors for
125 :param field_name: field to fetch errors for
126 :param form_errors: form errors dict
126 :param form_errors: form errors dict
127 """
127 """
128
128
129 def __call__(self, field_name, form_errors):
129 def __call__(self, field_name, form_errors):
130 tmpl = """<span class="error_msg">%s</span>"""
130 tmpl = """<span class="error_msg">%s</span>"""
131 if form_errors and field_name in form_errors:
131 if form_errors and field_name in form_errors:
132 return literal(tmpl % form_errors.get(field_name))
132 return literal(tmpl % form_errors.get(field_name))
133
133
134 get_error = _GetError()
134 get_error = _GetError()
135
135
136
136
137 class _ToolTip(object):
137 class _ToolTip(object):
138
138
139 def __call__(self, tooltip_title, trim_at=50):
139 def __call__(self, tooltip_title, trim_at=50):
140 """
140 """
141 Special function just to wrap our text into nice formatted
141 Special function just to wrap our text into nice formatted
142 autowrapped text
142 autowrapped text
143
143
144 :param tooltip_title:
144 :param tooltip_title:
145 """
145 """
146 tooltip_title = escape(tooltip_title)
146 tooltip_title = escape(tooltip_title)
147 tooltip_title = tooltip_title.replace('<', '&lt;').replace('>', '&gt;')
147 tooltip_title = tooltip_title.replace('<', '&lt;').replace('>', '&gt;')
148 return tooltip_title
148 return tooltip_title
149 tooltip = _ToolTip()
149 tooltip = _ToolTip()
150
150
151
151
152 class _FilesBreadCrumbs(object):
152 class _FilesBreadCrumbs(object):
153
153
154 def __call__(self, repo_name, rev, paths):
154 def __call__(self, repo_name, rev, paths):
155 if isinstance(paths, str):
155 if isinstance(paths, str):
156 paths = safe_unicode(paths)
156 paths = safe_unicode(paths)
157 url_l = [link_to(repo_name, url('files_home',
157 url_l = [link_to(repo_name, url('files_home',
158 repo_name=repo_name,
158 repo_name=repo_name,
159 revision=rev, f_path=''),
159 revision=rev, f_path=''),
160 class_='ypjax-link')]
160 class_='ypjax-link')]
161 paths_l = paths.split('/')
161 paths_l = paths.split('/')
162 for cnt, p in enumerate(paths_l):
162 for cnt, p in enumerate(paths_l):
163 if p != '':
163 if p != '':
164 url_l.append(link_to(p,
164 url_l.append(link_to(p,
165 url('files_home',
165 url('files_home',
166 repo_name=repo_name,
166 repo_name=repo_name,
167 revision=rev,
167 revision=rev,
168 f_path='/'.join(paths_l[:cnt + 1])
168 f_path='/'.join(paths_l[:cnt + 1])
169 ),
169 ),
170 class_='ypjax-link'
170 class_='ypjax-link'
171 )
171 )
172 )
172 )
173
173
174 return literal('/'.join(url_l))
174 return literal('/'.join(url_l))
175
175
176 files_breadcrumbs = _FilesBreadCrumbs()
176 files_breadcrumbs = _FilesBreadCrumbs()
177
177
178
178
179 class CodeHtmlFormatter(HtmlFormatter):
179 class CodeHtmlFormatter(HtmlFormatter):
180 """
180 """
181 My code Html Formatter for source codes
181 My code Html Formatter for source codes
182 """
182 """
183
183
184 def wrap(self, source, outfile):
184 def wrap(self, source, outfile):
185 return self._wrap_div(self._wrap_pre(self._wrap_code(source)))
185 return self._wrap_div(self._wrap_pre(self._wrap_code(source)))
186
186
187 def _wrap_code(self, source):
187 def _wrap_code(self, source):
188 for cnt, it in enumerate(source):
188 for cnt, it in enumerate(source):
189 i, t = it
189 i, t = it
190 t = '<div id="L%s">%s</div>' % (cnt + 1, t)
190 t = '<div id="L%s">%s</div>' % (cnt + 1, t)
191 yield i, t
191 yield i, t
192
192
193 def _wrap_tablelinenos(self, inner):
193 def _wrap_tablelinenos(self, inner):
194 dummyoutfile = StringIO.StringIO()
194 dummyoutfile = StringIO.StringIO()
195 lncount = 0
195 lncount = 0
196 for t, line in inner:
196 for t, line in inner:
197 if t:
197 if t:
198 lncount += 1
198 lncount += 1
199 dummyoutfile.write(line)
199 dummyoutfile.write(line)
200
200
201 fl = self.linenostart
201 fl = self.linenostart
202 mw = len(str(lncount + fl - 1))
202 mw = len(str(lncount + fl - 1))
203 sp = self.linenospecial
203 sp = self.linenospecial
204 st = self.linenostep
204 st = self.linenostep
205 la = self.lineanchors
205 la = self.lineanchors
206 aln = self.anchorlinenos
206 aln = self.anchorlinenos
207 nocls = self.noclasses
207 nocls = self.noclasses
208 if sp:
208 if sp:
209 lines = []
209 lines = []
210
210
211 for i in range(fl, fl + lncount):
211 for i in range(fl, fl + lncount):
212 if i % st == 0:
212 if i % st == 0:
213 if i % sp == 0:
213 if i % sp == 0:
214 if aln:
214 if aln:
215 lines.append('<a href="#%s%d" class="special">%*d</a>' %
215 lines.append('<a href="#%s%d" class="special">%*d</a>' %
216 (la, i, mw, i))
216 (la, i, mw, i))
217 else:
217 else:
218 lines.append('<span class="special">%*d</span>' % (mw, i))
218 lines.append('<span class="special">%*d</span>' % (mw, i))
219 else:
219 else:
220 if aln:
220 if aln:
221 lines.append('<a href="#%s%d">%*d</a>' % (la, i, mw, i))
221 lines.append('<a href="#%s%d">%*d</a>' % (la, i, mw, i))
222 else:
222 else:
223 lines.append('%*d' % (mw, i))
223 lines.append('%*d' % (mw, i))
224 else:
224 else:
225 lines.append('')
225 lines.append('')
226 ls = '\n'.join(lines)
226 ls = '\n'.join(lines)
227 else:
227 else:
228 lines = []
228 lines = []
229 for i in range(fl, fl + lncount):
229 for i in range(fl, fl + lncount):
230 if i % st == 0:
230 if i % st == 0:
231 if aln:
231 if aln:
232 lines.append('<a href="#%s%d">%*d</a>' % (la, i, mw, i))
232 lines.append('<a href="#%s%d">%*d</a>' % (la, i, mw, i))
233 else:
233 else:
234 lines.append('%*d' % (mw, i))
234 lines.append('%*d' % (mw, i))
235 else:
235 else:
236 lines.append('')
236 lines.append('')
237 ls = '\n'.join(lines)
237 ls = '\n'.join(lines)
238
238
239 # in case you wonder about the seemingly redundant <div> here: since the
239 # in case you wonder about the seemingly redundant <div> here: since the
240 # content in the other cell also is wrapped in a div, some browsers in
240 # content in the other cell also is wrapped in a div, some browsers in
241 # some configurations seem to mess up the formatting...
241 # some configurations seem to mess up the formatting...
242 if nocls:
242 if nocls:
243 yield 0, ('<table class="%stable">' % self.cssclass +
243 yield 0, ('<table class="%stable">' % self.cssclass +
244 '<tr><td><div class="linenodiv" '
244 '<tr><td><div class="linenodiv" '
245 'style="background-color: #f0f0f0; padding-right: 10px">'
245 'style="background-color: #f0f0f0; padding-right: 10px">'
246 '<pre style="line-height: 125%">' +
246 '<pre style="line-height: 125%">' +
247 ls + '</pre></div></td><td id="hlcode" class="code">')
247 ls + '</pre></div></td><td id="hlcode" class="code">')
248 else:
248 else:
249 yield 0, ('<table class="%stable">' % self.cssclass +
249 yield 0, ('<table class="%stable">' % self.cssclass +
250 '<tr><td class="linenos"><div class="linenodiv"><pre>' +
250 '<tr><td class="linenos"><div class="linenodiv"><pre>' +
251 ls + '</pre></div></td><td id="hlcode" class="code">')
251 ls + '</pre></div></td><td id="hlcode" class="code">')
252 yield 0, dummyoutfile.getvalue()
252 yield 0, dummyoutfile.getvalue()
253 yield 0, '</td></tr></table>'
253 yield 0, '</td></tr></table>'
254
254
255
255
256 def pygmentize(filenode, **kwargs):
256 def pygmentize(filenode, **kwargs):
257 """
257 """
258 pygmentize function using pygments
258 pygmentize function using pygments
259
259
260 :param filenode:
260 :param filenode:
261 """
261 """
262 lexer = get_custom_lexer(filenode.extension) or filenode.lexer
262 lexer = get_custom_lexer(filenode.extension) or filenode.lexer
263 return literal(code_highlight(filenode.content, lexer,
263 return literal(code_highlight(filenode.content, lexer,
264 CodeHtmlFormatter(**kwargs)))
264 CodeHtmlFormatter(**kwargs)))
265
265
266
266
267 def pygmentize_annotation(repo_name, filenode, **kwargs):
267 def pygmentize_annotation(repo_name, filenode, **kwargs):
268 """
268 """
269 pygmentize function for annotation
269 pygmentize function for annotation
270
270
271 :param filenode:
271 :param filenode:
272 """
272 """
273
273
274 color_dict = {}
274 color_dict = {}
275
275
276 def gen_color(n=10000):
276 def gen_color(n=10000):
277 """generator for getting n of evenly distributed colors using
277 """generator for getting n of evenly distributed colors using
278 hsv color and golden ratio. It always return same order of colors
278 hsv color and golden ratio. It always return same order of colors
279
279
280 :returns: RGB tuple
280 :returns: RGB tuple
281 """
281 """
282
282
283 def hsv_to_rgb(h, s, v):
283 def hsv_to_rgb(h, s, v):
284 if s == 0.0:
284 if s == 0.0:
285 return v, v, v
285 return v, v, v
286 i = int(h * 6.0) # XXX assume int() truncates!
286 i = int(h * 6.0) # XXX assume int() truncates!
287 f = (h * 6.0) - i
287 f = (h * 6.0) - i
288 p = v * (1.0 - s)
288 p = v * (1.0 - s)
289 q = v * (1.0 - s * f)
289 q = v * (1.0 - s * f)
290 t = v * (1.0 - s * (1.0 - f))
290 t = v * (1.0 - s * (1.0 - f))
291 i = i % 6
291 i = i % 6
292 if i == 0:
292 if i == 0:
293 return v, t, p
293 return v, t, p
294 if i == 1:
294 if i == 1:
295 return q, v, p
295 return q, v, p
296 if i == 2:
296 if i == 2:
297 return p, v, t
297 return p, v, t
298 if i == 3:
298 if i == 3:
299 return p, q, v
299 return p, q, v
300 if i == 4:
300 if i == 4:
301 return t, p, v
301 return t, p, v
302 if i == 5:
302 if i == 5:
303 return v, p, q
303 return v, p, q
304
304
305 golden_ratio = 0.618033988749895
305 golden_ratio = 0.618033988749895
306 h = 0.22717784590367374
306 h = 0.22717784590367374
307
307
308 for _ in xrange(n):
308 for _ in xrange(n):
309 h += golden_ratio
309 h += golden_ratio
310 h %= 1
310 h %= 1
311 HSV_tuple = [h, 0.95, 0.95]
311 HSV_tuple = [h, 0.95, 0.95]
312 RGB_tuple = hsv_to_rgb(*HSV_tuple)
312 RGB_tuple = hsv_to_rgb(*HSV_tuple)
313 yield map(lambda x: str(int(x * 256)), RGB_tuple)
313 yield map(lambda x: str(int(x * 256)), RGB_tuple)
314
314
315 cgenerator = gen_color()
315 cgenerator = gen_color()
316
316
317 def get_color_string(cs):
317 def get_color_string(cs):
318 if cs in color_dict:
318 if cs in color_dict:
319 col = color_dict[cs]
319 col = color_dict[cs]
320 else:
320 else:
321 col = color_dict[cs] = cgenerator.next()
321 col = color_dict[cs] = cgenerator.next()
322 return "color: rgb(%s)! important;" % (', '.join(col))
322 return "color: rgb(%s)! important;" % (', '.join(col))
323
323
324 def url_func(repo_name):
324 def url_func(repo_name):
325
325
326 def _url_func(changeset):
326 def _url_func(changeset):
327 author = changeset.author
327 author = changeset.author
328 date = changeset.date
328 date = changeset.date
329 message = tooltip(changeset.message)
329 message = tooltip(changeset.message)
330
330
331 tooltip_html = ("<div style='font-size:0.8em'><b>Author:</b>"
331 tooltip_html = ("<div style='font-size:0.8em'><b>Author:</b>"
332 " %s<br/><b>Date:</b> %s</b><br/><b>Message:"
332 " %s<br/><b>Date:</b> %s</b><br/><b>Message:"
333 "</b> %s<br/></div>")
333 "</b> %s<br/></div>")
334
334
335 tooltip_html = tooltip_html % (author, date, message)
335 tooltip_html = tooltip_html % (author, date, message)
336 lnk_format = '%5s:%s' % ('r%s' % changeset.revision,
336 lnk_format = '%5s:%s' % ('r%s' % changeset.revision,
337 short_id(changeset.raw_id))
337 short_id(changeset.raw_id))
338 uri = link_to(
338 uri = link_to(
339 lnk_format,
339 lnk_format,
340 url('changeset_home', repo_name=repo_name,
340 url('changeset_home', repo_name=repo_name,
341 revision=changeset.raw_id),
341 revision=changeset.raw_id),
342 style=get_color_string(changeset.raw_id),
342 style=get_color_string(changeset.raw_id),
343 class_='tooltip',
343 class_='tooltip',
344 title=tooltip_html
344 title=tooltip_html
345 )
345 )
346
346
347 uri += '\n'
347 uri += '\n'
348 return uri
348 return uri
349 return _url_func
349 return _url_func
350
350
351 return literal(annotate_highlight(filenode, url_func(repo_name), **kwargs))
351 return literal(annotate_highlight(filenode, url_func(repo_name), **kwargs))
352
352
353
353
354 def is_following_repo(repo_name, user_id):
354 def is_following_repo(repo_name, user_id):
355 from rhodecode.model.scm import ScmModel
355 from rhodecode.model.scm import ScmModel
356 return ScmModel().is_following_repo(repo_name, user_id)
356 return ScmModel().is_following_repo(repo_name, user_id)
357
357
358 flash = _Flash()
358 flash = _Flash()
359
359
360 #==============================================================================
360 #==============================================================================
361 # SCM FILTERS available via h.
361 # SCM FILTERS available via h.
362 #==============================================================================
362 #==============================================================================
363 from rhodecode.lib.vcs.utils import author_name, author_email
363 from rhodecode.lib.vcs.utils import author_name, author_email
364 from rhodecode.lib.utils2 import credentials_filter, age as _age
364 from rhodecode.lib.utils2 import credentials_filter, age as _age
365 from rhodecode.model.db import User, ChangesetStatus
365 from rhodecode.model.db import User, ChangesetStatus
366
366
367 age = lambda x, y=False: _age(x, y)
367 age = lambda x, y=False: _age(x, y)
368 capitalize = lambda x: x.capitalize()
368 capitalize = lambda x: x.capitalize()
369 email = author_email
369 email = author_email
370 short_id = lambda x: x[:12]
370 short_id = lambda x: x[:12]
371 hide_credentials = lambda x: ''.join(credentials_filter(x))
371 hide_credentials = lambda x: ''.join(credentials_filter(x))
372
372
373
373
374 def show_id(cs):
374 def show_id(cs):
375 """
375 """
376 Configurable function that shows ID
376 Configurable function that shows ID
377 by default it's r123:fffeeefffeee
377 by default it's r123:fffeeefffeee
378
378
379 :param cs: changeset instance
379 :param cs: changeset instance
380 """
380 """
381 from rhodecode import CONFIG
381 from rhodecode import CONFIG
382 def_len = safe_int(CONFIG.get('show_sha_length', 12))
382 def_len = safe_int(CONFIG.get('show_sha_length', 12))
383 show_rev = str2bool(CONFIG.get('show_revision_number', True))
383 show_rev = str2bool(CONFIG.get('show_revision_number', True))
384
384
385 raw_id = cs.raw_id[:def_len]
385 raw_id = cs.raw_id[:def_len]
386 if show_rev:
386 if show_rev:
387 return 'r%s:%s' % (cs.revision, raw_id)
387 return 'r%s:%s' % (cs.revision, raw_id)
388 else:
388 else:
389 return '%s' % (raw_id)
389 return '%s' % (raw_id)
390
390
391
391
392 def fmt_date(date):
392 def fmt_date(date):
393 if date:
393 if date:
394 _fmt = _(u"%a, %d %b %Y %H:%M:%S").encode('utf8')
394 _fmt = _(u"%a, %d %b %Y %H:%M:%S").encode('utf8')
395 return date.strftime(_fmt).decode('utf8')
395 return date.strftime(_fmt).decode('utf8')
396
396
397 return ""
397 return ""
398
398
399
399
400 def is_git(repository):
400 def is_git(repository):
401 if hasattr(repository, 'alias'):
401 if hasattr(repository, 'alias'):
402 _type = repository.alias
402 _type = repository.alias
403 elif hasattr(repository, 'repo_type'):
403 elif hasattr(repository, 'repo_type'):
404 _type = repository.repo_type
404 _type = repository.repo_type
405 else:
405 else:
406 _type = repository
406 _type = repository
407 return _type == 'git'
407 return _type == 'git'
408
408
409
409
410 def is_hg(repository):
410 def is_hg(repository):
411 if hasattr(repository, 'alias'):
411 if hasattr(repository, 'alias'):
412 _type = repository.alias
412 _type = repository.alias
413 elif hasattr(repository, 'repo_type'):
413 elif hasattr(repository, 'repo_type'):
414 _type = repository.repo_type
414 _type = repository.repo_type
415 else:
415 else:
416 _type = repository
416 _type = repository
417 return _type == 'hg'
417 return _type == 'hg'
418
418
419
419
420 def email_or_none(author):
420 def email_or_none(author):
421 # extract email from the commit string
421 # extract email from the commit string
422 _email = email(author)
422 _email = email(author)
423 if _email != '':
423 if _email != '':
424 # check it against RhodeCode database, and use the MAIN email for this
424 # check it against RhodeCode database, and use the MAIN email for this
425 # user
425 # user
426 user = User.get_by_email(_email, case_insensitive=True, cache=True)
426 user = User.get_by_email(_email, case_insensitive=True, cache=True)
427 if user is not None:
427 if user is not None:
428 return user.email
428 return user.email
429 return _email
429 return _email
430
430
431 # See if it contains a username we can get an email from
431 # See if it contains a username we can get an email from
432 user = User.get_by_username(author_name(author), case_insensitive=True,
432 user = User.get_by_username(author_name(author), case_insensitive=True,
433 cache=True)
433 cache=True)
434 if user is not None:
434 if user is not None:
435 return user.email
435 return user.email
436
436
437 # No valid email, not a valid user in the system, none!
437 # No valid email, not a valid user in the system, none!
438 return None
438 return None
439
439
440
440
441 def person(author, show_attr="username_and_name"):
441 def person(author, show_attr="username_and_name"):
442 # attr to return from fetched user
442 # attr to return from fetched user
443 person_getter = lambda usr: getattr(usr, show_attr)
443 person_getter = lambda usr: getattr(usr, show_attr)
444
444
445 # Valid email in the attribute passed, see if they're in the system
445 # Valid email in the attribute passed, see if they're in the system
446 _email = email(author)
446 _email = email(author)
447 if _email != '':
447 if _email != '':
448 user = User.get_by_email(_email, case_insensitive=True, cache=True)
448 user = User.get_by_email(_email, case_insensitive=True, cache=True)
449 if user is not None:
449 if user is not None:
450 return person_getter(user)
450 return person_getter(user)
451
451
452 # Maybe it's a username?
452 # Maybe it's a username?
453 _author = author_name(author)
453 _author = author_name(author)
454 user = User.get_by_username(_author, case_insensitive=True,
454 user = User.get_by_username(_author, case_insensitive=True,
455 cache=True)
455 cache=True)
456 if user is not None:
456 if user is not None:
457 return person_getter(user)
457 return person_getter(user)
458
458
459 # Still nothing? Just pass back the author name if any, else the email
459 # Still nothing? Just pass back the author name if any, else the email
460 return _author or _email
460 return _author or _email
461
461
462
462
463 def person_by_id(id_, show_attr="username_and_name"):
463 def person_by_id(id_, show_attr="username_and_name"):
464 # attr to return from fetched user
464 # attr to return from fetched user
465 person_getter = lambda usr: getattr(usr, show_attr)
465 person_getter = lambda usr: getattr(usr, show_attr)
466
466
467 #maybe it's an ID ?
467 #maybe it's an ID ?
468 if str(id_).isdigit() or isinstance(id_, int):
468 if str(id_).isdigit() or isinstance(id_, int):
469 id_ = int(id_)
469 id_ = int(id_)
470 user = User.get(id_)
470 user = User.get(id_)
471 if user is not None:
471 if user is not None:
472 return person_getter(user)
472 return person_getter(user)
473 return id_
473 return id_
474
474
475
475
476 def desc_stylize(value):
476 def desc_stylize(value):
477 """
477 """
478 converts tags from value into html equivalent
478 converts tags from value into html equivalent
479
479
480 :param value:
480 :param value:
481 """
481 """
482 value = re.sub(r'\[see\ \=\>\ *([a-zA-Z0-9\/\=\?\&\ \:\/\.\-]*)\]',
482 value = re.sub(r'\[see\ \=\>\ *([a-zA-Z0-9\/\=\?\&\ \:\/\.\-]*)\]',
483 '<div class="metatag" tag="see">see =&gt; \\1 </div>', value)
483 '<div class="metatag" tag="see">see =&gt; \\1 </div>', value)
484 value = re.sub(r'\[license\ \=\>\ *([a-zA-Z0-9\/\=\?\&\ \:\/\.\-]*)\]',
484 value = re.sub(r'\[license\ \=\>\ *([a-zA-Z0-9\/\=\?\&\ \:\/\.\-]*)\]',
485 '<div class="metatag" tag="license"><a href="http:\/\/www.opensource.org/licenses/\\1">\\1</a></div>', value)
485 '<div class="metatag" tag="license"><a href="http:\/\/www.opensource.org/licenses/\\1">\\1</a></div>', value)
486 value = re.sub(r'\[(requires|recommends|conflicts|base)\ \=\>\ *([a-zA-Z0-9\-\/]*)\]',
486 value = re.sub(r'\[(requires|recommends|conflicts|base)\ \=\>\ *([a-zA-Z0-9\-\/]*)\]',
487 '<div class="metatag" tag="\\1">\\1 =&gt; <a href="/\\2">\\2</a></div>', value)
487 '<div class="metatag" tag="\\1">\\1 =&gt; <a href="/\\2">\\2</a></div>', value)
488 value = re.sub(r'\[(lang|language)\ \=\>\ *([a-zA-Z\-\/\#\+]*)\]',
488 value = re.sub(r'\[(lang|language)\ \=\>\ *([a-zA-Z\-\/\#\+]*)\]',
489 '<div class="metatag" tag="lang">\\2</div>', value)
489 '<div class="metatag" tag="lang">\\2</div>', value)
490 value = re.sub(r'\[([a-z]+)\]',
490 value = re.sub(r'\[([a-z]+)\]',
491 '<div class="metatag" tag="\\1">\\1</div>', value)
491 '<div class="metatag" tag="\\1">\\1</div>', value)
492
492
493 return value
493 return value
494
494
495
495
496 def boolicon(value):
496 def boolicon(value):
497 """Returns boolean value of a value, represented as small html image of true/false
497 """Returns boolean value of a value, represented as small html image of true/false
498 icons
498 icons
499
499
500 :param value: value
500 :param value: value
501 """
501 """
502
502
503 if value:
503 if value:
504 return HTML.tag('img', src=url("/images/icons/accept.png"),
504 return HTML.tag('img', src=url("/images/icons/accept.png"),
505 alt=_('True'))
505 alt=_('True'))
506 else:
506 else:
507 return HTML.tag('img', src=url("/images/icons/cancel.png"),
507 return HTML.tag('img', src=url("/images/icons/cancel.png"),
508 alt=_('False'))
508 alt=_('False'))
509
509
510
510
511 def action_parser(user_log, feed=False, parse_cs=False):
511 def action_parser(user_log, feed=False, parse_cs=False):
512 """
512 """
513 This helper will action_map the specified string action into translated
513 This helper will action_map the specified string action into translated
514 fancy names with icons and links
514 fancy names with icons and links
515
515
516 :param user_log: user log instance
516 :param user_log: user log instance
517 :param feed: use output for feeds (no html and fancy icons)
517 :param feed: use output for feeds (no html and fancy icons)
518 :param parse_cs: parse Changesets into VCS instances
518 :param parse_cs: parse Changesets into VCS instances
519 """
519 """
520
520
521 action = user_log.action
521 action = user_log.action
522 action_params = ' '
522 action_params = ' '
523
523
524 x = action.split(':')
524 x = action.split(':')
525
525
526 if len(x) > 1:
526 if len(x) > 1:
527 action, action_params = x
527 action, action_params = x
528
528
529 def get_cs_links():
529 def get_cs_links():
530 revs_limit = 3 # display this amount always
530 revs_limit = 3 # display this amount always
531 revs_top_limit = 50 # show upto this amount of changesets hidden
531 revs_top_limit = 50 # show upto this amount of changesets hidden
532 revs_ids = action_params.split(',')
532 revs_ids = action_params.split(',')
533 deleted = user_log.repository is None
533 deleted = user_log.repository is None
534 if deleted:
534 if deleted:
535 return ','.join(revs_ids)
535 return ','.join(revs_ids)
536
536
537 repo_name = user_log.repository.repo_name
537 repo_name = user_log.repository.repo_name
538
538
539 def lnk(rev, repo_name):
539 def lnk(rev, repo_name):
540 if isinstance(rev, BaseChangeset) or isinstance(rev, AttributeDict):
540 if isinstance(rev, BaseChangeset) or isinstance(rev, AttributeDict):
541 lazy_cs = True
541 lazy_cs = True
542 if getattr(rev, 'op', None) and getattr(rev, 'ref_name', None):
542 if getattr(rev, 'op', None) and getattr(rev, 'ref_name', None):
543 lazy_cs = False
543 lazy_cs = False
544 lbl = '?'
544 lbl = '?'
545 if rev.op == 'delete_branch':
545 if rev.op == 'delete_branch':
546 lbl = '%s' % _('Deleted branch: %s') % rev.ref_name
546 lbl = '%s' % _('Deleted branch: %s') % rev.ref_name
547 title = ''
547 title = ''
548 elif rev.op == 'tag':
548 elif rev.op == 'tag':
549 lbl = '%s' % _('Created tag: %s') % rev.ref_name
549 lbl = '%s' % _('Created tag: %s') % rev.ref_name
550 title = ''
550 title = ''
551 _url = '#'
551 _url = '#'
552
552
553 else:
553 else:
554 lbl = '%s' % (rev.short_id[:8])
554 lbl = '%s' % (rev.short_id[:8])
555 _url = url('changeset_home', repo_name=repo_name,
555 _url = url('changeset_home', repo_name=repo_name,
556 revision=rev.raw_id)
556 revision=rev.raw_id)
557 title = tooltip(rev.message)
557 title = tooltip(rev.message)
558 else:
558 else:
559 ## changeset cannot be found/striped/removed etc.
559 ## changeset cannot be found/striped/removed etc.
560 lbl = ('%s' % rev)[:12]
560 lbl = ('%s' % rev)[:12]
561 _url = '#'
561 _url = '#'
562 title = _('Changeset not found')
562 title = _('Changeset not found')
563 if parse_cs:
563 if parse_cs:
564 return link_to(lbl, _url, title=title, class_='tooltip')
564 return link_to(lbl, _url, title=title, class_='tooltip')
565 return link_to(lbl, _url, raw_id=rev.raw_id, repo_name=repo_name,
565 return link_to(lbl, _url, raw_id=rev.raw_id, repo_name=repo_name,
566 class_='lazy-cs' if lazy_cs else '')
566 class_='lazy-cs' if lazy_cs else '')
567
567
568 def _get_op(rev_txt):
568 def _get_op(rev_txt):
569 _op = None
569 _op = None
570 _name = rev_txt
570 _name = rev_txt
571 if len(rev_txt.split('=>')) == 2:
571 if len(rev_txt.split('=>')) == 2:
572 _op, _name = rev_txt.split('=>')
572 _op, _name = rev_txt.split('=>')
573 return _op, _name
573 return _op, _name
574
574
575 revs = []
575 revs = []
576 if len(filter(lambda v: v != '', revs_ids)) > 0:
576 if len(filter(lambda v: v != '', revs_ids)) > 0:
577 repo = None
577 repo = None
578 for rev in revs_ids[:revs_top_limit]:
578 for rev in revs_ids[:revs_top_limit]:
579 _op, _name = _get_op(rev)
579 _op, _name = _get_op(rev)
580
580
581 # we want parsed changesets, or new log store format is bad
581 # we want parsed changesets, or new log store format is bad
582 if parse_cs:
582 if parse_cs:
583 try:
583 try:
584 if repo is None:
584 if repo is None:
585 repo = user_log.repository.scm_instance
585 repo = user_log.repository.scm_instance
586 _rev = repo.get_changeset(rev)
586 _rev = repo.get_changeset(rev)
587 revs.append(_rev)
587 revs.append(_rev)
588 except ChangesetDoesNotExistError:
588 except ChangesetDoesNotExistError:
589 log.error('cannot find revision %s in this repo' % rev)
589 log.error('cannot find revision %s in this repo' % rev)
590 revs.append(rev)
590 revs.append(rev)
591 continue
591 continue
592 else:
592 else:
593 _rev = AttributeDict({
593 _rev = AttributeDict({
594 'short_id': rev[:12],
594 'short_id': rev[:12],
595 'raw_id': rev,
595 'raw_id': rev,
596 'message': '',
596 'message': '',
597 'op': _op,
597 'op': _op,
598 'ref_name': _name
598 'ref_name': _name
599 })
599 })
600 revs.append(_rev)
600 revs.append(_rev)
601 cs_links = [" " + ', '.join(
601 cs_links = [" " + ', '.join(
602 [lnk(rev, repo_name) for rev in revs[:revs_limit]]
602 [lnk(rev, repo_name) for rev in revs[:revs_limit]]
603 )]
603 )]
604 _op1, _name1 = _get_op(revs_ids[0])
604 _op1, _name1 = _get_op(revs_ids[0])
605 _op2, _name2 = _get_op(revs_ids[-1])
605 _op2, _name2 = _get_op(revs_ids[-1])
606
606
607 _rev = '%s...%s' % (_name1, _name2)
607 _rev = '%s...%s' % (_name1, _name2)
608
608
609 compare_view = (
609 compare_view = (
610 ' <div class="compare_view tooltip" title="%s">'
610 ' <div class="compare_view tooltip" title="%s">'
611 '<a href="%s">%s</a> </div>' % (
611 '<a href="%s">%s</a> </div>' % (
612 _('Show all combined changesets %s->%s') % (
612 _('Show all combined changesets %s->%s') % (
613 revs_ids[0][:12], revs_ids[-1][:12]
613 revs_ids[0][:12], revs_ids[-1][:12]
614 ),
614 ),
615 url('changeset_home', repo_name=repo_name,
615 url('changeset_home', repo_name=repo_name,
616 revision=_rev
616 revision=_rev
617 ),
617 ),
618 _('compare view')
618 _('compare view')
619 )
619 )
620 )
620 )
621
621
622 # if we have exactly one more than normally displayed
622 # if we have exactly one more than normally displayed
623 # just display it, takes less space than displaying
623 # just display it, takes less space than displaying
624 # "and 1 more revisions"
624 # "and 1 more revisions"
625 if len(revs_ids) == revs_limit + 1:
625 if len(revs_ids) == revs_limit + 1:
626 rev = revs[revs_limit]
626 rev = revs[revs_limit]
627 cs_links.append(", " + lnk(rev, repo_name))
627 cs_links.append(", " + lnk(rev, repo_name))
628
628
629 # hidden-by-default ones
629 # hidden-by-default ones
630 if len(revs_ids) > revs_limit + 1:
630 if len(revs_ids) > revs_limit + 1:
631 uniq_id = revs_ids[0]
631 uniq_id = revs_ids[0]
632 html_tmpl = (
632 html_tmpl = (
633 '<span> %s <a class="show_more" id="_%s" '
633 '<span> %s <a class="show_more" id="_%s" '
634 'href="#more">%s</a> %s</span>'
634 'href="#more">%s</a> %s</span>'
635 )
635 )
636 if not feed:
636 if not feed:
637 cs_links.append(html_tmpl % (
637 cs_links.append(html_tmpl % (
638 _('and'),
638 _('and'),
639 uniq_id, _('%s more') % (len(revs_ids) - revs_limit),
639 uniq_id, _('%s more') % (len(revs_ids) - revs_limit),
640 _('revisions')
640 _('revisions')
641 )
641 )
642 )
642 )
643
643
644 if not feed:
644 if not feed:
645 html_tmpl = '<span id="%s" style="display:none">, %s </span>'
645 html_tmpl = '<span id="%s" style="display:none">, %s </span>'
646 else:
646 else:
647 html_tmpl = '<span id="%s"> %s </span>'
647 html_tmpl = '<span id="%s"> %s </span>'
648
648
649 morelinks = ', '.join(
649 morelinks = ', '.join(
650 [lnk(rev, repo_name) for rev in revs[revs_limit:]]
650 [lnk(rev, repo_name) for rev in revs[revs_limit:]]
651 )
651 )
652
652
653 if len(revs_ids) > revs_top_limit:
653 if len(revs_ids) > revs_top_limit:
654 morelinks += ', ...'
654 morelinks += ', ...'
655
655
656 cs_links.append(html_tmpl % (uniq_id, morelinks))
656 cs_links.append(html_tmpl % (uniq_id, morelinks))
657 if len(revs) > 1:
657 if len(revs) > 1:
658 cs_links.append(compare_view)
658 cs_links.append(compare_view)
659 return ''.join(cs_links)
659 return ''.join(cs_links)
660
660
661 def get_fork_name():
661 def get_fork_name():
662 repo_name = action_params
662 repo_name = action_params
663 _url = url('summary_home', repo_name=repo_name)
663 _url = url('summary_home', repo_name=repo_name)
664 return _('fork name %s') % link_to(action_params, _url)
664 return _('fork name %s') % link_to(action_params, _url)
665
665
666 def get_user_name():
666 def get_user_name():
667 user_name = action_params
667 user_name = action_params
668 return user_name
668 return user_name
669
669
670 def get_users_group():
670 def get_users_group():
671 group_name = action_params
671 group_name = action_params
672 return group_name
672 return group_name
673
673
674 def get_pull_request():
674 def get_pull_request():
675 pull_request_id = action_params
675 pull_request_id = action_params
676 deleted = user_log.repository is None
676 deleted = user_log.repository is None
677 if deleted:
677 if deleted:
678 repo_name = user_log.repository_name
678 repo_name = user_log.repository_name
679 else:
679 else:
680 repo_name = user_log.repository.repo_name
680 repo_name = user_log.repository.repo_name
681 return link_to(_('Pull request #%s') % pull_request_id,
681 return link_to(_('Pull request #%s') % pull_request_id,
682 url('pullrequest_show', repo_name=repo_name,
682 url('pullrequest_show', repo_name=repo_name,
683 pull_request_id=pull_request_id))
683 pull_request_id=pull_request_id))
684
684
685 def get_archive_name():
685 def get_archive_name():
686 archive_name = action_params
686 archive_name = action_params
687 return archive_name
687 return archive_name
688
688
689 # action : translated str, callback(extractor), icon
689 # action : translated str, callback(extractor), icon
690 action_map = {
690 action_map = {
691 'user_deleted_repo': (_('[deleted] repository'),
691 'user_deleted_repo': (_('[deleted] repository'),
692 None, 'database_delete.png'),
692 None, 'database_delete.png'),
693 'user_created_repo': (_('[created] repository'),
693 'user_created_repo': (_('[created] repository'),
694 None, 'database_add.png'),
694 None, 'database_add.png'),
695 'user_created_fork': (_('[created] repository as fork'),
695 'user_created_fork': (_('[created] repository as fork'),
696 None, 'arrow_divide.png'),
696 None, 'arrow_divide.png'),
697 'user_forked_repo': (_('[forked] repository'),
697 'user_forked_repo': (_('[forked] repository'),
698 get_fork_name, 'arrow_divide.png'),
698 get_fork_name, 'arrow_divide.png'),
699 'user_updated_repo': (_('[updated] repository'),
699 'user_updated_repo': (_('[updated] repository'),
700 None, 'database_edit.png'),
700 None, 'database_edit.png'),
701 'user_downloaded_archive': (_('[downloaded] archive from repository'),
701 'user_downloaded_archive': (_('[downloaded] archive from repository'),
702 get_archive_name, 'page_white_compressed.png'),
702 get_archive_name, 'page_white_compressed.png'),
703 'admin_deleted_repo': (_('[delete] repository'),
703 'admin_deleted_repo': (_('[delete] repository'),
704 None, 'database_delete.png'),
704 None, 'database_delete.png'),
705 'admin_created_repo': (_('[created] repository'),
705 'admin_created_repo': (_('[created] repository'),
706 None, 'database_add.png'),
706 None, 'database_add.png'),
707 'admin_forked_repo': (_('[forked] repository'),
707 'admin_forked_repo': (_('[forked] repository'),
708 None, 'arrow_divide.png'),
708 None, 'arrow_divide.png'),
709 'admin_updated_repo': (_('[updated] repository'),
709 'admin_updated_repo': (_('[updated] repository'),
710 None, 'database_edit.png'),
710 None, 'database_edit.png'),
711 'admin_created_user': (_('[created] user'),
711 'admin_created_user': (_('[created] user'),
712 get_user_name, 'user_add.png'),
712 get_user_name, 'user_add.png'),
713 'admin_updated_user': (_('[updated] user'),
713 'admin_updated_user': (_('[updated] user'),
714 get_user_name, 'user_edit.png'),
714 get_user_name, 'user_edit.png'),
715 'admin_created_users_group': (_('[created] user group'),
715 'admin_created_users_group': (_('[created] user group'),
716 get_users_group, 'group_add.png'),
716 get_users_group, 'group_add.png'),
717 'admin_updated_users_group': (_('[updated] user group'),
717 'admin_updated_users_group': (_('[updated] user group'),
718 get_users_group, 'group_edit.png'),
718 get_users_group, 'group_edit.png'),
719 'user_commented_revision': (_('[commented] on revision in repository'),
719 'user_commented_revision': (_('[commented] on revision in repository'),
720 get_cs_links, 'comment_add.png'),
720 get_cs_links, 'comment_add.png'),
721 'user_commented_pull_request': (_('[commented] on pull request for'),
721 'user_commented_pull_request': (_('[commented] on pull request for'),
722 get_pull_request, 'comment_add.png'),
722 get_pull_request, 'comment_add.png'),
723 'user_closed_pull_request': (_('[closed] pull request for'),
723 'user_closed_pull_request': (_('[closed] pull request for'),
724 get_pull_request, 'tick.png'),
724 get_pull_request, 'tick.png'),
725 'push': (_('[pushed] into'),
725 'push': (_('[pushed] into'),
726 get_cs_links, 'script_add.png'),
726 get_cs_links, 'script_add.png'),
727 'push_local': (_('[committed via RhodeCode] into repository'),
727 'push_local': (_('[committed via RhodeCode] into repository'),
728 get_cs_links, 'script_edit.png'),
728 get_cs_links, 'script_edit.png'),
729 'push_remote': (_('[pulled from remote] into repository'),
729 'push_remote': (_('[pulled from remote] into repository'),
730 get_cs_links, 'connect.png'),
730 get_cs_links, 'connect.png'),
731 'pull': (_('[pulled] from'),
731 'pull': (_('[pulled] from'),
732 None, 'down_16.png'),
732 None, 'down_16.png'),
733 'started_following_repo': (_('[started following] repository'),
733 'started_following_repo': (_('[started following] repository'),
734 None, 'heart_add.png'),
734 None, 'heart_add.png'),
735 'stopped_following_repo': (_('[stopped following] repository'),
735 'stopped_following_repo': (_('[stopped following] repository'),
736 None, 'heart_delete.png'),
736 None, 'heart_delete.png'),
737 }
737 }
738
738
739 action_str = action_map.get(action, action)
739 action_str = action_map.get(action, action)
740 if feed:
740 if feed:
741 action = action_str[0].replace('[', '').replace(']', '')
741 action = action_str[0].replace('[', '').replace(']', '')
742 else:
742 else:
743 action = action_str[0]\
743 action = action_str[0]\
744 .replace('[', '<span class="journal_highlight">')\
744 .replace('[', '<span class="journal_highlight">')\
745 .replace(']', '</span>')
745 .replace(']', '</span>')
746
746
747 action_params_func = lambda: ""
747 action_params_func = lambda: ""
748
748
749 if callable(action_str[1]):
749 if callable(action_str[1]):
750 action_params_func = action_str[1]
750 action_params_func = action_str[1]
751
751
752 def action_parser_icon():
752 def action_parser_icon():
753 action = user_log.action
753 action = user_log.action
754 action_params = None
754 action_params = None
755 x = action.split(':')
755 x = action.split(':')
756
756
757 if len(x) > 1:
757 if len(x) > 1:
758 action, action_params = x
758 action, action_params = x
759
759
760 tmpl = """<img src="%s%s" alt="%s"/>"""
760 tmpl = """<img src="%s%s" alt="%s"/>"""
761 ico = action_map.get(action, ['', '', ''])[2]
761 ico = action_map.get(action, ['', '', ''])[2]
762 return literal(tmpl % ((url('/images/icons/')), ico, action))
762 return literal(tmpl % ((url('/images/icons/')), ico, action))
763
763
764 # returned callbacks we need to call to get
764 # returned callbacks we need to call to get
765 return [lambda: literal(action), action_params_func, action_parser_icon]
765 return [lambda: literal(action), action_params_func, action_parser_icon]
766
766
767
767
768
768
769 #==============================================================================
769 #==============================================================================
770 # PERMS
770 # PERMS
771 #==============================================================================
771 #==============================================================================
772 from rhodecode.lib.auth import HasPermissionAny, HasPermissionAll, \
772 from rhodecode.lib.auth import HasPermissionAny, HasPermissionAll, \
773 HasRepoPermissionAny, HasRepoPermissionAll, HasReposGroupPermissionAll, \
773 HasRepoPermissionAny, HasRepoPermissionAll, HasReposGroupPermissionAll, \
774 HasReposGroupPermissionAny
774 HasReposGroupPermissionAny
775
775
776
776
777 #==============================================================================
777 #==============================================================================
778 # GRAVATAR URL
778 # GRAVATAR URL
779 #==============================================================================
779 #==============================================================================
780
780
781 def gravatar_url(email_address, size=30, ssl_enabled=True):
781 def gravatar_url(email_address, size=30, ssl_enabled=True):
782 from pylons import url # doh, we need to re-import url to mock it later
782 from pylons import url # doh, we need to re-import url to mock it later
783 from rhodecode import CONFIG
783 from rhodecode import CONFIG
784
784
785 _def = 'anonymous@rhodecode.org' # default gravatar
785 _def = 'anonymous@rhodecode.org' # default gravatar
786 use_gravatar = str2bool(CONFIG.get('use_gravatar'))
786 use_gravatar = str2bool(CONFIG.get('use_gravatar'))
787 alternative_gravatar_url = CONFIG.get('alternative_gravatar_url', '')
787 alternative_gravatar_url = CONFIG.get('alternative_gravatar_url', '')
788 email_address = email_address or _def
788 email_address = email_address or _def
789 if not use_gravatar or not email_address or email_address == _def:
789 if not use_gravatar or not email_address or email_address == _def:
790 f = lambda a, l: min(l, key=lambda x: abs(x - a))
790 f = lambda a, l: min(l, key=lambda x: abs(x - a))
791 return url("/images/user%s.png" % f(size, [14, 16, 20, 24, 30]))
791 return url("/images/user%s.png" % f(size, [14, 16, 20, 24, 30]))
792
792
793 if use_gravatar and alternative_gravatar_url:
793 if use_gravatar and alternative_gravatar_url:
794 tmpl = alternative_gravatar_url
794 tmpl = alternative_gravatar_url
795 parsed_url = urlparse.urlparse(url.current(qualified=True))
795 parsed_url = urlparse.urlparse(url.current(qualified=True))
796 tmpl = tmpl.replace('{email}', email_address)\
796 tmpl = tmpl.replace('{email}', email_address)\
797 .replace('{md5email}', hashlib.md5(email_address.lower()).hexdigest()) \
797 .replace('{md5email}', hashlib.md5(email_address.lower()).hexdigest()) \
798 .replace('{netloc}', parsed_url.netloc)\
798 .replace('{netloc}', parsed_url.netloc)\
799 .replace('{scheme}', parsed_url.scheme)\
799 .replace('{scheme}', parsed_url.scheme)\
800 .replace('{size}', str(size))
800 .replace('{size}', str(size))
801 return tmpl
801 return tmpl
802
802
803 default = 'identicon'
803 default = 'identicon'
804 baseurl_nossl = "http://www.gravatar.com/avatar/"
804 baseurl_nossl = "http://www.gravatar.com/avatar/"
805 baseurl_ssl = "https://secure.gravatar.com/avatar/"
805 baseurl_ssl = "https://secure.gravatar.com/avatar/"
806 baseurl = baseurl_ssl if ssl_enabled else baseurl_nossl
806 baseurl = baseurl_ssl if ssl_enabled else baseurl_nossl
807
807
808 if isinstance(email_address, unicode):
808 if isinstance(email_address, unicode):
809 #hashlib crashes on unicode items
809 #hashlib crashes on unicode items
810 email_address = safe_str(email_address)
810 email_address = safe_str(email_address)
811 # construct the url
811 # construct the url
812 gravatar_url = baseurl + hashlib.md5(email_address.lower()).hexdigest() + "?"
812 gravatar_url = baseurl + hashlib.md5(email_address.lower()).hexdigest() + "?"
813 gravatar_url += urllib.urlencode({'d': default, 's': str(size)})
813 gravatar_url += urllib.urlencode({'d': default, 's': str(size)})
814
814
815 return gravatar_url
815 return gravatar_url
816
816
817
817
818 class Page(_Page):
818 class Page(_Page):
819 """
819 """
820 Custom pager to match rendering style with YUI paginator
820 Custom pager to match rendering style with YUI paginator
821 """
821 """
822
822
823 def _get_pos(self, cur_page, max_page, items):
823 def _get_pos(self, cur_page, max_page, items):
824 edge = (items / 2) + 1
824 edge = (items / 2) + 1
825 if (cur_page <= edge):
825 if (cur_page <= edge):
826 radius = max(items / 2, items - cur_page)
826 radius = max(items / 2, items - cur_page)
827 elif (max_page - cur_page) < edge:
827 elif (max_page - cur_page) < edge:
828 radius = (items - 1) - (max_page - cur_page)
828 radius = (items - 1) - (max_page - cur_page)
829 else:
829 else:
830 radius = items / 2
830 radius = items / 2
831
831
832 left = max(1, (cur_page - (radius)))
832 left = max(1, (cur_page - (radius)))
833 right = min(max_page, cur_page + (radius))
833 right = min(max_page, cur_page + (radius))
834 return left, cur_page, right
834 return left, cur_page, right
835
835
836 def _range(self, regexp_match):
836 def _range(self, regexp_match):
837 """
837 """
838 Return range of linked pages (e.g. '1 2 [3] 4 5 6 7 8').
838 Return range of linked pages (e.g. '1 2 [3] 4 5 6 7 8').
839
839
840 Arguments:
840 Arguments:
841
841
842 regexp_match
842 regexp_match
843 A "re" (regular expressions) match object containing the
843 A "re" (regular expressions) match object containing the
844 radius of linked pages around the current page in
844 radius of linked pages around the current page in
845 regexp_match.group(1) as a string
845 regexp_match.group(1) as a string
846
846
847 This function is supposed to be called as a callable in
847 This function is supposed to be called as a callable in
848 re.sub.
848 re.sub.
849
849
850 """
850 """
851 radius = int(regexp_match.group(1))
851 radius = int(regexp_match.group(1))
852
852
853 # Compute the first and last page number within the radius
853 # Compute the first and last page number within the radius
854 # e.g. '1 .. 5 6 [7] 8 9 .. 12'
854 # e.g. '1 .. 5 6 [7] 8 9 .. 12'
855 # -> leftmost_page = 5
855 # -> leftmost_page = 5
856 # -> rightmost_page = 9
856 # -> rightmost_page = 9
857 leftmost_page, _cur, rightmost_page = self._get_pos(self.page,
857 leftmost_page, _cur, rightmost_page = self._get_pos(self.page,
858 self.last_page,
858 self.last_page,
859 (radius * 2) + 1)
859 (radius * 2) + 1)
860 nav_items = []
860 nav_items = []
861
861
862 # Create a link to the first page (unless we are on the first page
862 # Create a link to the first page (unless we are on the first page
863 # or there would be no need to insert '..' spacers)
863 # or there would be no need to insert '..' spacers)
864 if self.page != self.first_page and self.first_page < leftmost_page:
864 if self.page != self.first_page and self.first_page < leftmost_page:
865 nav_items.append(self._pagerlink(self.first_page, self.first_page))
865 nav_items.append(self._pagerlink(self.first_page, self.first_page))
866
866
867 # Insert dots if there are pages between the first page
867 # Insert dots if there are pages between the first page
868 # and the currently displayed page range
868 # and the currently displayed page range
869 if leftmost_page - self.first_page > 1:
869 if leftmost_page - self.first_page > 1:
870 # Wrap in a SPAN tag if nolink_attr is set
870 # Wrap in a SPAN tag if nolink_attr is set
871 text = '..'
871 text = '..'
872 if self.dotdot_attr:
872 if self.dotdot_attr:
873 text = HTML.span(c=text, **self.dotdot_attr)
873 text = HTML.span(c=text, **self.dotdot_attr)
874 nav_items.append(text)
874 nav_items.append(text)
875
875
876 for thispage in xrange(leftmost_page, rightmost_page + 1):
876 for thispage in xrange(leftmost_page, rightmost_page + 1):
877 # Hilight the current page number and do not use a link
877 # Hilight the current page number and do not use a link
878 if thispage == self.page:
878 if thispage == self.page:
879 text = '%s' % (thispage,)
879 text = '%s' % (thispage,)
880 # Wrap in a SPAN tag if nolink_attr is set
880 # Wrap in a SPAN tag if nolink_attr is set
881 if self.curpage_attr:
881 if self.curpage_attr:
882 text = HTML.span(c=text, **self.curpage_attr)
882 text = HTML.span(c=text, **self.curpage_attr)
883 nav_items.append(text)
883 nav_items.append(text)
884 # Otherwise create just a link to that page
884 # Otherwise create just a link to that page
885 else:
885 else:
886 text = '%s' % (thispage,)
886 text = '%s' % (thispage,)
887 nav_items.append(self._pagerlink(thispage, text))
887 nav_items.append(self._pagerlink(thispage, text))
888
888
889 # Insert dots if there are pages between the displayed
889 # Insert dots if there are pages between the displayed
890 # page numbers and the end of the page range
890 # page numbers and the end of the page range
891 if self.last_page - rightmost_page > 1:
891 if self.last_page - rightmost_page > 1:
892 text = '..'
892 text = '..'
893 # Wrap in a SPAN tag if nolink_attr is set
893 # Wrap in a SPAN tag if nolink_attr is set
894 if self.dotdot_attr:
894 if self.dotdot_attr:
895 text = HTML.span(c=text, **self.dotdot_attr)
895 text = HTML.span(c=text, **self.dotdot_attr)
896 nav_items.append(text)
896 nav_items.append(text)
897
897
898 # Create a link to the very last page (unless we are on the last
898 # Create a link to the very last page (unless we are on the last
899 # page or there would be no need to insert '..' spacers)
899 # page or there would be no need to insert '..' spacers)
900 if self.page != self.last_page and rightmost_page < self.last_page:
900 if self.page != self.last_page and rightmost_page < self.last_page:
901 nav_items.append(self._pagerlink(self.last_page, self.last_page))
901 nav_items.append(self._pagerlink(self.last_page, self.last_page))
902
902
903 ## prerender links
904 nav_items.append(literal('<link rel="prerender" href="/rhodecode/changelog/1?page=%s">' % str(int(self.page)+1)))
903 return self.separator.join(nav_items)
905 return self.separator.join(nav_items)
904
906
905 def pager(self, format='~2~', page_param='page', partial_param='partial',
907 def pager(self, format='~2~', page_param='page', partial_param='partial',
906 show_if_single_page=False, separator=' ', onclick=None,
908 show_if_single_page=False, separator=' ', onclick=None,
907 symbol_first='<<', symbol_last='>>',
909 symbol_first='<<', symbol_last='>>',
908 symbol_previous='<', symbol_next='>',
910 symbol_previous='<', symbol_next='>',
909 link_attr={'class': 'pager_link'},
911 link_attr={'class': 'pager_link', 'rel': 'prerender'},
910 curpage_attr={'class': 'pager_curpage'},
912 curpage_attr={'class': 'pager_curpage'},
911 dotdot_attr={'class': 'pager_dotdot'}, **kwargs):
913 dotdot_attr={'class': 'pager_dotdot'}, **kwargs):
912
914
913 self.curpage_attr = curpage_attr
915 self.curpage_attr = curpage_attr
914 self.separator = separator
916 self.separator = separator
915 self.pager_kwargs = kwargs
917 self.pager_kwargs = kwargs
916 self.page_param = page_param
918 self.page_param = page_param
917 self.partial_param = partial_param
919 self.partial_param = partial_param
918 self.onclick = onclick
920 self.onclick = onclick
919 self.link_attr = link_attr
921 self.link_attr = link_attr
920 self.dotdot_attr = dotdot_attr
922 self.dotdot_attr = dotdot_attr
921
923
922 # Don't show navigator if there is no more than one page
924 # Don't show navigator if there is no more than one page
923 if self.page_count == 0 or (self.page_count == 1 and not show_if_single_page):
925 if self.page_count == 0 or (self.page_count == 1 and not show_if_single_page):
924 return ''
926 return ''
925
927
926 from string import Template
928 from string import Template
927 # Replace ~...~ in token format by range of pages
929 # Replace ~...~ in token format by range of pages
928 result = re.sub(r'~(\d+)~', self._range, format)
930 result = re.sub(r'~(\d+)~', self._range, format)
929
931
930 # Interpolate '%' variables
932 # Interpolate '%' variables
931 result = Template(result).safe_substitute({
933 result = Template(result).safe_substitute({
932 'first_page': self.first_page,
934 'first_page': self.first_page,
933 'last_page': self.last_page,
935 'last_page': self.last_page,
934 'page': self.page,
936 'page': self.page,
935 'page_count': self.page_count,
937 'page_count': self.page_count,
936 'items_per_page': self.items_per_page,
938 'items_per_page': self.items_per_page,
937 'first_item': self.first_item,
939 'first_item': self.first_item,
938 'last_item': self.last_item,
940 'last_item': self.last_item,
939 'item_count': self.item_count,
941 'item_count': self.item_count,
940 'link_first': self.page > self.first_page and \
942 'link_first': self.page > self.first_page and \
941 self._pagerlink(self.first_page, symbol_first) or '',
943 self._pagerlink(self.first_page, symbol_first) or '',
942 'link_last': self.page < self.last_page and \
944 'link_last': self.page < self.last_page and \
943 self._pagerlink(self.last_page, symbol_last) or '',
945 self._pagerlink(self.last_page, symbol_last) or '',
944 'link_previous': self.previous_page and \
946 'link_previous': self.previous_page and \
945 self._pagerlink(self.previous_page, symbol_previous) \
947 self._pagerlink(self.previous_page, symbol_previous) \
946 or HTML.span(symbol_previous, class_="yui-pg-previous"),
948 or HTML.span(symbol_previous, class_="yui-pg-previous"),
947 'link_next': self.next_page and \
949 'link_next': self.next_page and \
948 self._pagerlink(self.next_page, symbol_next) \
950 self._pagerlink(self.next_page, symbol_next) \
949 or HTML.span(symbol_next, class_="yui-pg-next")
951 or HTML.span(symbol_next, class_="yui-pg-next")
950 })
952 })
951
953
952 return literal(result)
954 return literal(result)
953
955
954
956
955 #==============================================================================
957 #==============================================================================
956 # REPO PAGER, PAGER FOR REPOSITORY
958 # REPO PAGER, PAGER FOR REPOSITORY
957 #==============================================================================
959 #==============================================================================
958 class RepoPage(Page):
960 class RepoPage(Page):
959
961
960 def __init__(self, collection, page=1, items_per_page=20,
962 def __init__(self, collection, page=1, items_per_page=20,
961 item_count=None, url=None, **kwargs):
963 item_count=None, url=None, **kwargs):
962
964
963 """Create a "RepoPage" instance. special pager for paging
965 """Create a "RepoPage" instance. special pager for paging
964 repository
966 repository
965 """
967 """
966 self._url_generator = url
968 self._url_generator = url
967
969
968 # Safe the kwargs class-wide so they can be used in the pager() method
970 # Safe the kwargs class-wide so they can be used in the pager() method
969 self.kwargs = kwargs
971 self.kwargs = kwargs
970
972
971 # Save a reference to the collection
973 # Save a reference to the collection
972 self.original_collection = collection
974 self.original_collection = collection
973
975
974 self.collection = collection
976 self.collection = collection
975
977
976 # The self.page is the number of the current page.
978 # The self.page is the number of the current page.
977 # The first page has the number 1!
979 # The first page has the number 1!
978 try:
980 try:
979 self.page = int(page) # make it int() if we get it as a string
981 self.page = int(page) # make it int() if we get it as a string
980 except (ValueError, TypeError):
982 except (ValueError, TypeError):
981 self.page = 1
983 self.page = 1
982
984
983 self.items_per_page = items_per_page
985 self.items_per_page = items_per_page
984
986
985 # Unless the user tells us how many items the collections has
987 # Unless the user tells us how many items the collections has
986 # we calculate that ourselves.
988 # we calculate that ourselves.
987 if item_count is not None:
989 if item_count is not None:
988 self.item_count = item_count
990 self.item_count = item_count
989 else:
991 else:
990 self.item_count = len(self.collection)
992 self.item_count = len(self.collection)
991
993
992 # Compute the number of the first and last available page
994 # Compute the number of the first and last available page
993 if self.item_count > 0:
995 if self.item_count > 0:
994 self.first_page = 1
996 self.first_page = 1
995 self.page_count = int(math.ceil(float(self.item_count) /
997 self.page_count = int(math.ceil(float(self.item_count) /
996 self.items_per_page))
998 self.items_per_page))
997 self.last_page = self.first_page + self.page_count - 1
999 self.last_page = self.first_page + self.page_count - 1
998
1000
999 # Make sure that the requested page number is the range of
1001 # Make sure that the requested page number is the range of
1000 # valid pages
1002 # valid pages
1001 if self.page > self.last_page:
1003 if self.page > self.last_page:
1002 self.page = self.last_page
1004 self.page = self.last_page
1003 elif self.page < self.first_page:
1005 elif self.page < self.first_page:
1004 self.page = self.first_page
1006 self.page = self.first_page
1005
1007
1006 # Note: the number of items on this page can be less than
1008 # Note: the number of items on this page can be less than
1007 # items_per_page if the last page is not full
1009 # items_per_page if the last page is not full
1008 self.first_item = max(0, (self.item_count) - (self.page *
1010 self.first_item = max(0, (self.item_count) - (self.page *
1009 items_per_page))
1011 items_per_page))
1010 self.last_item = ((self.item_count - 1) - items_per_page *
1012 self.last_item = ((self.item_count - 1) - items_per_page *
1011 (self.page - 1))
1013 (self.page - 1))
1012
1014
1013 self.items = list(self.collection[self.first_item:self.last_item + 1])
1015 self.items = list(self.collection[self.first_item:self.last_item + 1])
1014
1016
1015 # Links to previous and next page
1017 # Links to previous and next page
1016 if self.page > self.first_page:
1018 if self.page > self.first_page:
1017 self.previous_page = self.page - 1
1019 self.previous_page = self.page - 1
1018 else:
1020 else:
1019 self.previous_page = None
1021 self.previous_page = None
1020
1022
1021 if self.page < self.last_page:
1023 if self.page < self.last_page:
1022 self.next_page = self.page + 1
1024 self.next_page = self.page + 1
1023 else:
1025 else:
1024 self.next_page = None
1026 self.next_page = None
1025
1027
1026 # No items available
1028 # No items available
1027 else:
1029 else:
1028 self.first_page = None
1030 self.first_page = None
1029 self.page_count = 0
1031 self.page_count = 0
1030 self.last_page = None
1032 self.last_page = None
1031 self.first_item = None
1033 self.first_item = None
1032 self.last_item = None
1034 self.last_item = None
1033 self.previous_page = None
1035 self.previous_page = None
1034 self.next_page = None
1036 self.next_page = None
1035 self.items = []
1037 self.items = []
1036
1038
1037 # This is a subclass of the 'list' type. Initialise the list now.
1039 # This is a subclass of the 'list' type. Initialise the list now.
1038 list.__init__(self, reversed(self.items))
1040 list.__init__(self, reversed(self.items))
1039
1041
1040
1042
1041 def changed_tooltip(nodes):
1043 def changed_tooltip(nodes):
1042 """
1044 """
1043 Generates a html string for changed nodes in changeset page.
1045 Generates a html string for changed nodes in changeset page.
1044 It limits the output to 30 entries
1046 It limits the output to 30 entries
1045
1047
1046 :param nodes: LazyNodesGenerator
1048 :param nodes: LazyNodesGenerator
1047 """
1049 """
1048 if nodes:
1050 if nodes:
1049 pref = ': <br/> '
1051 pref = ': <br/> '
1050 suf = ''
1052 suf = ''
1051 if len(nodes) > 30:
1053 if len(nodes) > 30:
1052 suf = '<br/>' + _(' and %s more') % (len(nodes) - 30)
1054 suf = '<br/>' + _(' and %s more') % (len(nodes) - 30)
1053 return literal(pref + '<br/> '.join([safe_unicode(x.path)
1055 return literal(pref + '<br/> '.join([safe_unicode(x.path)
1054 for x in nodes[:30]]) + suf)
1056 for x in nodes[:30]]) + suf)
1055 else:
1057 else:
1056 return ': ' + _('No Files')
1058 return ': ' + _('No Files')
1057
1059
1058
1060
1059 def repo_link(groups_and_repos):
1061 def repo_link(groups_and_repos):
1060 """
1062 """
1061 Makes a breadcrumbs link to repo within a group
1063 Makes a breadcrumbs link to repo within a group
1062 joins &raquo; on each group to create a fancy link
1064 joins &raquo; on each group to create a fancy link
1063
1065
1064 ex::
1066 ex::
1065 group >> subgroup >> repo
1067 group >> subgroup >> repo
1066
1068
1067 :param groups_and_repos:
1069 :param groups_and_repos:
1068 :param last_url:
1070 :param last_url:
1069 """
1071 """
1070 groups, just_name, repo_name = groups_and_repos
1072 groups, just_name, repo_name = groups_and_repos
1071 last_url = url('summary_home', repo_name=repo_name)
1073 last_url = url('summary_home', repo_name=repo_name)
1072 last_link = link_to(just_name, last_url)
1074 last_link = link_to(just_name, last_url)
1073
1075
1074 def make_link(group):
1076 def make_link(group):
1075 return link_to(group.name,
1077 return link_to(group.name,
1076 url('repos_group_home', group_name=group.group_name))
1078 url('repos_group_home', group_name=group.group_name))
1077 return literal(' &raquo; '.join(map(make_link, groups) + ['<span>%s</span>' % last_link]))
1079 return literal(' &raquo; '.join(map(make_link, groups) + ['<span>%s</span>' % last_link]))
1078
1080
1079
1081
1080 def fancy_file_stats(stats):
1082 def fancy_file_stats(stats):
1081 """
1083 """
1082 Displays a fancy two colored bar for number of added/deleted
1084 Displays a fancy two colored bar for number of added/deleted
1083 lines of code on file
1085 lines of code on file
1084
1086
1085 :param stats: two element list of added/deleted lines of code
1087 :param stats: two element list of added/deleted lines of code
1086 """
1088 """
1087 from rhodecode.lib.diffs import NEW_FILENODE, DEL_FILENODE, \
1089 from rhodecode.lib.diffs import NEW_FILENODE, DEL_FILENODE, \
1088 MOD_FILENODE, RENAMED_FILENODE, CHMOD_FILENODE, BIN_FILENODE
1090 MOD_FILENODE, RENAMED_FILENODE, CHMOD_FILENODE, BIN_FILENODE
1089
1091
1090 def cgen(l_type, a_v, d_v):
1092 def cgen(l_type, a_v, d_v):
1091 mapping = {'tr': 'top-right-rounded-corner-mid',
1093 mapping = {'tr': 'top-right-rounded-corner-mid',
1092 'tl': 'top-left-rounded-corner-mid',
1094 'tl': 'top-left-rounded-corner-mid',
1093 'br': 'bottom-right-rounded-corner-mid',
1095 'br': 'bottom-right-rounded-corner-mid',
1094 'bl': 'bottom-left-rounded-corner-mid'}
1096 'bl': 'bottom-left-rounded-corner-mid'}
1095 map_getter = lambda x: mapping[x]
1097 map_getter = lambda x: mapping[x]
1096
1098
1097 if l_type == 'a' and d_v:
1099 if l_type == 'a' and d_v:
1098 #case when added and deleted are present
1100 #case when added and deleted are present
1099 return ' '.join(map(map_getter, ['tl', 'bl']))
1101 return ' '.join(map(map_getter, ['tl', 'bl']))
1100
1102
1101 if l_type == 'a' and not d_v:
1103 if l_type == 'a' and not d_v:
1102 return ' '.join(map(map_getter, ['tr', 'br', 'tl', 'bl']))
1104 return ' '.join(map(map_getter, ['tr', 'br', 'tl', 'bl']))
1103
1105
1104 if l_type == 'd' and a_v:
1106 if l_type == 'd' and a_v:
1105 return ' '.join(map(map_getter, ['tr', 'br']))
1107 return ' '.join(map(map_getter, ['tr', 'br']))
1106
1108
1107 if l_type == 'd' and not a_v:
1109 if l_type == 'd' and not a_v:
1108 return ' '.join(map(map_getter, ['tr', 'br', 'tl', 'bl']))
1110 return ' '.join(map(map_getter, ['tr', 'br', 'tl', 'bl']))
1109
1111
1110 a, d = stats['added'], stats['deleted']
1112 a, d = stats['added'], stats['deleted']
1111 width = 100
1113 width = 100
1112
1114
1113 if stats['binary']:
1115 if stats['binary']:
1114 #binary mode
1116 #binary mode
1115 lbl = ''
1117 lbl = ''
1116 bin_op = 1
1118 bin_op = 1
1117
1119
1118 if BIN_FILENODE in stats['ops']:
1120 if BIN_FILENODE in stats['ops']:
1119 lbl = 'bin+'
1121 lbl = 'bin+'
1120
1122
1121 if NEW_FILENODE in stats['ops']:
1123 if NEW_FILENODE in stats['ops']:
1122 lbl += _('new file')
1124 lbl += _('new file')
1123 bin_op = NEW_FILENODE
1125 bin_op = NEW_FILENODE
1124 elif MOD_FILENODE in stats['ops']:
1126 elif MOD_FILENODE in stats['ops']:
1125 lbl += _('mod')
1127 lbl += _('mod')
1126 bin_op = MOD_FILENODE
1128 bin_op = MOD_FILENODE
1127 elif DEL_FILENODE in stats['ops']:
1129 elif DEL_FILENODE in stats['ops']:
1128 lbl += _('del')
1130 lbl += _('del')
1129 bin_op = DEL_FILENODE
1131 bin_op = DEL_FILENODE
1130 elif RENAMED_FILENODE in stats['ops']:
1132 elif RENAMED_FILENODE in stats['ops']:
1131 lbl += _('rename')
1133 lbl += _('rename')
1132 bin_op = RENAMED_FILENODE
1134 bin_op = RENAMED_FILENODE
1133
1135
1134 #chmod can go with other operations
1136 #chmod can go with other operations
1135 if CHMOD_FILENODE in stats['ops']:
1137 if CHMOD_FILENODE in stats['ops']:
1136 _org_lbl = _('chmod')
1138 _org_lbl = _('chmod')
1137 lbl += _org_lbl if lbl.endswith('+') else '+%s' % _org_lbl
1139 lbl += _org_lbl if lbl.endswith('+') else '+%s' % _org_lbl
1138
1140
1139 #import ipdb;ipdb.set_trace()
1141 #import ipdb;ipdb.set_trace()
1140 b_d = '<div class="bin bin%s %s" style="width:100%%">%s</div>' % (bin_op, cgen('a', a_v='', d_v=0), lbl)
1142 b_d = '<div class="bin bin%s %s" style="width:100%%">%s</div>' % (bin_op, cgen('a', a_v='', d_v=0), lbl)
1141 b_a = '<div class="bin bin1" style="width:0%%"></div>'
1143 b_a = '<div class="bin bin1" style="width:0%%"></div>'
1142 return literal('<div style="width:%spx">%s%s</div>' % (width, b_a, b_d))
1144 return literal('<div style="width:%spx">%s%s</div>' % (width, b_a, b_d))
1143
1145
1144 t = stats['added'] + stats['deleted']
1146 t = stats['added'] + stats['deleted']
1145 unit = float(width) / (t or 1)
1147 unit = float(width) / (t or 1)
1146
1148
1147 # needs > 9% of width to be visible or 0 to be hidden
1149 # needs > 9% of width to be visible or 0 to be hidden
1148 a_p = max(9, unit * a) if a > 0 else 0
1150 a_p = max(9, unit * a) if a > 0 else 0
1149 d_p = max(9, unit * d) if d > 0 else 0
1151 d_p = max(9, unit * d) if d > 0 else 0
1150 p_sum = a_p + d_p
1152 p_sum = a_p + d_p
1151
1153
1152 if p_sum > width:
1154 if p_sum > width:
1153 #adjust the percentage to be == 100% since we adjusted to 9
1155 #adjust the percentage to be == 100% since we adjusted to 9
1154 if a_p > d_p:
1156 if a_p > d_p:
1155 a_p = a_p - (p_sum - width)
1157 a_p = a_p - (p_sum - width)
1156 else:
1158 else:
1157 d_p = d_p - (p_sum - width)
1159 d_p = d_p - (p_sum - width)
1158
1160
1159 a_v = a if a > 0 else ''
1161 a_v = a if a > 0 else ''
1160 d_v = d if d > 0 else ''
1162 d_v = d if d > 0 else ''
1161
1163
1162 d_a = '<div class="added %s" style="width:%s%%">%s</div>' % (
1164 d_a = '<div class="added %s" style="width:%s%%">%s</div>' % (
1163 cgen('a', a_v, d_v), a_p, a_v
1165 cgen('a', a_v, d_v), a_p, a_v
1164 )
1166 )
1165 d_d = '<div class="deleted %s" style="width:%s%%">%s</div>' % (
1167 d_d = '<div class="deleted %s" style="width:%s%%">%s</div>' % (
1166 cgen('d', a_v, d_v), d_p, d_v
1168 cgen('d', a_v, d_v), d_p, d_v
1167 )
1169 )
1168 return literal('<div style="width:%spx">%s%s</div>' % (width, d_a, d_d))
1170 return literal('<div style="width:%spx">%s%s</div>' % (width, d_a, d_d))
1169
1171
1170
1172
1171 def urlify_text(text_, safe=True):
1173 def urlify_text(text_, safe=True):
1172 """
1174 """
1173 Extrac urls from text and make html links out of them
1175 Extrac urls from text and make html links out of them
1174
1176
1175 :param text_:
1177 :param text_:
1176 """
1178 """
1177
1179
1178 url_pat = re.compile(r'''(http[s]?://(?:[a-zA-Z]|[0-9]|[$-_@.&+]'''
1180 url_pat = re.compile(r'''(http[s]?://(?:[a-zA-Z]|[0-9]|[$-_@.&+]'''
1179 '''|[!*\(\),]|(?:%[0-9a-fA-F][0-9a-fA-F]))+)''')
1181 '''|[!*\(\),]|(?:%[0-9a-fA-F][0-9a-fA-F]))+)''')
1180
1182
1181 def url_func(match_obj):
1183 def url_func(match_obj):
1182 url_full = match_obj.groups()[0]
1184 url_full = match_obj.groups()[0]
1183 return '<a href="%(url)s">%(url)s</a>' % ({'url': url_full})
1185 return '<a href="%(url)s">%(url)s</a>' % ({'url': url_full})
1184 _newtext = url_pat.sub(url_func, text_)
1186 _newtext = url_pat.sub(url_func, text_)
1185 if safe:
1187 if safe:
1186 return literal(_newtext)
1188 return literal(_newtext)
1187 return _newtext
1189 return _newtext
1188
1190
1189
1191
1190 def urlify_changesets(text_, repository):
1192 def urlify_changesets(text_, repository):
1191 """
1193 """
1192 Extract revision ids from changeset and make link from them
1194 Extract revision ids from changeset and make link from them
1193
1195
1194 :param text_:
1196 :param text_:
1195 :param repository: repo name to build the URL with
1197 :param repository: repo name to build the URL with
1196 """
1198 """
1197 from pylons import url # doh, we need to re-import url to mock it later
1199 from pylons import url # doh, we need to re-import url to mock it later
1198 URL_PAT = re.compile(r'(^|\s)([0-9a-fA-F]{12,40})($|\s)')
1200 URL_PAT = re.compile(r'(^|\s)([0-9a-fA-F]{12,40})($|\s)')
1199
1201
1200 def url_func(match_obj):
1202 def url_func(match_obj):
1201 rev = match_obj.groups()[1]
1203 rev = match_obj.groups()[1]
1202 pref = match_obj.groups()[0]
1204 pref = match_obj.groups()[0]
1203 suf = match_obj.groups()[2]
1205 suf = match_obj.groups()[2]
1204
1206
1205 tmpl = (
1207 tmpl = (
1206 '%(pref)s<a class="%(cls)s" href="%(url)s">'
1208 '%(pref)s<a class="%(cls)s" href="%(url)s">'
1207 '%(rev)s</a>%(suf)s'
1209 '%(rev)s</a>%(suf)s'
1208 )
1210 )
1209 return tmpl % {
1211 return tmpl % {
1210 'pref': pref,
1212 'pref': pref,
1211 'cls': 'revision-link',
1213 'cls': 'revision-link',
1212 'url': url('changeset_home', repo_name=repository, revision=rev),
1214 'url': url('changeset_home', repo_name=repository, revision=rev),
1213 'rev': rev,
1215 'rev': rev,
1214 'suf': suf
1216 'suf': suf
1215 }
1217 }
1216
1218
1217 newtext = URL_PAT.sub(url_func, text_)
1219 newtext = URL_PAT.sub(url_func, text_)
1218
1220
1219 return newtext
1221 return newtext
1220
1222
1221
1223
1222 def urlify_commit(text_, repository=None, link_=None):
1224 def urlify_commit(text_, repository=None, link_=None):
1223 """
1225 """
1224 Parses given text message and makes proper links.
1226 Parses given text message and makes proper links.
1225 issues are linked to given issue-server, and rest is a changeset link
1227 issues are linked to given issue-server, and rest is a changeset link
1226 if link_ is given, in other case it's a plain text
1228 if link_ is given, in other case it's a plain text
1227
1229
1228 :param text_:
1230 :param text_:
1229 :param repository:
1231 :param repository:
1230 :param link_: changeset link
1232 :param link_: changeset link
1231 """
1233 """
1232 import traceback
1234 import traceback
1233 from pylons import url # doh, we need to re-import url to mock it later
1235 from pylons import url # doh, we need to re-import url to mock it later
1234
1236
1235 def escaper(string):
1237 def escaper(string):
1236 return string.replace('<', '&lt;').replace('>', '&gt;')
1238 return string.replace('<', '&lt;').replace('>', '&gt;')
1237
1239
1238 def linkify_others(t, l):
1240 def linkify_others(t, l):
1239 urls = re.compile(r'(\<a.*?\<\/a\>)',)
1241 urls = re.compile(r'(\<a.*?\<\/a\>)',)
1240 links = []
1242 links = []
1241 for e in urls.split(t):
1243 for e in urls.split(t):
1242 if not urls.match(e):
1244 if not urls.match(e):
1243 links.append('<a class="message-link" href="%s">%s</a>' % (l, e))
1245 links.append('<a class="message-link" href="%s">%s</a>' % (l, e))
1244 else:
1246 else:
1245 links.append(e)
1247 links.append(e)
1246
1248
1247 return ''.join(links)
1249 return ''.join(links)
1248
1250
1249 # urlify changesets - extrac revisions and make link out of them
1251 # urlify changesets - extrac revisions and make link out of them
1250 newtext = urlify_changesets(escaper(text_), repository)
1252 newtext = urlify_changesets(escaper(text_), repository)
1251
1253
1252 # extract http/https links and make them real urls
1254 # extract http/https links and make them real urls
1253 newtext = urlify_text(newtext, safe=False)
1255 newtext = urlify_text(newtext, safe=False)
1254
1256
1255 try:
1257 try:
1256 from rhodecode import CONFIG
1258 from rhodecode import CONFIG
1257 conf = CONFIG
1259 conf = CONFIG
1258
1260
1259 # allow multiple issue servers to be used
1261 # allow multiple issue servers to be used
1260 valid_indices = [
1262 valid_indices = [
1261 x.group(1)
1263 x.group(1)
1262 for x in map(lambda x: re.match(r'issue_pat(.*)', x), conf.keys())
1264 for x in map(lambda x: re.match(r'issue_pat(.*)', x), conf.keys())
1263 if x and 'issue_server_link%s' % x.group(1) in conf
1265 if x and 'issue_server_link%s' % x.group(1) in conf
1264 and 'issue_prefix%s' % x.group(1) in conf
1266 and 'issue_prefix%s' % x.group(1) in conf
1265 ]
1267 ]
1266
1268
1267 log.debug('found issue server suffixes `%s` during valuation of: %s'
1269 log.debug('found issue server suffixes `%s` during valuation of: %s'
1268 % (','.join(valid_indices), newtext))
1270 % (','.join(valid_indices), newtext))
1269
1271
1270 for pattern_index in valid_indices:
1272 for pattern_index in valid_indices:
1271 ISSUE_PATTERN = conf.get('issue_pat%s' % pattern_index)
1273 ISSUE_PATTERN = conf.get('issue_pat%s' % pattern_index)
1272 ISSUE_SERVER_LNK = conf.get('issue_server_link%s' % pattern_index)
1274 ISSUE_SERVER_LNK = conf.get('issue_server_link%s' % pattern_index)
1273 ISSUE_PREFIX = conf.get('issue_prefix%s' % pattern_index)
1275 ISSUE_PREFIX = conf.get('issue_prefix%s' % pattern_index)
1274
1276
1275 log.debug('pattern suffix `%s` PAT:%s SERVER_LINK:%s PREFIX:%s'
1277 log.debug('pattern suffix `%s` PAT:%s SERVER_LINK:%s PREFIX:%s'
1276 % (pattern_index, ISSUE_PATTERN, ISSUE_SERVER_LNK,
1278 % (pattern_index, ISSUE_PATTERN, ISSUE_SERVER_LNK,
1277 ISSUE_PREFIX))
1279 ISSUE_PREFIX))
1278
1280
1279 URL_PAT = re.compile(r'%s' % ISSUE_PATTERN)
1281 URL_PAT = re.compile(r'%s' % ISSUE_PATTERN)
1280
1282
1281 def url_func(match_obj):
1283 def url_func(match_obj):
1282 pref = ''
1284 pref = ''
1283 if match_obj.group().startswith(' '):
1285 if match_obj.group().startswith(' '):
1284 pref = ' '
1286 pref = ' '
1285
1287
1286 issue_id = ''.join(match_obj.groups())
1288 issue_id = ''.join(match_obj.groups())
1287 tmpl = (
1289 tmpl = (
1288 '%(pref)s<a class="%(cls)s" href="%(url)s">'
1290 '%(pref)s<a class="%(cls)s" href="%(url)s">'
1289 '%(issue-prefix)s%(id-repr)s'
1291 '%(issue-prefix)s%(id-repr)s'
1290 '</a>'
1292 '</a>'
1291 )
1293 )
1292 url = ISSUE_SERVER_LNK.replace('{id}', issue_id)
1294 url = ISSUE_SERVER_LNK.replace('{id}', issue_id)
1293 if repository:
1295 if repository:
1294 url = url.replace('{repo}', repository)
1296 url = url.replace('{repo}', repository)
1295 repo_name = repository.split(URL_SEP)[-1]
1297 repo_name = repository.split(URL_SEP)[-1]
1296 url = url.replace('{repo_name}', repo_name)
1298 url = url.replace('{repo_name}', repo_name)
1297
1299
1298 return tmpl % {
1300 return tmpl % {
1299 'pref': pref,
1301 'pref': pref,
1300 'cls': 'issue-tracker-link',
1302 'cls': 'issue-tracker-link',
1301 'url': url,
1303 'url': url,
1302 'id-repr': issue_id,
1304 'id-repr': issue_id,
1303 'issue-prefix': ISSUE_PREFIX,
1305 'issue-prefix': ISSUE_PREFIX,
1304 'serv': ISSUE_SERVER_LNK,
1306 'serv': ISSUE_SERVER_LNK,
1305 }
1307 }
1306 newtext = URL_PAT.sub(url_func, newtext)
1308 newtext = URL_PAT.sub(url_func, newtext)
1307 log.debug('processed prefix:`%s` => %s' % (pattern_index, newtext))
1309 log.debug('processed prefix:`%s` => %s' % (pattern_index, newtext))
1308
1310
1309 # if we actually did something above
1311 # if we actually did something above
1310 if link_:
1312 if link_:
1311 # wrap not links into final link => link_
1313 # wrap not links into final link => link_
1312 newtext = linkify_others(newtext, link_)
1314 newtext = linkify_others(newtext, link_)
1313 except Exception:
1315 except Exception:
1314 log.error(traceback.format_exc())
1316 log.error(traceback.format_exc())
1315 pass
1317 pass
1316
1318
1317 return literal(newtext)
1319 return literal(newtext)
1318
1320
1319
1321
1320 def rst(source):
1322 def rst(source):
1321 return literal('<div class="rst-block">%s</div>' %
1323 return literal('<div class="rst-block">%s</div>' %
1322 MarkupRenderer.rst(source))
1324 MarkupRenderer.rst(source))
1323
1325
1324
1326
1325 def rst_w_mentions(source):
1327 def rst_w_mentions(source):
1326 """
1328 """
1327 Wrapped rst renderer with @mention highlighting
1329 Wrapped rst renderer with @mention highlighting
1328
1330
1329 :param source:
1331 :param source:
1330 """
1332 """
1331 return literal('<div class="rst-block">%s</div>' %
1333 return literal('<div class="rst-block">%s</div>' %
1332 MarkupRenderer.rst_with_mentions(source))
1334 MarkupRenderer.rst_with_mentions(source))
1333
1335
1334
1336
1335 def changeset_status(repo, revision):
1337 def changeset_status(repo, revision):
1336 return ChangesetStatusModel().get_status(repo, revision)
1338 return ChangesetStatusModel().get_status(repo, revision)
1337
1339
1338
1340
1339 def changeset_status_lbl(changeset_status):
1341 def changeset_status_lbl(changeset_status):
1340 return dict(ChangesetStatus.STATUSES).get(changeset_status)
1342 return dict(ChangesetStatus.STATUSES).get(changeset_status)
1341
1343
1342
1344
1343 def get_permission_name(key):
1345 def get_permission_name(key):
1344 return dict(Permission.PERMS).get(key)
1346 return dict(Permission.PERMS).get(key)
1345
1347
1346
1348
1347 def journal_filter_help():
1349 def journal_filter_help():
1348 return _(textwrap.dedent('''
1350 return _(textwrap.dedent('''
1349 Example filter terms:
1351 Example filter terms:
1350 repository:vcs
1352 repository:vcs
1351 username:marcin
1353 username:marcin
1352 action:*push*
1354 action:*push*
1353 ip:127.0.0.1
1355 ip:127.0.0.1
1354 date:20120101
1356 date:20120101
1355 date:[20120101100000 TO 20120102]
1357 date:[20120101100000 TO 20120102]
1356
1358
1357 Generate wildcards using '*' character:
1359 Generate wildcards using '*' character:
1358 "repositroy:vcs*" - search everything starting with 'vcs'
1360 "repositroy:vcs*" - search everything starting with 'vcs'
1359 "repository:*vcs*" - search for repository containing 'vcs'
1361 "repository:*vcs*" - search for repository containing 'vcs'
1360
1362
1361 Optional AND / OR operators in queries
1363 Optional AND / OR operators in queries
1362 "repository:vcs OR repository:test"
1364 "repository:vcs OR repository:test"
1363 "username:test AND repository:test*"
1365 "username:test AND repository:test*"
1364 '''))
1366 '''))
1365
1367
1366
1368
1367 def not_mapped_error(repo_name):
1369 def not_mapped_error(repo_name):
1368 flash(_('%s repository is not mapped to db perhaps'
1370 flash(_('%s repository is not mapped to db perhaps'
1369 ' it was created or renamed from the filesystem'
1371 ' it was created or renamed from the filesystem'
1370 ' please run the application again'
1372 ' please run the application again'
1371 ' in order to rescan repositories') % repo_name, category='error')
1373 ' in order to rescan repositories') % repo_name, category='error')
1372
1374
1373
1375
1374 def ip_range(ip_addr):
1376 def ip_range(ip_addr):
1375 from rhodecode.model.db import UserIpMap
1377 from rhodecode.model.db import UserIpMap
1376 s, e = UserIpMap._get_ip_range(ip_addr)
1378 s, e = UserIpMap._get_ip_range(ip_addr)
1377 return '%s - %s' % (s, e)
1379 return '%s - %s' % (s, e)
@@ -1,280 +1,194 b''
1 # -*- coding: utf-8 -*-
1 # -*- coding: utf-8 -*-
2 """
2 """
3 rhodecode.lib.indexers.__init__
3 rhodecode.lib.indexers.__init__
4 ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
4 ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
5
5
6 Whoosh indexing module for RhodeCode
6 Whoosh indexing module for RhodeCode
7
7
8 :created_on: Aug 17, 2010
8 :created_on: Aug 17, 2010
9 :author: marcink
9 :author: marcink
10 :copyright: (C) 2010-2012 Marcin Kuzminski <marcin@python-works.com>
10 :copyright: (C) 2010-2012 Marcin Kuzminski <marcin@python-works.com>
11 :license: GPLv3, see COPYING for more details.
11 :license: GPLv3, see COPYING for more details.
12 """
12 """
13 # This program is free software: you can redistribute it and/or modify
13 # This program is free software: you can redistribute it and/or modify
14 # it under the terms of the GNU General Public License as published by
14 # it under the terms of the GNU General Public License as published by
15 # the Free Software Foundation, either version 3 of the License, or
15 # the Free Software Foundation, either version 3 of the License, or
16 # (at your option) any later version.
16 # (at your option) any later version.
17 #
17 #
18 # This program is distributed in the hope that it will be useful,
18 # This program is distributed in the hope that it will be useful,
19 # but WITHOUT ANY WARRANTY; without even the implied warranty of
19 # but WITHOUT ANY WARRANTY; without even the implied warranty of
20 # MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
20 # MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
21 # GNU General Public License for more details.
21 # GNU General Public License for more details.
22 #
22 #
23 # You should have received a copy of the GNU General Public License
23 # You should have received a copy of the GNU General Public License
24 # along with this program. If not, see <http://www.gnu.org/licenses/>.
24 # along with this program. If not, see <http://www.gnu.org/licenses/>.
25 import os
25 import os
26 import sys
26 import sys
27 import traceback
28 import logging
27 import logging
29 from os.path import dirname as dn, join as jn
28 from os.path import dirname as dn, join as jn
30
29
31 #to get the rhodecode import
30 #to get the rhodecode import
32 sys.path.append(dn(dn(dn(os.path.realpath(__file__)))))
31 sys.path.append(dn(dn(dn(os.path.realpath(__file__)))))
33
32
34 from string import strip
35 from shutil import rmtree
36
37 from whoosh.analysis import RegexTokenizer, LowercaseFilter, StopFilter
33 from whoosh.analysis import RegexTokenizer, LowercaseFilter, StopFilter
38 from whoosh.fields import TEXT, ID, STORED, NUMERIC, BOOLEAN, Schema, FieldType, DATETIME
34 from whoosh.fields import TEXT, ID, STORED, NUMERIC, BOOLEAN, Schema, FieldType, DATETIME
39 from whoosh.index import create_in, open_dir
40 from whoosh.formats import Characters
35 from whoosh.formats import Characters
41 from whoosh.highlight import highlight, HtmlFormatter, ContextFragmenter
36 from whoosh.highlight import highlight as whoosh_highlight, HtmlFormatter, ContextFragmenter
42
43 from webhelpers.html.builder import escape, literal
44 from sqlalchemy import engine_from_config
45
46 from rhodecode.model import init_model
47 from rhodecode.model.scm import ScmModel
48 from rhodecode.model.repo import RepoModel
49 from rhodecode.config.environment import load_environment
50 from rhodecode.lib.utils2 import LazyProperty
37 from rhodecode.lib.utils2 import LazyProperty
51 from rhodecode.lib.utils import BasePasterCommand, Command, add_cache,\
52 load_rcextensions
53
38
54 log = logging.getLogger(__name__)
39 log = logging.getLogger(__name__)
55
40
56 # CUSTOM ANALYZER wordsplit + lowercase filter
41 # CUSTOM ANALYZER wordsplit + lowercase filter
57 ANALYZER = RegexTokenizer(expression=r"\w+") | LowercaseFilter()
42 ANALYZER = RegexTokenizer(expression=r"\w+") | LowercaseFilter()
58
43
59 #INDEX SCHEMA DEFINITION
44 #INDEX SCHEMA DEFINITION
60 SCHEMA = Schema(
45 SCHEMA = Schema(
61 fileid=ID(unique=True),
46 fileid=ID(unique=True),
62 owner=TEXT(),
47 owner=TEXT(),
63 repository=TEXT(stored=True),
48 repository=TEXT(stored=True),
64 path=TEXT(stored=True),
49 path=TEXT(stored=True),
65 content=FieldType(format=Characters(), analyzer=ANALYZER,
50 content=FieldType(format=Characters(), analyzer=ANALYZER,
66 scorable=True, stored=True),
51 scorable=True, stored=True),
67 modtime=STORED(),
52 modtime=STORED(),
68 extension=TEXT(stored=True)
53 extension=TEXT(stored=True)
69 )
54 )
70
55
71 IDX_NAME = 'HG_INDEX'
56 IDX_NAME = 'HG_INDEX'
72 FORMATTER = HtmlFormatter('span', between='\n<span class="break">...</span>\n')
57 FORMATTER = HtmlFormatter('span', between='\n<span class="break">...</span>\n')
73 FRAGMENTER = ContextFragmenter(200)
58 FRAGMENTER = ContextFragmenter(200)
74
59
75 CHGSETS_SCHEMA = Schema(
60 CHGSETS_SCHEMA = Schema(
76 raw_id=ID(unique=True, stored=True),
61 raw_id=ID(unique=True, stored=True),
77 date=NUMERIC(stored=True),
62 date=NUMERIC(stored=True),
78 last=BOOLEAN(),
63 last=BOOLEAN(),
79 owner=TEXT(),
64 owner=TEXT(),
80 repository=ID(unique=True, stored=True),
65 repository=ID(unique=True, stored=True),
81 author=TEXT(stored=True),
66 author=TEXT(stored=True),
82 message=FieldType(format=Characters(), analyzer=ANALYZER,
67 message=FieldType(format=Characters(), analyzer=ANALYZER,
83 scorable=True, stored=True),
68 scorable=True, stored=True),
84 parents=TEXT(),
69 parents=TEXT(),
85 added=TEXT(),
70 added=TEXT(),
86 removed=TEXT(),
71 removed=TEXT(),
87 changed=TEXT(),
72 changed=TEXT(),
88 )
73 )
89
74
90 CHGSET_IDX_NAME = 'CHGSET_INDEX'
75 CHGSET_IDX_NAME = 'CHGSET_INDEX'
91
76
92 # used only to generate queries in journal
77 # used only to generate queries in journal
93 JOURNAL_SCHEMA = Schema(
78 JOURNAL_SCHEMA = Schema(
94 username=TEXT(),
79 username=TEXT(),
95 date=DATETIME(),
80 date=DATETIME(),
96 action=TEXT(),
81 action=TEXT(),
97 repository=TEXT(),
82 repository=TEXT(),
98 ip=TEXT(),
83 ip=TEXT(),
99 )
84 )
100
85
101
86
102 class MakeIndex(BasePasterCommand):
103
104 max_args = 1
105 min_args = 1
106
107 usage = "CONFIG_FILE"
108 summary = "Creates or update full text search index"
109 group_name = "RhodeCode"
110 takes_config_file = -1
111 parser = Command.standard_parser(verbose=True)
112
113 def command(self):
114 logging.config.fileConfig(self.path_to_ini_file)
115 from pylons import config
116 add_cache(config)
117 engine = engine_from_config(config, 'sqlalchemy.db1.')
118 init_model(engine)
119 index_location = config['index_dir']
120 repo_location = self.options.repo_location \
121 if self.options.repo_location else RepoModel().repos_path
122 repo_list = map(strip, self.options.repo_list.split(',')) \
123 if self.options.repo_list else None
124 repo_update_list = map(strip, self.options.repo_update_list.split(',')) \
125 if self.options.repo_update_list else None
126 load_rcextensions(config['here'])
127 #======================================================================
128 # WHOOSH DAEMON
129 #======================================================================
130 from rhodecode.lib.pidlock import LockHeld, DaemonLock
131 from rhodecode.lib.indexers.daemon import WhooshIndexingDaemon
132 try:
133 l = DaemonLock(file_=jn(dn(dn(index_location)), 'make_index.lock'))
134 WhooshIndexingDaemon(index_location=index_location,
135 repo_location=repo_location,
136 repo_list=repo_list,
137 repo_update_list=repo_update_list)\
138 .run(full_index=self.options.full_index)
139 l.release()
140 except LockHeld:
141 sys.exit(1)
142
143 def update_parser(self):
144 self.parser.add_option('--repo-location',
145 action='store',
146 dest='repo_location',
147 help="Specifies repositories location to index OPTIONAL",
148 )
149 self.parser.add_option('--index-only',
150 action='store',
151 dest='repo_list',
152 help="Specifies a comma separated list of repositores "
153 "to build index on. If not given all repositories "
154 "are scanned for indexing. OPTIONAL",
155 )
156 self.parser.add_option('--update-only',
157 action='store',
158 dest='repo_update_list',
159 help="Specifies a comma separated list of repositores "
160 "to re-build index on. OPTIONAL",
161 )
162 self.parser.add_option('-f',
163 action='store_true',
164 dest='full_index',
165 help="Specifies that index should be made full i.e"
166 " destroy old and build from scratch",
167 default=False)
168
169
170 class WhooshResultWrapper(object):
87 class WhooshResultWrapper(object):
171 def __init__(self, search_type, searcher, matcher, highlight_items,
88 def __init__(self, search_type, searcher, matcher, highlight_items,
172 repo_location):
89 repo_location):
173 self.search_type = search_type
90 self.search_type = search_type
174 self.searcher = searcher
91 self.searcher = searcher
175 self.matcher = matcher
92 self.matcher = matcher
176 self.highlight_items = highlight_items
93 self.highlight_items = highlight_items
177 self.fragment_size = 200
94 self.fragment_size = 200
178 self.repo_location = repo_location
95 self.repo_location = repo_location
179
96
180 @LazyProperty
97 @LazyProperty
181 def doc_ids(self):
98 def doc_ids(self):
182 docs_id = []
99 docs_id = []
183 while self.matcher.is_active():
100 while self.matcher.is_active():
184 docnum = self.matcher.id()
101 docnum = self.matcher.id()
185 chunks = [offsets for offsets in self.get_chunks()]
102 chunks = [offsets for offsets in self.get_chunks()]
186 docs_id.append([docnum, chunks])
103 docs_id.append([docnum, chunks])
187 self.matcher.next()
104 self.matcher.next()
188 return docs_id
105 return docs_id
189
106
190 def __str__(self):
107 def __str__(self):
191 return '<%s at %s>' % (self.__class__.__name__, len(self.doc_ids))
108 return '<%s at %s>' % (self.__class__.__name__, len(self.doc_ids))
192
109
193 def __repr__(self):
110 def __repr__(self):
194 return self.__str__()
111 return self.__str__()
195
112
196 def __len__(self):
113 def __len__(self):
197 return len(self.doc_ids)
114 return len(self.doc_ids)
198
115
199 def __iter__(self):
116 def __iter__(self):
200 """
117 """
201 Allows Iteration over results,and lazy generate content
118 Allows Iteration over results,and lazy generate content
202
119
203 *Requires* implementation of ``__getitem__`` method.
120 *Requires* implementation of ``__getitem__`` method.
204 """
121 """
205 for docid in self.doc_ids:
122 for docid in self.doc_ids:
206 yield self.get_full_content(docid)
123 yield self.get_full_content(docid)
207
124
208 def __getitem__(self, key):
125 def __getitem__(self, key):
209 """
126 """
210 Slicing of resultWrapper
127 Slicing of resultWrapper
211 """
128 """
212 i, j = key.start, key.stop
129 i, j = key.start, key.stop
213
130
214 slices = []
131 slices = []
215 for docid in self.doc_ids[i:j]:
132 for docid in self.doc_ids[i:j]:
216 slices.append(self.get_full_content(docid))
133 slices.append(self.get_full_content(docid))
217 return slices
134 return slices
218
135
219 def get_full_content(self, docid):
136 def get_full_content(self, docid):
220 res = self.searcher.stored_fields(docid[0])
137 res = self.searcher.stored_fields(docid[0])
221 log.debug('result: %s' % res)
138 log.debug('result: %s' % res)
222 if self.search_type == 'content':
139 if self.search_type == 'content':
223 full_repo_path = jn(self.repo_location, res['repository'])
140 full_repo_path = jn(self.repo_location, res['repository'])
224 f_path = res['path'].split(full_repo_path)[-1]
141 f_path = res['path'].split(full_repo_path)[-1]
225 f_path = f_path.lstrip(os.sep)
142 f_path = f_path.lstrip(os.sep)
226 content_short = self.get_short_content(res, docid[1])
143 content_short = self.get_short_content(res, docid[1])
227 res.update({'content_short': content_short,
144 res.update({'content_short': content_short,
228 'content_short_hl': self.highlight(content_short),
145 'content_short_hl': self.highlight(content_short),
229 'f_path': f_path
146 'f_path': f_path
230 })
147 })
231 elif self.search_type == 'path':
148 elif self.search_type == 'path':
232 full_repo_path = jn(self.repo_location, res['repository'])
149 full_repo_path = jn(self.repo_location, res['repository'])
233 f_path = res['path'].split(full_repo_path)[-1]
150 f_path = res['path'].split(full_repo_path)[-1]
234 f_path = f_path.lstrip(os.sep)
151 f_path = f_path.lstrip(os.sep)
235 res.update({'f_path': f_path})
152 res.update({'f_path': f_path})
236 elif self.search_type == 'message':
153 elif self.search_type == 'message':
237 res.update({'message_hl': self.highlight(res['message'])})
154 res.update({'message_hl': self.highlight(res['message'])})
238
155
239 log.debug('result: %s' % res)
156 log.debug('result: %s' % res)
240
157
241 return res
158 return res
242
159
243 def get_short_content(self, res, chunks):
160 def get_short_content(self, res, chunks):
244
161
245 return ''.join([res['content'][chunk[0]:chunk[1]] for chunk in chunks])
162 return ''.join([res['content'][chunk[0]:chunk[1]] for chunk in chunks])
246
163
247 def get_chunks(self):
164 def get_chunks(self):
248 """
165 """
249 Smart function that implements chunking the content
166 Smart function that implements chunking the content
250 but not overlap chunks so it doesn't highlight the same
167 but not overlap chunks so it doesn't highlight the same
251 close occurrences twice.
168 close occurrences twice.
252
253 :param matcher:
254 :param size:
255 """
169 """
256 memory = [(0, 0)]
170 memory = [(0, 0)]
257 if self.matcher.supports('positions'):
171 if self.matcher.supports('positions'):
258 for span in self.matcher.spans():
172 for span in self.matcher.spans():
259 start = span.startchar or 0
173 start = span.startchar or 0
260 end = span.endchar or 0
174 end = span.endchar or 0
261 start_offseted = max(0, start - self.fragment_size)
175 start_offseted = max(0, start - self.fragment_size)
262 end_offseted = end + self.fragment_size
176 end_offseted = end + self.fragment_size
263
177
264 if start_offseted < memory[-1][1]:
178 if start_offseted < memory[-1][1]:
265 start_offseted = memory[-1][1]
179 start_offseted = memory[-1][1]
266 memory.append((start_offseted, end_offseted,))
180 memory.append((start_offseted, end_offseted,))
267 yield (start_offseted, end_offseted,)
181 yield (start_offseted, end_offseted,)
268
182
269 def highlight(self, content, top=5):
183 def highlight(self, content, top=5):
270 if self.search_type not in ['content', 'message']:
184 if self.search_type not in ['content', 'message']:
271 return ''
185 return ''
272 hl = highlight(
186 hl = whoosh_highlight(
273 text=content,
187 text=content,
274 terms=self.highlight_items,
188 terms=self.highlight_items,
275 analyzer=ANALYZER,
189 analyzer=ANALYZER,
276 fragmenter=FRAGMENTER,
190 fragmenter=FRAGMENTER,
277 formatter=FORMATTER,
191 formatter=FORMATTER,
278 top=top
192 top=top
279 )
193 )
280 return hl
194 return hl
@@ -1,85 +1,84 b''
1 # -*- coding: utf-8 -*-
1 # -*- coding: utf-8 -*-
2 """
2 """
3 rhodecode.lib.paster_commands.cache_keys
3 rhodecode.lib.paster_commands.cache_keys
4 ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
4 ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
5
5
6 cleanup-keys paster command for RhodeCode
6 cleanup-keys paster command for RhodeCode
7
7
8
8
9 :created_on: mar 27, 2013
9 :created_on: mar 27, 2013
10 :author: marcink
10 :author: marcink
11 :copyright: (C) 2010-2013 Marcin Kuzminski <marcin@python-works.com>
11 :copyright: (C) 2010-2013 Marcin Kuzminski <marcin@python-works.com>
12 :license: GPLv3, see COPYING for more details.
12 :license: GPLv3, see COPYING for more details.
13 """
13 """
14 # This program is free software: you can redistribute it and/or modify
14 # This program is free software: you can redistribute it and/or modify
15 # it under the terms of the GNU General Public License as published by
15 # it under the terms of the GNU General Public License as published by
16 # the Free Software Foundation, either version 3 of the License, or
16 # the Free Software Foundation, either version 3 of the License, or
17 # (at your option) any later version.
17 # (at your option) any later version.
18 #
18 #
19 # This program is distributed in the hope that it will be useful,
19 # This program is distributed in the hope that it will be useful,
20 # but WITHOUT ANY WARRANTY; without even the implied warranty of
20 # but WITHOUT ANY WARRANTY; without even the implied warranty of
21 # MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
21 # MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
22 # GNU General Public License for more details.
22 # GNU General Public License for more details.
23 #
23 #
24 # You should have received a copy of the GNU General Public License
24 # You should have received a copy of the GNU General Public License
25 # along with this program. If not, see <http://www.gnu.org/licenses/>.
25 # along with this program. If not, see <http://www.gnu.org/licenses/>.
26 from __future__ import with_statement
26 from __future__ import with_statement
27
27
28 import os
28 import os
29 import sys
29 import sys
30 import logging
30 import logging
31
31
32 from os.path import dirname as dn, join as jn
33 from rhodecode.model.meta import Session
32 from rhodecode.model.meta import Session
34 #to get the rhodecode import
33 from rhodecode.lib.utils import BasePasterCommand
34 from rhodecode.model.db import CacheInvalidation
35
36 # fix rhodecode import
37 from os.path import dirname as dn
35 rc_path = dn(dn(dn(os.path.realpath(__file__))))
38 rc_path = dn(dn(dn(os.path.realpath(__file__))))
36 sys.path.append(rc_path)
39 sys.path.append(rc_path)
37 from rhodecode.lib.utils import BasePasterCommand
38
39 from rhodecode.model.db import CacheInvalidation
40
41
40
42 log = logging.getLogger(__name__)
41 log = logging.getLogger(__name__)
43
42
44
43
45 class Command(BasePasterCommand):
44 class Command(BasePasterCommand):
46
45
47 max_args = 1
46 max_args = 1
48 min_args = 1
47 min_args = 1
49
48
50 usage = "CONFIG_FILE"
49 usage = "CONFIG_FILE"
51 group_name = "RhodeCode"
50 group_name = "RhodeCode"
52 takes_config_file = -1
51 takes_config_file = -1
53 parser = BasePasterCommand.standard_parser(verbose=True)
52 parser = BasePasterCommand.standard_parser(verbose=True)
54 summary = "Cache keys utils"
53 summary = "Cache keys utils"
55
54
56 def command(self):
55 def command(self):
57 #get SqlAlchemy session
56 #get SqlAlchemy session
58 self._init_session()
57 self._init_session()
59 _caches = CacheInvalidation.query().order_by(CacheInvalidation.cache_key).all()
58 _caches = CacheInvalidation.query().order_by(CacheInvalidation.cache_key).all()
60 if self.options.show:
59 if self.options.show:
61 for c_obj in _caches:
60 for c_obj in _caches:
62 print 'key:%s active:%s' % (c_obj.cache_key, c_obj.cache_active)
61 print 'key:%s active:%s' % (c_obj.cache_key, c_obj.cache_active)
63 elif self.options.cleanup:
62 elif self.options.cleanup:
64 for c_obj in _caches:
63 for c_obj in _caches:
65 Session().delete(c_obj)
64 Session().delete(c_obj)
66 print 'removing key:%s' % (c_obj.cache_key)
65 print 'removing key:%s' % (c_obj.cache_key)
67 Session().commit()
66 Session().commit()
68 else:
67 else:
69 print 'nothing done exiting...'
68 print 'nothing done exiting...'
70 sys.exit(0)
69 sys.exit(0)
71
70
72 def update_parser(self):
71 def update_parser(self):
73 self.parser.add_option(
72 self.parser.add_option(
74 '--show',
73 '--show',
75 action='store_true',
74 action='store_true',
76 dest='show',
75 dest='show',
77 help=("show existing cache keys with together with status")
76 help=("show existing cache keys with together with status")
78 )
77 )
79
78
80 self.parser.add_option(
79 self.parser.add_option(
81 '--cleanup',
80 '--cleanup',
82 action="store_true",
81 action="store_true",
83 dest="cleanup",
82 dest="cleanup",
84 help="cleanup existing cache keys"
83 help="cleanup existing cache keys"
85 )
84 )
@@ -1,153 +1,152 b''
1 # -*- coding: utf-8 -*-
1 # -*- coding: utf-8 -*-
2 """
2 """
3 rhodecode.lib.paster_commands.cleanup
3 rhodecode.lib.paster_commands.cleanup
4 ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
4 ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
5
5
6 cleanup-repos paster command for RhodeCode
6 cleanup-repos paster command for RhodeCode
7
7
8
8
9 :created_on: Jul 14, 2012
9 :created_on: Jul 14, 2012
10 :author: marcink
10 :author: marcink
11 :copyright: (C) 2010-2012 Marcin Kuzminski <marcin@python-works.com>
11 :copyright: (C) 2010-2012 Marcin Kuzminski <marcin@python-works.com>
12 :license: GPLv3, see COPYING for more details.
12 :license: GPLv3, see COPYING for more details.
13 """
13 """
14 # This program is free software: you can redistribute it and/or modify
14 # This program is free software: you can redistribute it and/or modify
15 # it under the terms of the GNU General Public License as published by
15 # it under the terms of the GNU General Public License as published by
16 # the Free Software Foundation, either version 3 of the License, or
16 # the Free Software Foundation, either version 3 of the License, or
17 # (at your option) any later version.
17 # (at your option) any later version.
18 #
18 #
19 # This program is distributed in the hope that it will be useful,
19 # This program is distributed in the hope that it will be useful,
20 # but WITHOUT ANY WARRANTY; without even the implied warranty of
20 # but WITHOUT ANY WARRANTY; without even the implied warranty of
21 # MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
21 # MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
22 # GNU General Public License for more details.
22 # GNU General Public License for more details.
23 #
23 #
24 # You should have received a copy of the GNU General Public License
24 # You should have received a copy of the GNU General Public License
25 # along with this program. If not, see <http://www.gnu.org/licenses/>.
25 # along with this program. If not, see <http://www.gnu.org/licenses/>.
26 from __future__ import with_statement
26 from __future__ import with_statement
27
27
28 import os
28 import os
29 import sys
29 import sys
30 import re
30 import re
31 import shutil
31 import shutil
32 import logging
32 import logging
33 import datetime
33 import datetime
34
34
35 from os.path import dirname as dn, join as jn
36 #to get the rhodecode import
37 rc_path = dn(dn(dn(os.path.realpath(__file__))))
38 sys.path.append(rc_path)
39 from rhodecode.lib.utils import BasePasterCommand, ask_ok, REMOVED_REPO_PAT
35 from rhodecode.lib.utils import BasePasterCommand, ask_ok, REMOVED_REPO_PAT
40
41 from rhodecode.lib.utils2 import safe_str
36 from rhodecode.lib.utils2 import safe_str
42 from rhodecode.model.db import RhodeCodeUi
37 from rhodecode.model.db import RhodeCodeUi
43
38
39 # fix rhodecode import
40 from os.path import dirname as dn
41 rc_path = dn(dn(dn(os.path.realpath(__file__))))
42 sys.path.append(rc_path)
44
43
45 log = logging.getLogger(__name__)
44 log = logging.getLogger(__name__)
46
45
47
46
48 class Command(BasePasterCommand):
47 class Command(BasePasterCommand):
49
48
50 max_args = 1
49 max_args = 1
51 min_args = 1
50 min_args = 1
52
51
53 usage = "CONFIG_FILE"
52 usage = "CONFIG_FILE"
54 group_name = "RhodeCode"
53 group_name = "RhodeCode"
55 takes_config_file = -1
54 takes_config_file = -1
56 parser = BasePasterCommand.standard_parser(verbose=True)
55 parser = BasePasterCommand.standard_parser(verbose=True)
57 summary = "Cleanup deleted repos"
56 summary = "Cleanup deleted repos"
58
57
59 def _parse_older_than(self, val):
58 def _parse_older_than(self, val):
60 regex = re.compile(r'((?P<days>\d+?)d)?((?P<hours>\d+?)h)?((?P<minutes>\d+?)m)?((?P<seconds>\d+?)s)?')
59 regex = re.compile(r'((?P<days>\d+?)d)?((?P<hours>\d+?)h)?((?P<minutes>\d+?)m)?((?P<seconds>\d+?)s)?')
61 parts = regex.match(val)
60 parts = regex.match(val)
62 if not parts:
61 if not parts:
63 return
62 return
64 parts = parts.groupdict()
63 parts = parts.groupdict()
65 time_params = {}
64 time_params = {}
66 for (name, param) in parts.iteritems():
65 for (name, param) in parts.iteritems():
67 if param:
66 if param:
68 time_params[name] = int(param)
67 time_params[name] = int(param)
69 return datetime.timedelta(**time_params)
68 return datetime.timedelta(**time_params)
70
69
71 def _extract_date(self, name):
70 def _extract_date(self, name):
72 """
71 """
73 Extract the date part from rm__<date> pattern of removed repos,
72 Extract the date part from rm__<date> pattern of removed repos,
74 and convert it to datetime object
73 and convert it to datetime object
75
74
76 :param name:
75 :param name:
77 """
76 """
78 date_part = name[4:19] # 4:19 since we don't parse milisecods
77 date_part = name[4:19] # 4:19 since we don't parse milisecods
79 return datetime.datetime.strptime(date_part, '%Y%m%d_%H%M%S')
78 return datetime.datetime.strptime(date_part, '%Y%m%d_%H%M%S')
80
79
81 def command(self):
80 def command(self):
82 #get SqlAlchemy session
81 #get SqlAlchemy session
83 self._init_session()
82 self._init_session()
84
83
85 repos_location = RhodeCodeUi.get_repos_location()
84 repos_location = RhodeCodeUi.get_repos_location()
86 to_remove = []
85 to_remove = []
87 for dn, dirs, f in os.walk(safe_str(repos_location)):
86 for dn, dirs, f in os.walk(safe_str(repos_location)):
88 alldirs = list(dirs)
87 alldirs = list(dirs)
89 del dirs[:]
88 del dirs[:]
90 if ('.hg' in alldirs or
89 if ('.hg' in alldirs or
91 'objects' in alldirs and ('refs' in alldirs or 'packed-refs' in f)):
90 'objects' in alldirs and ('refs' in alldirs or 'packed-refs' in f)):
92 continue
91 continue
93 for loc in alldirs:
92 for loc in alldirs:
94 if REMOVED_REPO_PAT.match(loc):
93 if REMOVED_REPO_PAT.match(loc):
95 to_remove.append([os.path.join(dn, loc),
94 to_remove.append([os.path.join(dn, loc),
96 self._extract_date(loc)])
95 self._extract_date(loc)])
97 else:
96 else:
98 dirs.append(loc)
97 dirs.append(loc)
99
98
100 #filter older than (if present)!
99 #filter older than (if present)!
101 now = datetime.datetime.now()
100 now = datetime.datetime.now()
102 older_than = self.options.older_than
101 older_than = self.options.older_than
103 if older_than:
102 if older_than:
104 to_remove_filtered = []
103 to_remove_filtered = []
105 older_than_date = self._parse_older_than(older_than)
104 older_than_date = self._parse_older_than(older_than)
106 for name, date_ in to_remove:
105 for name, date_ in to_remove:
107 repo_age = now - date_
106 repo_age = now - date_
108 if repo_age > older_than_date:
107 if repo_age > older_than_date:
109 to_remove_filtered.append([name, date_])
108 to_remove_filtered.append([name, date_])
110
109
111 to_remove = to_remove_filtered
110 to_remove = to_remove_filtered
112 print >> sys.stdout, 'removing %s deleted repos older than %s (%s)' \
111 print >> sys.stdout, 'removing %s deleted repos older than %s (%s)' \
113 % (len(to_remove), older_than, older_than_date)
112 % (len(to_remove), older_than, older_than_date)
114 else:
113 else:
115 print >> sys.stdout, 'removing all [%s] deleted repos' \
114 print >> sys.stdout, 'removing all [%s] deleted repos' \
116 % len(to_remove)
115 % len(to_remove)
117 if self.options.dont_ask or not to_remove:
116 if self.options.dont_ask or not to_remove:
118 # don't ask just remove !
117 # don't ask just remove !
119 remove = True
118 remove = True
120 else:
119 else:
121 remove = ask_ok('the following repositories will be deleted completely:\n%s\n'
120 remove = ask_ok('the following repositories will be deleted completely:\n%s\n'
122 'are you sure you want to remove them [y/n]?'
121 'are you sure you want to remove them [y/n]?'
123 % ', \n'.join(['%s removed on %s'
122 % ', \n'.join(['%s removed on %s'
124 % (safe_str(x[0]), safe_str(x[1])) for x in to_remove]))
123 % (safe_str(x[0]), safe_str(x[1])) for x in to_remove]))
125
124
126 if remove:
125 if remove:
127 for path, date_ in to_remove:
126 for path, date_ in to_remove:
128 print >> sys.stdout, 'removing repository %s' % path
127 print >> sys.stdout, 'removing repository %s' % path
129 shutil.rmtree(path)
128 shutil.rmtree(path)
130 else:
129 else:
131 print 'nothing done exiting...'
130 print 'nothing done exiting...'
132 sys.exit(0)
131 sys.exit(0)
133
132
134 def update_parser(self):
133 def update_parser(self):
135 self.parser.add_option(
134 self.parser.add_option(
136 '--older-than',
135 '--older-than',
137 action='store',
136 action='store',
138 dest='older_than',
137 dest='older_than',
139 help=("only remove repos that have been removed "
138 help=("only remove repos that have been removed "
140 "at least given time ago. "
139 "at least given time ago. "
141 "The default is to remove all removed repositories. "
140 "The default is to remove all removed repositories. "
142 "Possible suffixes: "
141 "Possible suffixes: "
143 "d (days), h (hours), m (minutes), s (seconds). "
142 "d (days), h (hours), m (minutes), s (seconds). "
144 "For example --older-than=30d deletes repositories "
143 "For example --older-than=30d deletes repositories "
145 "removed more than 30 days ago.")
144 "removed more than 30 days ago.")
146 )
145 )
147
146
148 self.parser.add_option(
147 self.parser.add_option(
149 '--dont-ask',
148 '--dont-ask',
150 action="store_true",
149 action="store_true",
151 dest="dont_ask",
150 dest="dont_ask",
152 help="remove repositories without asking for confirmation."
151 help="remove repositories without asking for confirmation."
153 )
152 )
@@ -1,76 +1,76 b''
1 # -*- coding: utf-8 -*-
1 # -*- coding: utf-8 -*-
2 """
2 """
3 rhodecode.lib.paster_commands.ishell
3 rhodecode.lib.paster_commands.ishell
4 ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
4 ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
5
5
6 interactive shell paster command for RhodeCode
6 interactive shell paster command for RhodeCode
7
7
8
8
9 :created_on: Apr 4, 2013
9 :created_on: Apr 4, 2013
10 :author: marcink
10 :author: marcink
11 :copyright: (C) 2010-2013 Marcin Kuzminski <marcin@python-works.com>
11 :copyright: (C) 2010-2013 Marcin Kuzminski <marcin@python-works.com>
12 :license: GPLv3, see COPYING for more details.
12 :license: GPLv3, see COPYING for more details.
13 """
13 """
14 # This program is free software: you can redistribute it and/or modify
14 # This program is free software: you can redistribute it and/or modify
15 # it under the terms of the GNU General Public License as published by
15 # it under the terms of the GNU General Public License as published by
16 # the Free Software Foundation, either version 3 of the License, or
16 # the Free Software Foundation, either version 3 of the License, or
17 # (at your option) any later version.
17 # (at your option) any later version.
18 #
18 #
19 # This program is distributed in the hope that it will be useful,
19 # This program is distributed in the hope that it will be useful,
20 # but WITHOUT ANY WARRANTY; without even the implied warranty of
20 # but WITHOUT ANY WARRANTY; without even the implied warranty of
21 # MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
21 # MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
22 # GNU General Public License for more details.
22 # GNU General Public License for more details.
23 #
23 #
24 # You should have received a copy of the GNU General Public License
24 # You should have received a copy of the GNU General Public License
25 # along with this program. If not, see <http://www.gnu.org/licenses/>.
25 # along with this program. If not, see <http://www.gnu.org/licenses/>.
26 from __future__ import with_statement
26 from __future__ import with_statement
27
27
28 import os
28 import os
29 import sys
29 import sys
30 import logging
30 import logging
31
31
32 from os.path import dirname as dn, join as jn
32 from rhodecode.lib.utils import BasePasterCommand
33 #to get the rhodecode import
33
34 # fix rhodecode import
35 from os.path import dirname as dn
34 rc_path = dn(dn(dn(os.path.realpath(__file__))))
36 rc_path = dn(dn(dn(os.path.realpath(__file__))))
35 sys.path.append(rc_path)
37 sys.path.append(rc_path)
36 from rhodecode.lib.utils import BasePasterCommand
37
38
38
39 log = logging.getLogger(__name__)
39 log = logging.getLogger(__name__)
40
40
41
41
42 class Command(BasePasterCommand):
42 class Command(BasePasterCommand):
43
43
44 max_args = 1
44 max_args = 1
45 min_args = 1
45 min_args = 1
46
46
47 usage = "CONFIG_FILE"
47 usage = "CONFIG_FILE"
48 group_name = "RhodeCode"
48 group_name = "RhodeCode"
49 takes_config_file = -1
49 takes_config_file = -1
50 parser = BasePasterCommand.standard_parser(verbose=True)
50 parser = BasePasterCommand.standard_parser(verbose=True)
51 summary = "Interactive shell"
51 summary = "Interactive shell"
52
52
53 def command(self):
53 def command(self):
54 #get SqlAlchemy session
54 #get SqlAlchemy session
55 self._init_session()
55 self._init_session()
56
56
57 # imports, used in ipython shell
57 # imports, used in ipython shell
58 import os
58 import os
59 import sys
59 import sys
60 import time
60 import time
61 import shutil
61 import shutil
62 import datetime
62 import datetime
63 from rhodecode.model.db import *
63 from rhodecode.model.db import *
64
64
65 try:
65 try:
66 from IPython import embed
66 from IPython import embed
67 from IPython.config.loader import Config
67 from IPython.config.loader import Config
68 cfg = Config()
68 cfg = Config()
69 cfg.InteractiveShellEmbed.confirm_exit = False
69 cfg.InteractiveShellEmbed.confirm_exit = False
70 embed(config=cfg, banner1="RhodeCode IShell.")
70 embed(config=cfg, banner1="RhodeCode IShell.")
71 except ImportError:
71 except ImportError:
72 print 'ipython installation required for ishell'
72 print 'ipython installation required for ishell'
73 sys.exit(-1)
73 sys.exit(-1)
74
74
75 def update_parser(self):
75 def update_parser(self):
76 pass
76 pass
@@ -1,82 +1,81 b''
1 # -*- coding: utf-8 -*-
1 # -*- coding: utf-8 -*-
2 """
2 """
3 rhodecode.lib.paster_commands.make_rcextensions
3 rhodecode.lib.paster_commands.make_rcextensions
4 ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
4 ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
5
5
6 make-rcext paster command for RhodeCode
6 make-rcext paster command for RhodeCode
7
7
8 :created_on: Mar 6, 2012
8 :created_on: Mar 6, 2012
9 :author: marcink
9 :author: marcink
10 :copyright: (C) 2010-2012 Marcin Kuzminski <marcin@python-works.com>
10 :copyright: (C) 2010-2012 Marcin Kuzminski <marcin@python-works.com>
11 :license: GPLv3, see COPYING for more details.
11 :license: GPLv3, see COPYING for more details.
12 """
12 """
13 # This program is free software: you can redistribute it and/or modify
13 # This program is free software: you can redistribute it and/or modify
14 # it under the terms of the GNU General Public License as published by
14 # it under the terms of the GNU General Public License as published by
15 # the Free Software Foundation, either version 3 of the License, or
15 # the Free Software Foundation, either version 3 of the License, or
16 # (at your option) any later version.
16 # (at your option) any later version.
17 #
17 #
18 # This program is distributed in the hope that it will be useful,
18 # This program is distributed in the hope that it will be useful,
19 # but WITHOUT ANY WARRANTY; without even the implied warranty of
19 # but WITHOUT ANY WARRANTY; without even the implied warranty of
20 # MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
20 # MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
21 # GNU General Public License for more details.
21 # GNU General Public License for more details.
22 #
22 #
23 # You should have received a copy of the GNU General Public License
23 # You should have received a copy of the GNU General Public License
24 # along with this program. If not, see <http://www.gnu.org/licenses/>.
24 # along with this program. If not, see <http://www.gnu.org/licenses/>.
25 from __future__ import with_statement
25 from __future__ import with_statement
26
26
27 import os
27 import os
28 import sys
28 import sys
29 import logging
29 import pkg_resources
30 import pkg_resources
30 import traceback
31
31 import logging
32 from rhodecode.lib.utils import BasePasterCommand, ask_ok
32
33
33 from os.path import dirname as dn, join as jn
34 # fix rhodecode import
34 #to get the rhodecode import
35 from os.path import dirname as dn
35 rc_path = dn(dn(dn(os.path.realpath(__file__))))
36 rc_path = dn(dn(dn(os.path.realpath(__file__))))
36 sys.path.append(rc_path)
37 sys.path.append(rc_path)
37
38
38 from rhodecode.lib.utils import BasePasterCommand, ask_ok
39
40 log = logging.getLogger(__name__)
39 log = logging.getLogger(__name__)
41
40
42
41
43 class Command(BasePasterCommand):
42 class Command(BasePasterCommand):
44
43
45 max_args = 1
44 max_args = 1
46 min_args = 1
45 min_args = 1
47
46
48 usage = "CONFIG_FILE"
47 usage = "CONFIG_FILE"
49 group_name = "RhodeCode"
48 group_name = "RhodeCode"
50 takes_config_file = -1
49 takes_config_file = -1
51 parser = BasePasterCommand.standard_parser(verbose=True)
50 parser = BasePasterCommand.standard_parser(verbose=True)
52 summary = "Creates additional extensions for rhodecode"
51 summary = "Creates additional extensions for rhodecode"
53
52
54 def command(self):
53 def command(self):
55 logging.config.fileConfig(self.path_to_ini_file)
54 logging.config.fileConfig(self.path_to_ini_file)
56 from pylons import config
55 from pylons import config
57
56
58 def _make_file(ext_file, tmpl):
57 def _make_file(ext_file, tmpl):
59 bdir = os.path.split(ext_file)[0]
58 bdir = os.path.split(ext_file)[0]
60 if not os.path.isdir(bdir):
59 if not os.path.isdir(bdir):
61 os.makedirs(bdir)
60 os.makedirs(bdir)
62 with open(ext_file, 'wb') as f:
61 with open(ext_file, 'wb') as f:
63 f.write(tmpl)
62 f.write(tmpl)
64 log.info('Writen new extensions file to %s' % ext_file)
63 log.info('Writen new extensions file to %s' % ext_file)
65
64
66 here = config['here']
65 here = config['here']
67 tmpl = pkg_resources.resource_string(
66 tmpl = pkg_resources.resource_string(
68 'rhodecode', jn('config', 'rcextensions', '__init__.py')
67 'rhodecode', os.path.join('config', 'rcextensions', '__init__.py')
69 )
68 )
70 ext_file = jn(here, 'rcextensions', '__init__.py')
69 ext_file = os.path.join(here, 'rcextensions', '__init__.py')
71 if os.path.exists(ext_file):
70 if os.path.exists(ext_file):
72 msg = ('Extension file already exists, do you want '
71 msg = ('Extension file already exists, do you want '
73 'to overwrite it ? [y/n]')
72 'to overwrite it ? [y/n]')
74 if ask_ok(msg):
73 if ask_ok(msg):
75 _make_file(ext_file, tmpl)
74 _make_file(ext_file, tmpl)
76 else:
75 else:
77 log.info('nothing done...')
76 log.info('nothing done...')
78 else:
77 else:
79 _make_file(ext_file, tmpl)
78 _make_file(ext_file, tmpl)
80
79
81 def update_parser(self):
80 def update_parser(self):
82 pass
81 pass
@@ -1,74 +1,70 b''
1 # -*- coding: utf-8 -*-
1 # -*- coding: utf-8 -*-
2 """
2 """
3 rhodecode.lib.paster_commands.make_rcextensions
3 rhodecode.lib.paster_commands.make_rcextensions
4 ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
4 ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
5
5
6 repo-scan paster command for RhodeCode
6 repo-scan paster command for RhodeCode
7
7
8
8
9 :created_on: Feb 9, 2013
9 :created_on: Feb 9, 2013
10 :author: marcink
10 :author: marcink
11 :copyright: (C) 2010-2013 Marcin Kuzminski <marcin@python-works.com>
11 :copyright: (C) 2010-2013 Marcin Kuzminski <marcin@python-works.com>
12 :license: GPLv3, see COPYING for more details.
12 :license: GPLv3, see COPYING for more details.
13 """
13 """
14 # This program is free software: you can redistribute it and/or modify
14 # This program is free software: you can redistribute it and/or modify
15 # it under the terms of the GNU General Public License as published by
15 # it under the terms of the GNU General Public License as published by
16 # the Free Software Foundation, either version 3 of the License, or
16 # the Free Software Foundation, either version 3 of the License, or
17 # (at your option) any later version.
17 # (at your option) any later version.
18 #
18 #
19 # This program is distributed in the hope that it will be useful,
19 # This program is distributed in the hope that it will be useful,
20 # but WITHOUT ANY WARRANTY; without even the implied warranty of
20 # but WITHOUT ANY WARRANTY; without even the implied warranty of
21 # MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
21 # MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
22 # GNU General Public License for more details.
22 # GNU General Public License for more details.
23 #
23 #
24 # You should have received a copy of the GNU General Public License
24 # You should have received a copy of the GNU General Public License
25 # along with this program. If not, see <http://www.gnu.org/licenses/>.
25 # along with this program. If not, see <http://www.gnu.org/licenses/>.
26 from __future__ import with_statement
26 from __future__ import with_statement
27
27
28 import os
28 import os
29 import sys
29 import sys
30 import logging
30 import logging
31
31
32 from os.path import dirname as dn, join as jn
33 from rhodecode.model.scm import ScmModel
32 from rhodecode.model.scm import ScmModel
34 #to get the rhodecode import
33 from rhodecode.lib.utils import BasePasterCommand, repo2db_mapper
34
35 # fix rhodecode import
36 from os.path import dirname as dn
35 rc_path = dn(dn(dn(os.path.realpath(__file__))))
37 rc_path = dn(dn(dn(os.path.realpath(__file__))))
36 sys.path.append(rc_path)
38 sys.path.append(rc_path)
37 from rhodecode.lib.utils import BasePasterCommand, repo2db_mapper
38
39 from rhodecode.model.db import Repository
40 from rhodecode.model.repo import RepoModel
41 from rhodecode.model.meta import Session
42
43
39
44 log = logging.getLogger(__name__)
40 log = logging.getLogger(__name__)
45
41
46
42
47 class Command(BasePasterCommand):
43 class Command(BasePasterCommand):
48
44
49 max_args = 1
45 max_args = 1
50 min_args = 1
46 min_args = 1
51
47
52 usage = "CONFIG_FILE"
48 usage = "CONFIG_FILE"
53 group_name = "RhodeCode"
49 group_name = "RhodeCode"
54 takes_config_file = -1
50 takes_config_file = -1
55 parser = BasePasterCommand.standard_parser(verbose=True)
51 parser = BasePasterCommand.standard_parser(verbose=True)
56 summary = "Rescan default location for new repositories"
52 summary = "Rescan default location for new repositories"
57
53
58 def command(self):
54 def command(self):
59 #get SqlAlchemy session
55 #get SqlAlchemy session
60 self._init_session()
56 self._init_session()
61 rm_obsolete = self.options.delete_obsolete
57 rm_obsolete = self.options.delete_obsolete
62 log.info('Now scanning root location for new repos...')
58 log.info('Now scanning root location for new repos...')
63 added, removed = repo2db_mapper(ScmModel().repo_scan(),
59 added, removed = repo2db_mapper(ScmModel().repo_scan(),
64 remove_obsolete=rm_obsolete)
60 remove_obsolete=rm_obsolete)
65 added = ','.join(added) or '-'
61 added = ','.join(added) or '-'
66 removed = ','.join(removed) or '-'
62 removed = ','.join(removed) or '-'
67 log.info('Scan completed added:%s removed:%s' % (added, removed))
63 log.info('Scan completed added:%s removed:%s' % (added, removed))
68
64
69 def update_parser(self):
65 def update_parser(self):
70 self.parser.add_option('--delete-obsolete',
66 self.parser.add_option('--delete-obsolete',
71 action='store_true',
67 action='store_true',
72 help="Use this flag do delete repositories that are "
68 help="Use this flag do delete repositories that are "
73 "present in RhodeCode database but not on the filesystem",
69 "present in RhodeCode database but not on the filesystem",
74 )
70 )
@@ -1,101 +1,101 b''
1 import os
1 import os
2 import sys
2 import sys
3 from paste.script.appinstall import AbstractInstallCommand
3 from paste.script.appinstall import AbstractInstallCommand
4 from paste.script.command import BadCommand
4 from paste.script.command import BadCommand
5 from paste.deploy import appconfig
5 from paste.deploy import appconfig
6
6
7 from os.path import dirname as dn, join as jn
7 # fix rhodecode import
8 #to get the rhodecode import
8 from os.path import dirname as dn
9 rc_path = dn(dn(dn(os.path.realpath(__file__))))
9 rc_path = dn(dn(dn(os.path.realpath(__file__))))
10 sys.path.append(rc_path)
10 sys.path.append(rc_path)
11
11
12
12
13 class Command(AbstractInstallCommand):
13 class Command(AbstractInstallCommand):
14
14
15 default_verbosity = 1
15 default_verbosity = 1
16 max_args = 1
16 max_args = 1
17 min_args = 1
17 min_args = 1
18 summary = "Setup an application, given a config file"
18 summary = "Setup an application, given a config file"
19 usage = "CONFIG_FILE"
19 usage = "CONFIG_FILE"
20 group_name = "RhodeCode"
20 group_name = "RhodeCode"
21
21
22 description = """\
22 description = """\
23
23
24 Setup RhodeCode according to its configuration file. This is
24 Setup RhodeCode according to its configuration file. This is
25 the second part of a two-phase web application installation
25 the second part of a two-phase web application installation
26 process (the first phase is prepare-app). The setup process
26 process (the first phase is prepare-app). The setup process
27 consist of things like setting up databases, creating super user
27 consist of things like setting up databases, creating super user
28 """
28 """
29
29
30 parser = AbstractInstallCommand.standard_parser(
30 parser = AbstractInstallCommand.standard_parser(
31 simulate=True, quiet=True, interactive=True)
31 simulate=True, quiet=True, interactive=True)
32 parser.add_option('--user',
32 parser.add_option('--user',
33 action='store',
33 action='store',
34 dest='username',
34 dest='username',
35 default=None,
35 default=None,
36 help='Admin Username')
36 help='Admin Username')
37 parser.add_option('--email',
37 parser.add_option('--email',
38 action='store',
38 action='store',
39 dest='email',
39 dest='email',
40 default=None,
40 default=None,
41 help='Admin Email')
41 help='Admin Email')
42 parser.add_option('--password',
42 parser.add_option('--password',
43 action='store',
43 action='store',
44 dest='password',
44 dest='password',
45 default=None,
45 default=None,
46 help='Admin password min 6 chars')
46 help='Admin password min 6 chars')
47 parser.add_option('--repos',
47 parser.add_option('--repos',
48 action='store',
48 action='store',
49 dest='repos_location',
49 dest='repos_location',
50 default=None,
50 default=None,
51 help='Absolute path to repositories location')
51 help='Absolute path to repositories location')
52 parser.add_option('--name',
52 parser.add_option('--name',
53 action='store',
53 action='store',
54 dest='section_name',
54 dest='section_name',
55 default=None,
55 default=None,
56 help='The name of the section to set up (default: app:main)')
56 help='The name of the section to set up (default: app:main)')
57 parser.add_option('--force-yes',
57 parser.add_option('--force-yes',
58 action='store_true',
58 action='store_true',
59 dest='force_ask',
59 dest='force_ask',
60 default=None,
60 default=None,
61 help='Force yes to every question')
61 help='Force yes to every question')
62 parser.add_option('--force-no',
62 parser.add_option('--force-no',
63 action='store_false',
63 action='store_false',
64 dest='force_ask',
64 dest='force_ask',
65 default=None,
65 default=None,
66 help='Force no to every question')
66 help='Force no to every question')
67
67
68 def command(self):
68 def command(self):
69 config_spec = self.args[0]
69 config_spec = self.args[0]
70 section = self.options.section_name
70 section = self.options.section_name
71 if section is None:
71 if section is None:
72 if '#' in config_spec:
72 if '#' in config_spec:
73 config_spec, section = config_spec.split('#', 1)
73 config_spec, section = config_spec.split('#', 1)
74 else:
74 else:
75 section = 'main'
75 section = 'main'
76 if not ':' in section:
76 if not ':' in section:
77 plain_section = section
77 plain_section = section
78 section = 'app:' + section
78 section = 'app:' + section
79 else:
79 else:
80 plain_section = section.split(':', 1)[0]
80 plain_section = section.split(':', 1)[0]
81 if not config_spec.startswith('config:'):
81 if not config_spec.startswith('config:'):
82 config_spec = 'config:' + config_spec
82 config_spec = 'config:' + config_spec
83 if plain_section != 'main':
83 if plain_section != 'main':
84 config_spec += '#' + plain_section
84 config_spec += '#' + plain_section
85 config_file = config_spec[len('config:'):].split('#', 1)[0]
85 config_file = config_spec[len('config:'):].split('#', 1)[0]
86 config_file = os.path.join(os.getcwd(), config_file)
86 config_file = os.path.join(os.getcwd(), config_file)
87 self.logging_file_config(config_file)
87 self.logging_file_config(config_file)
88 conf = appconfig(config_spec, relative_to=os.getcwd())
88 conf = appconfig(config_spec, relative_to=os.getcwd())
89 ep_name = conf.context.entry_point_name
89 ep_name = conf.context.entry_point_name
90 ep_group = conf.context.protocol
90 ep_group = conf.context.protocol
91 dist = conf.context.distribution
91 dist = conf.context.distribution
92 if dist is None:
92 if dist is None:
93 raise BadCommand(
93 raise BadCommand(
94 "The section %r is not the application (probably a filter). "
94 "The section %r is not the application (probably a filter). "
95 "You should add #section_name, where section_name is the "
95 "You should add #section_name, where section_name is the "
96 "section that configures your application" % plain_section)
96 "section that configures your application" % plain_section)
97 installer = self.get_installer(dist, ep_group, ep_name)
97 installer = self.get_installer(dist, ep_group, ep_name)
98 installer.setup_config(
98 installer.setup_config(
99 self, config_file, section, self.sysconfig_install_vars(installer))
99 self, config_file, section, self.sysconfig_install_vars(installer))
100 self.call_sysconfig_functions(
100 self.call_sysconfig_functions(
101 'post_setup_hook', installer, config_file)
101 'post_setup_hook', installer, config_file)
@@ -1,87 +1,87 b''
1 # -*- coding: utf-8 -*-
1 # -*- coding: utf-8 -*-
2 """
2 """
3 rhodecode.lib.paster_commands.make_rcextensions
3 rhodecode.lib.paster_commands.make_rcextensions
4 ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
4 ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
5
5
6 uodate-repoinfo paster command for RhodeCode
6 uodate-repoinfo paster command for RhodeCode
7
7
8 :created_on: Jul 14, 2012
8 :created_on: Jul 14, 2012
9 :author: marcink
9 :author: marcink
10 :copyright: (C) 2010-2012 Marcin Kuzminski <marcin@python-works.com>
10 :copyright: (C) 2010-2012 Marcin Kuzminski <marcin@python-works.com>
11 :license: GPLv3, see COPYING for more details.
11 :license: GPLv3, see COPYING for more details.
12 """
12 """
13 # This program is free software: you can redistribute it and/or modify
13 # This program is free software: you can redistribute it and/or modify
14 # it under the terms of the GNU General Public License as published by
14 # it under the terms of the GNU General Public License as published by
15 # the Free Software Foundation, either version 3 of the License, or
15 # the Free Software Foundation, either version 3 of the License, or
16 # (at your option) any later version.
16 # (at your option) any later version.
17 #
17 #
18 # This program is distributed in the hope that it will be useful,
18 # This program is distributed in the hope that it will be useful,
19 # but WITHOUT ANY WARRANTY; without even the implied warranty of
19 # but WITHOUT ANY WARRANTY; without even the implied warranty of
20 # MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
20 # MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
21 # GNU General Public License for more details.
21 # GNU General Public License for more details.
22 #
22 #
23 # You should have received a copy of the GNU General Public License
23 # You should have received a copy of the GNU General Public License
24 # along with this program. If not, see <http://www.gnu.org/licenses/>.
24 # along with this program. If not, see <http://www.gnu.org/licenses/>.
25 from __future__ import with_statement
25 from __future__ import with_statement
26
26
27 import os
27 import os
28 import sys
28 import sys
29 import logging
29 import logging
30 import string
30 import string
31
31
32 from os.path import dirname as dn, join as jn
33 #to get the rhodecode import
34 rc_path = dn(dn(dn(os.path.realpath(__file__))))
35 sys.path.append(rc_path)
36 from rhodecode.lib.utils import BasePasterCommand
32 from rhodecode.lib.utils import BasePasterCommand
37
38 from rhodecode.model.db import Repository
33 from rhodecode.model.db import Repository
39 from rhodecode.model.repo import RepoModel
34 from rhodecode.model.repo import RepoModel
40 from rhodecode.model.meta import Session
35 from rhodecode.model.meta import Session
41
36
37 # fix rhodecode import
38 from os.path import dirname as dn
39 rc_path = dn(dn(dn(os.path.realpath(__file__))))
40 sys.path.append(rc_path)
41
42 log = logging.getLogger(__name__)
42 log = logging.getLogger(__name__)
43
43
44
44
45 class Command(BasePasterCommand):
45 class Command(BasePasterCommand):
46
46
47 max_args = 1
47 max_args = 1
48 min_args = 1
48 min_args = 1
49
49
50 usage = "CONFIG_FILE"
50 usage = "CONFIG_FILE"
51 group_name = "RhodeCode"
51 group_name = "RhodeCode"
52 takes_config_file = -1
52 takes_config_file = -1
53 parser = BasePasterCommand.standard_parser(verbose=True)
53 parser = BasePasterCommand.standard_parser(verbose=True)
54 summary = "Updates repositories caches for last changeset"
54 summary = "Updates repositories caches for last changeset"
55
55
56 def command(self):
56 def command(self):
57 #get SqlAlchemy session
57 #get SqlAlchemy session
58 self._init_session()
58 self._init_session()
59
59
60 repo_update_list = map(string.strip,
60 repo_update_list = map(string.strip,
61 self.options.repo_update_list.split(',')) \
61 self.options.repo_update_list.split(',')) \
62 if self.options.repo_update_list else None
62 if self.options.repo_update_list else None
63
63
64 if repo_update_list:
64 if repo_update_list:
65 repo_list = Repository.query()\
65 repo_list = Repository.query()\
66 .filter(Repository.repo_name.in_(repo_update_list))
66 .filter(Repository.repo_name.in_(repo_update_list))
67 else:
67 else:
68 repo_list = Repository.getAll()
68 repo_list = Repository.getAll()
69 RepoModel.update_repoinfo(repositories=repo_list)
69 RepoModel.update_repoinfo(repositories=repo_list)
70 Session().commit()
70 Session().commit()
71
71
72 if self.options.invalidate_cache:
72 if self.options.invalidate_cache:
73 for r in repo_list:
73 for r in repo_list:
74 r.set_invalidate()
74 r.set_invalidate()
75 log.info('Updated cache for %s repositories' % (len(repo_list)))
75 log.info('Updated cache for %s repositories' % (len(repo_list)))
76
76
77 def update_parser(self):
77 def update_parser(self):
78 self.parser.add_option('--update-only',
78 self.parser.add_option('--update-only',
79 action='store',
79 action='store',
80 dest='repo_update_list',
80 dest='repo_update_list',
81 help="Specifies a comma separated list of repositores "
81 help="Specifies a comma separated list of repositores "
82 "to update last commit info for. OPTIONAL")
82 "to update last commit info for. OPTIONAL")
83 self.parser.add_option('--invalidate-cache',
83 self.parser.add_option('--invalidate-cache',
84 action='store_true',
84 action='store_true',
85 dest='invalidate_cache',
85 dest='invalidate_cache',
86 help="Trigger cache invalidation event for repos. "
86 help="Trigger cache invalidation event for repos. "
87 "OPTIONAL")
87 "OPTIONAL")
@@ -1,174 +1,174 b''
1 import os
1 import os
2 import sys
2 import sys
3 import platform
3 import platform
4
4
5 if sys.version_info < (2, 5):
5 if sys.version_info < (2, 5):
6 raise Exception('RhodeCode requires python 2.5 or later')
6 raise Exception('RhodeCode requires python 2.5 or later')
7
7
8
8
9 here = os.path.abspath(os.path.dirname(__file__))
9 here = os.path.abspath(os.path.dirname(__file__))
10
10
11
11
12 def _get_meta_var(name, data, callback_handler=None):
12 def _get_meta_var(name, data, callback_handler=None):
13 import re
13 import re
14 matches = re.compile(r'(?:%s)\s*=\s*(.*)' % name).search(data)
14 matches = re.compile(r'(?:%s)\s*=\s*(.*)' % name).search(data)
15 if matches:
15 if matches:
16 if not callable(callback_handler):
16 if not callable(callback_handler):
17 callback_handler = lambda v: v
17 callback_handler = lambda v: v
18
18
19 return callback_handler(eval(matches.groups()[0]))
19 return callback_handler(eval(matches.groups()[0]))
20
20
21 _meta = open(os.path.join(here, 'rhodecode', '__init__.py'), 'rb')
21 _meta = open(os.path.join(here, 'rhodecode', '__init__.py'), 'rb')
22 _metadata = _meta.read()
22 _metadata = _meta.read()
23 _meta.close()
23 _meta.close()
24
24
25 callback = lambda V: ('.'.join(map(str, V[:3])) + '.'.join(V[3:]))
25 callback = lambda V: ('.'.join(map(str, V[:3])) + '.'.join(V[3:]))
26 __version__ = _get_meta_var('VERSION', _metadata, callback)
26 __version__ = _get_meta_var('VERSION', _metadata, callback)
27 __license__ = _get_meta_var('__license__', _metadata)
27 __license__ = _get_meta_var('__license__', _metadata)
28 __author__ = _get_meta_var('__author__', _metadata)
28 __author__ = _get_meta_var('__author__', _metadata)
29 __url__ = _get_meta_var('__url__', _metadata)
29 __url__ = _get_meta_var('__url__', _metadata)
30 # defines current platform
30 # defines current platform
31 __platform__ = platform.system()
31 __platform__ = platform.system()
32
32
33 is_windows = __platform__ in ['Windows']
33 is_windows = __platform__ in ['Windows']
34
34
35 requirements = [
35 requirements = [
36 "waitress==0.8.4",
36 "waitress==0.8.4",
37 "webob==1.0.8",
37 "webob==1.0.8",
38 "webtest==1.4.3",
38 "webtest==1.4.3",
39 "Pylons==1.0.0",
39 "Pylons==1.0.0",
40 "Beaker==1.6.4",
40 "Beaker==1.6.4",
41 "WebHelpers==1.3",
41 "WebHelpers==1.3",
42 "formencode==1.2.4",
42 "formencode==1.2.4",
43 "SQLAlchemy==0.7.10",
43 "SQLAlchemy==0.7.10",
44 "Mako==0.7.3",
44 "Mako==0.7.3",
45 "pygments>=1.5",
45 "pygments>=1.5",
46 "whoosh>=2.4.0,<2.5",
46 "whoosh>=2.4.0,<2.5",
47 "celery>=2.2.5,<2.3",
47 "celery>=2.2.5,<2.3",
48 "babel",
48 "babel",
49 "python-dateutil>=1.5.0,<2.0.0",
49 "python-dateutil>=1.5.0,<2.0.0",
50 "dulwich>=0.8.7,<0.9.0",
50 "dulwich>=0.8.7,<0.9.0",
51 "markdown==2.2.1",
51 "markdown==2.2.1",
52 "docutils==0.8.1",
52 "docutils==0.8.1",
53 "simplejson==2.5.2",
53 "simplejson==2.5.2",
54 "mock",
54 "mock",
55 ]
55 ]
56
56
57 if sys.version_info < (2, 6):
57 if sys.version_info < (2, 6):
58 requirements.append("pysqlite")
58 requirements.append("pysqlite")
59
59
60 if sys.version_info < (2, 7):
60 if sys.version_info < (2, 7):
61 requirements.append("unittest2")
61 requirements.append("unittest2")
62 requirements.append("argparse")
62 requirements.append("argparse")
63
63
64 if is_windows:
64 if is_windows:
65 requirements.append("mercurial==2.6.1")
65 requirements.append("mercurial==2.6.1")
66 else:
66 else:
67 requirements.append("py-bcrypt")
67 requirements.append("py-bcrypt")
68 requirements.append("mercurial==2.6.1")
68 requirements.append("mercurial==2.6.1")
69
69
70
70
71 dependency_links = [
71 dependency_links = [
72 ]
72 ]
73
73
74 classifiers = [
74 classifiers = [
75 'Development Status :: 4 - Beta',
75 'Development Status :: 4 - Beta',
76 'Environment :: Web Environment',
76 'Environment :: Web Environment',
77 'Framework :: Pylons',
77 'Framework :: Pylons',
78 'Intended Audience :: Developers',
78 'Intended Audience :: Developers',
79 'License :: OSI Approved :: GNU General Public License (GPL)',
79 'License :: OSI Approved :: GNU General Public License (GPL)',
80 'Operating System :: OS Independent',
80 'Operating System :: OS Independent',
81 'Programming Language :: Python',
81 'Programming Language :: Python',
82 'Programming Language :: Python :: 2.5',
82 'Programming Language :: Python :: 2.5',
83 'Programming Language :: Python :: 2.6',
83 'Programming Language :: Python :: 2.6',
84 'Programming Language :: Python :: 2.7',
84 'Programming Language :: Python :: 2.7',
85 ]
85 ]
86
86
87
87
88 # additional files from project that goes somewhere in the filesystem
88 # additional files from project that goes somewhere in the filesystem
89 # relative to sys.prefix
89 # relative to sys.prefix
90 data_files = []
90 data_files = []
91
91
92 # additional files that goes into package itself
92 # additional files that goes into package itself
93 package_data = {'rhodecode': ['i18n/*/LC_MESSAGES/*.mo', ], }
93 package_data = {'rhodecode': ['i18n/*/LC_MESSAGES/*.mo', ], }
94
94
95 description = ('RhodeCode is a fast and powerful management tool '
95 description = ('RhodeCode is a fast and powerful management tool '
96 'for Mercurial and GIT with a built in push/pull server, '
96 'for Mercurial and GIT with a built in push/pull server, '
97 'full text search and code-review.')
97 'full text search and code-review.')
98 keywords = ' '.join(['rhodecode', 'rhodiumcode', 'mercurial', 'git',
98 keywords = ' '.join(['rhodecode', 'rhodiumcode', 'mercurial', 'git',
99 'code review', 'repo groups', 'ldap'
99 'code review', 'repo groups', 'ldap'
100 'repository management', 'hgweb replacement'
100 'repository management', 'hgweb replacement'
101 'hgwebdir', 'gitweb replacement', 'serving hgweb', ])
101 'hgwebdir', 'gitweb replacement', 'serving hgweb', ])
102 # long description
102 # long description
103 try:
103 try:
104 readme_file = 'README.rst'
104 readme_file = 'README.rst'
105 changelog_file = 'docs/changelog.rst'
105 changelog_file = 'docs/changelog.rst'
106 long_description = open(readme_file).read() + '\n\n' + \
106 long_description = open(readme_file).read() + '\n\n' + \
107 open(changelog_file).read()
107 open(changelog_file).read()
108
108
109 except IOError, err:
109 except IOError, err:
110 sys.stderr.write("[WARNING] Cannot find file specified as "
110 sys.stderr.write("[WARNING] Cannot find file specified as "
111 "long_description (%s)\n or changelog (%s) skipping that file" \
111 "long_description (%s)\n or changelog (%s) skipping that file" \
112 % (readme_file, changelog_file))
112 % (readme_file, changelog_file))
113 long_description = description
113 long_description = description
114
114
115
115
116 try:
116 try:
117 from setuptools import setup, find_packages
117 from setuptools import setup, find_packages
118 except ImportError:
118 except ImportError:
119 from ez_setup import use_setuptools
119 from ez_setup import use_setuptools
120 use_setuptools()
120 use_setuptools()
121 from setuptools import setup, find_packages
121 from setuptools import setup, find_packages
122 # packages
122 # packages
123 packages = find_packages(exclude=['ez_setup'])
123 packages = find_packages(exclude=['ez_setup'])
124
124
125 setup(
125 setup(
126 name='RhodeCode',
126 name='RhodeCode',
127 version=__version__,
127 version=__version__,
128 description=description,
128 description=description,
129 long_description=long_description,
129 long_description=long_description,
130 keywords=keywords,
130 keywords=keywords,
131 license=__license__,
131 license=__license__,
132 author=__author__,
132 author=__author__,
133 author_email='marcin@python-works.com',
133 author_email='marcin@python-works.com',
134 dependency_links=dependency_links,
134 dependency_links=dependency_links,
135 url=__url__,
135 url=__url__,
136 install_requires=requirements,
136 install_requires=requirements,
137 classifiers=classifiers,
137 classifiers=classifiers,
138 setup_requires=["PasteScript>=1.6.3"],
138 setup_requires=["PasteScript>=1.6.3"],
139 data_files=data_files,
139 data_files=data_files,
140 packages=packages,
140 packages=packages,
141 include_package_data=True,
141 include_package_data=True,
142 test_suite='nose.collector',
142 test_suite='nose.collector',
143 package_data=package_data,
143 package_data=package_data,
144 message_extractors={'rhodecode': [
144 message_extractors={'rhodecode': [
145 ('**.py', 'python', None),
145 ('**.py', 'python', None),
146 ('templates/**.mako', 'mako', {'input_encoding': 'utf-8'}),
146 ('templates/**.mako', 'mako', {'input_encoding': 'utf-8'}),
147 ('templates/**.html', 'mako', {'input_encoding': 'utf-8'}),
147 ('templates/**.html', 'mako', {'input_encoding': 'utf-8'}),
148 ('public/**', 'ignore', None)]},
148 ('public/**', 'ignore', None)]},
149 zip_safe=False,
149 zip_safe=False,
150 paster_plugins=['PasteScript', 'Pylons'],
150 paster_plugins=['PasteScript', 'Pylons'],
151 entry_points="""
151 entry_points="""
152 [console_scripts]
152 [console_scripts]
153 rhodecode-api = rhodecode.bin.rhodecode_api:main
153 rhodecode-api = rhodecode.bin.rhodecode_api:main
154 rhodecode-gist = rhodecode.bin.rhodecode_gist:main
154 rhodecode-gist = rhodecode.bin.rhodecode_gist:main
155
155
156 [paste.app_factory]
156 [paste.app_factory]
157 main = rhodecode.config.middleware:make_app
157 main = rhodecode.config.middleware:make_app
158
158
159 [paste.app_install]
159 [paste.app_install]
160 main = pylons.util:PylonsInstaller
160 main = pylons.util:PylonsInstaller
161
161
162 [paste.global_paster_command]
162 [paste.global_paster_command]
163 setup-rhodecode=rhodecode.lib.paster_commands.setup_rhodecode:Command
163 setup-rhodecode=rhodecode.lib.paster_commands.setup_rhodecode:Command
164 cleanup-repos=rhodecode.lib.paster_commands.cleanup:Command
164 cleanup-repos=rhodecode.lib.paster_commands.cleanup:Command
165 update-repoinfo=rhodecode.lib.paster_commands.update_repoinfo:Command
165 update-repoinfo=rhodecode.lib.paster_commands.update_repoinfo:Command
166 make-rcext=rhodecode.lib.paster_commands.make_rcextensions:Command
166 make-rcext=rhodecode.lib.paster_commands.make_rcextensions:Command
167 repo-scan=rhodecode.lib.paster_commands.repo_scan:Command
167 repo-scan=rhodecode.lib.paster_commands.repo_scan:Command
168 cache-keys=rhodecode.lib.paster_commands.cache_keys:Command
168 cache-keys=rhodecode.lib.paster_commands.cache_keys:Command
169 ishell=rhodecode.lib.paster_commands.ishell:Command
169 ishell=rhodecode.lib.paster_commands.ishell:Command
170 make-index=rhodecode.lib.indexers:MakeIndex
170 make-index=rhodecode.lib.paster_commands.make_index:Command
171 upgrade-db=rhodecode.lib.dbmigrate:UpgradeDb
171 upgrade-db=rhodecode.lib.dbmigrate:UpgradeDb
172 celeryd=rhodecode.lib.celerypylons.commands:CeleryDaemonCommand
172 celeryd=rhodecode.lib.celerypylons.commands:CeleryDaemonCommand
173 """,
173 """,
174 )
174 )
General Comments 0
You need to be logged in to leave comments. Login now