##// END OF EJS Templates
search: add support for elastic search 6...
dan -
r3319:b8fd1d7a default
parent child Browse files
Show More
@@ -0,0 +1,257 b''
1 # -*- coding: utf-8 -*-
2
3 # Copyright (C) 2012-2018 RhodeCode GmbH
4 #
5 # This program is free software: you can redistribute it and/or modify
6 # it under the terms of the GNU Affero General Public License, version 3
7 # (only), as published by the Free Software Foundation.
8 #
9 # This program is distributed in the hope that it will be useful,
10 # but WITHOUT ANY WARRANTY; without even the implied warranty of
11 # MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
12 # GNU General Public License for more details.
13 #
14 # You should have received a copy of the GNU Affero General Public License
15 # along with this program. If not, see <http://www.gnu.org/licenses/>.
16 #
17 # This program is dual-licensed. If you wish to learn more about the
18 # RhodeCode Enterprise Edition, including its added features, Support services,
19 # and proprietary license terms, please see https://rhodecode.com/licenses/
20 import re
21
22 import pygments.filter
23 import pygments.filters
24 from pygments.token import Comment
25
26 HL_BEG_MARKER = '__RCSearchHLMarkBEG__'
27 HL_END_MARKER = '__RCSearchHLMarkEND__'
28 HL_MARKER_RE = '{}(.*?){}'.format(HL_BEG_MARKER, HL_END_MARKER)
29
30
31 class ElasticSearchHLFilter(pygments.filters.Filter):
32 _names = [HL_BEG_MARKER, HL_END_MARKER]
33
34 def __init__(self, **options):
35 pygments.filters.Filter.__init__(self, **options)
36
37 def filter(self, lexer, stream):
38 def tokenize(_value):
39 for token in re.split('({}|{})'.format(
40 self._names[0], self._names[1]), _value):
41 if token:
42 yield token
43
44 hl = False
45 for ttype, value in stream:
46
47 if self._names[0] in value or self._names[1] in value:
48 for item in tokenize(value):
49 if item == self._names[0]:
50 # skip marker, but start HL
51 hl = True
52 continue
53 elif item == self._names[1]:
54 hl = False
55 continue
56
57 if hl:
58 yield Comment.ElasticMatch, item
59 else:
60 yield ttype, item
61 else:
62 if hl:
63 yield Comment.ElasticMatch, value
64 else:
65 yield ttype, value
66
67
68 def extract_phrases(text_query):
69 """
70 Extracts phrases from search term string making sure phrases
71 contained in double quotes are kept together - and discarding empty values
72 or fully whitespace values eg.
73
74 'some text "a phrase" more' => ['some', 'text', 'a phrase', 'more']
75
76 """
77
78 in_phrase = False
79 buf = ''
80 phrases = []
81 for char in text_query:
82 if in_phrase:
83 if char == '"': # end phrase
84 phrases.append(buf)
85 buf = ''
86 in_phrase = False
87 continue
88 else:
89 buf += char
90 continue
91 else:
92 if char == '"': # start phrase
93 in_phrase = True
94 phrases.append(buf)
95 buf = ''
96 continue
97 elif char == ' ':
98 phrases.append(buf)
99 buf = ''
100 continue
101 else:
102 buf += char
103
104 phrases.append(buf)
105 phrases = [phrase.strip() for phrase in phrases if phrase.strip()]
106 return phrases
107
108
109 def get_matching_phrase_offsets(text, phrases):
110 """
111 Returns a list of string offsets in `text` that the list of `terms` match
112
113 >>> get_matching_phrase_offsets('some text here', ['some', 'here'])
114 [(0, 4), (10, 14)]
115
116 """
117 phrases = phrases or []
118 offsets = []
119
120 for phrase in phrases:
121 for match in re.finditer(phrase, text):
122 offsets.append((match.start(), match.end()))
123
124 return offsets
125
126
127 def get_matching_markers_offsets(text, markers=None):
128 """
129 Returns a list of string offsets in `text` that the are between matching markers
130
131 >>> get_matching_markers_offsets('$1some$2 text $1here$2 marked', ['\$1(.*?)\$2'])
132 [(0, 5), (16, 22)]
133
134 """
135 markers = markers or [HL_MARKER_RE]
136 offsets = []
137
138 if markers:
139 for mark in markers:
140 for match in re.finditer(mark, text):
141 offsets.append((match.start(), match.end()))
142
143 return offsets
144
145
146 def normalize_text_for_matching(x):
147 """
148 Replaces all non alfanum characters to spaces and lower cases the string,
149 useful for comparing two text strings without punctuation
150 """
151 return re.sub(r'[^\w]', ' ', x.lower())
152
153
154 def get_matching_line_offsets(lines, terms=None, markers=None):
155 """ Return a set of `lines` indices (starting from 1) matching a
156 text search query, along with `context` lines above/below matching lines
157
158 :param lines: list of strings representing lines
159 :param terms: search term string to match in lines eg. 'some text'
160 :param markers: instead of terms, use highlight markers instead that
161 mark beginning and end for matched item. eg. ['START(.*?)END']
162
163 eg.
164
165 text = '''
166 words words words
167 words words words
168 some text some
169 words words words
170 words words words
171 text here what
172 '''
173 get_matching_line_offsets(text, 'text', context=1)
174 6, {3: [(5, 9)], 6: [(0, 4)]]
175
176 """
177 matching_lines = {}
178 line_index = 0
179
180 if terms:
181 phrases = [normalize_text_for_matching(phrase)
182 for phrase in extract_phrases(terms)]
183
184 for line_index, line in enumerate(lines.splitlines(), start=1):
185 normalized_line = normalize_text_for_matching(line)
186 match_offsets = get_matching_phrase_offsets(normalized_line, phrases)
187 if match_offsets:
188 matching_lines[line_index] = match_offsets
189
190 else:
191 markers = markers or [HL_MARKER_RE]
192 for line_index, line in enumerate(lines.splitlines(), start=1):
193 match_offsets = get_matching_markers_offsets(line, markers=markers)
194 if match_offsets:
195 matching_lines[line_index] = match_offsets
196
197 return line_index, matching_lines
198
199
200 def lucene_query_parser():
201 # from pyparsing lucene_grammar
202 from pyparsing import (
203 Literal, CaselessKeyword, Forward, Regex, QuotedString, Suppress,
204 Optional, Group, infixNotation, opAssoc, ParserElement, pyparsing_common)
205
206 ParserElement.enablePackrat()
207
208 COLON, LBRACK, RBRACK, LBRACE, RBRACE, TILDE, CARAT = map(Literal, ":[]{}~^")
209 LPAR, RPAR = map(Suppress, "()")
210 and_, or_, not_, to_ = map(CaselessKeyword, "AND OR NOT TO".split())
211 keyword = and_ | or_ | not_ | to_
212
213 expression = Forward()
214
215 valid_word = Regex(r'([a-zA-Z0-9*_+.-]|\\[!(){}\[\]^"~*?\\:])+').setName("word")
216 valid_word.setParseAction(
217 lambda t: t[0]
218 .replace('\\\\', chr(127))
219 .replace('\\', '')
220 .replace(chr(127), '\\')
221 )
222
223 string = QuotedString('"')
224
225 required_modifier = Literal("+")("required")
226 prohibit_modifier = Literal("-")("prohibit")
227 integer = Regex(r"\d+").setParseAction(lambda t: int(t[0]))
228 proximity_modifier = Group(TILDE + integer("proximity"))
229 number = pyparsing_common.fnumber()
230 fuzzy_modifier = TILDE + Optional(number, default=0.5)("fuzzy")
231
232 term = Forward()
233 field_name = valid_word().setName("fieldname")
234 incl_range_search = Group(LBRACK + term("lower") + to_ + term("upper") + RBRACK)
235 excl_range_search = Group(LBRACE + term("lower") + to_ + term("upper") + RBRACE)
236 range_search = incl_range_search("incl_range") | excl_range_search("excl_range")
237 boost = (CARAT + number("boost"))
238
239 string_expr = Group(string + proximity_modifier) | string
240 word_expr = Group(valid_word + fuzzy_modifier) | valid_word
241 term << (Optional(field_name("field") + COLON) +
242 (word_expr | string_expr | range_search | Group(
243 LPAR + expression + RPAR)) +
244 Optional(boost))
245 term.setParseAction(lambda t: [t] if 'field' in t or 'boost' in t else None)
246
247 expression << infixNotation(
248 term,
249 [
250 (required_modifier | prohibit_modifier, 1, opAssoc.RIGHT),
251 ((not_ | '!').setParseAction(lambda: "NOT"), 1, opAssoc.RIGHT),
252 ((and_ | '&&').setParseAction(lambda: "AND"), 2, opAssoc.LEFT),
253 (Optional(or_ | '||').setParseAction(lambda: "OR"), 2, opAssoc.LEFT),
254 ]
255 )
256
257 return expression
@@ -0,0 +1,100 b''
1 # -*- coding: utf-8 -*-
2
3 # Copyright (C) 2010-2018 RhodeCode GmbH
4 #
5 # This program is free software: you can redistribute it and/or modify
6 # it under the terms of the GNU Affero General Public License, version 3
7 # (only), as published by the Free Software Foundation.
8 #
9 # This program is distributed in the hope that it will be useful,
10 # but WITHOUT ANY WARRANTY; without even the implied warranty of
11 # MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
12 # GNU General Public License for more details.
13 #
14 # You should have received a copy of the GNU Affero General Public License
15 # along with this program. If not, see <http://www.gnu.org/licenses/>.
16 #
17 # This program is dual-licensed. If you wish to learn more about the
18 # RhodeCode Enterprise Edition, including its added features, Support services,
19 # and proprietary license terms, please see https://rhodecode.com/licenses/
20
21 import copy
22 import mock
23 import pytest
24
25 from rhodecode.lib.index import search_utils
26
27
28 @pytest.mark.parametrize('test_text, expected_output', [
29 ('some text', ['some', 'text']),
30 ('some text', ['some', 'text']),
31 ('some text "with a phrase"', ['some', 'text', 'with a phrase']),
32 ('"a phrase" "another phrase"', ['a phrase', 'another phrase']),
33 ('"justphrase"', ['justphrase']),
34 ('""', []),
35 ('', []),
36 (' ', []),
37 ('" "', []),
38 ])
39 def test_extract_phrases(test_text, expected_output):
40 assert search_utils.extract_phrases(test_text) == expected_output
41
42
43 @pytest.mark.parametrize('test_text, text_phrases, expected_output', [
44 ('some text here', ['some', 'here'], [(0, 4), (10, 14)]),
45 ('here here there', ['here'], [(0, 4), (5, 9), (11, 15)]),
46 ('irrelevant', ['not found'], []),
47 ('irrelevant', ['not found'], []),
48 ])
49 def test_get_matching_phrase_offsets(test_text, text_phrases, expected_output):
50 assert search_utils.get_matching_phrase_offsets(
51 test_text, text_phrases) == expected_output
52
53
54 @pytest.mark.parametrize('test_text, text_phrases, expected_output', [
55 ('__RCSearchHLMarkBEG__some__RCSearchHLMarkEND__ text __RCSearchHLMarkBEG__here__RCSearchHLMarkEND__', [], [(0, 46), (52, 98)]),
56 ('__RCSearchHLMarkBEG__here__RCSearchHLMarkEND__ __RCSearchHLMarkBEG__here__RCSearchHLMarkEND__ there', [], [(0, 46), (47, 93)]),
57 ('some text __RCSearchHLMarkBEG__here__RCSearchHLMarkEND__', [], [(10, 56)]),
58 ('__RCSearchHLMarkBEG__here__RCSearchHLMarkEND__ __RCSearchHLMarkBEG__here__RCSearchHLMarkEND__ __RCSearchHLMarkBEG__there__RCSearchHLMarkEND__', [], [(0, 46), (47, 93), (94, 141)]),
59 ('irrelevant', ['not found'], []),
60 ('irrelevant', ['not found'], []),
61 ])
62 def test_get_matching_marker_offsets(test_text, text_phrases, expected_output):
63
64 assert search_utils.get_matching_markers_offsets(test_text) == expected_output
65
66
67 def test_normalize_text_for_matching():
68 assert search_utils.normalize_text_for_matching(
69 'OJjfe)*#$*@)$JF*)3r2f80h') == 'ojjfe jf 3r2f80h'
70
71
72 def test_get_matching_line_offsets():
73 words = '\n'.join([
74 'words words words',
75 'words words words',
76 'some text some',
77 'words words words',
78 'words words words',
79 'text here what'
80 ])
81 total_lines, matched_offsets = \
82 search_utils.get_matching_line_offsets(words, terms='text')
83 assert total_lines == 6
84 assert matched_offsets == {3: [(5, 9)], 6: [(0, 4)]}
85
86
87 def test_get_matching_line_offsets_using_markers():
88 words = '\n'.join([
89 'words words words',
90 'words words words',
91 'some __1__text__2__ some',
92 'words words words',
93 'words words words',
94 '__1__text__2__ here what'
95 ])
96 total_lines, matched_offsets = \
97 search_utils.get_matching_line_offsets(words, terms=None,
98 markers=['__1__(.*?)__2__'])
99 assert total_lines == 6
100 assert matched_offsets == {3: [(5, 19)], 6: [(0, 14)]}
@@ -1,2289 +1,2336 b''
1 1 # Generated by pip2nix 0.8.0.dev1
2 2 # See https://github.com/johbo/pip2nix
3 3
4 4 { pkgs, fetchurl, fetchgit, fetchhg }:
5 5
6 6 self: super: {
7 7 "alembic" = super.buildPythonPackage {
8 8 name = "alembic-1.0.5";
9 9 doCheck = false;
10 10 propagatedBuildInputs = [
11 11 self."sqlalchemy"
12 12 self."mako"
13 13 self."python-editor"
14 14 self."python-dateutil"
15 15 ];
16 16 src = fetchurl {
17 17 url = "https://files.pythonhosted.org/packages/1c/65/b8e4f5b2f345bb13b5e0a3fddd892b0b3f0e8ad4880e954fdc6a50d00d84/alembic-1.0.5.tar.gz";
18 18 sha256 = "0rpjqp2iq6p49x1nli18ivak1izz547nnjxi110mzrgc1v7dxzz9";
19 19 };
20 20 meta = {
21 21 license = [ pkgs.lib.licenses.mit ];
22 22 };
23 23 };
24 24 "amqp" = super.buildPythonPackage {
25 25 name = "amqp-2.3.1";
26 26 doCheck = false;
27 27 propagatedBuildInputs = [
28 28 self."vine"
29 29 ];
30 30 src = fetchurl {
31 31 url = "https://files.pythonhosted.org/packages/1b/32/242ff76cd802766f11c89c72f3389b5c8de4bdfbab406137b90c5fae8b05/amqp-2.3.1.tar.gz";
32 32 sha256 = "0wlfnvhmfrn7c8qif2jyvsm63ibdxp02ss564qwrvqfhz0di72s0";
33 33 };
34 34 meta = {
35 35 license = [ pkgs.lib.licenses.bsdOriginal ];
36 36 };
37 37 };
38 38 "appenlight-client" = super.buildPythonPackage {
39 39 name = "appenlight-client-0.6.26";
40 40 doCheck = false;
41 41 propagatedBuildInputs = [
42 42 self."webob"
43 43 self."requests"
44 44 self."six"
45 45 ];
46 46 src = fetchurl {
47 47 url = "https://files.pythonhosted.org/packages/2e/56/418fc10379b96e795ee39a15e69a730c222818af04c3821fa354eaa859ec/appenlight_client-0.6.26.tar.gz";
48 48 sha256 = "0s9xw3sb8s3pk73k78nnq4jil3q4mk6bczfa1fmgfx61kdxl2712";
49 49 };
50 50 meta = {
51 51 license = [ pkgs.lib.licenses.bsdOriginal ];
52 52 };
53 53 };
54 54 "atomicwrites" = super.buildPythonPackage {
55 55 name = "atomicwrites-1.2.1";
56 56 doCheck = false;
57 57 src = fetchurl {
58 58 url = "https://files.pythonhosted.org/packages/ac/ed/a311712ef6b4355035489f665e63e1a73f9eb371929e3c98e5efd451069e/atomicwrites-1.2.1.tar.gz";
59 59 sha256 = "1vmkbw9j0qammwxbxycrs39gvdg4lc2d4lk98kwf8ag2manyi6pc";
60 60 };
61 61 meta = {
62 62 license = [ pkgs.lib.licenses.mit ];
63 63 };
64 64 };
65 65 "attrs" = super.buildPythonPackage {
66 66 name = "attrs-18.2.0";
67 67 doCheck = false;
68 68 src = fetchurl {
69 69 url = "https://files.pythonhosted.org/packages/0f/9e/26b1d194aab960063b266170e53c39f73ea0d0d3f5ce23313e0ec8ee9bdf/attrs-18.2.0.tar.gz";
70 70 sha256 = "0s9ydh058wmmf5v391pym877x4ahxg45dw6a0w4c7s5wgpigdjqh";
71 71 };
72 72 meta = {
73 73 license = [ pkgs.lib.licenses.mit ];
74 74 };
75 75 };
76 76 "authomatic" = super.buildPythonPackage {
77 77 name = "authomatic-0.1.0.post1";
78 78 doCheck = false;
79 79 src = fetchurl {
80 80 url = "https://code.rhodecode.com/upstream/authomatic/archive/90a9ce60cc405ae8a2bf5c3713acd5d78579a04e.tar.gz?md5=3c68720a1322b25254009518d1ff6801";
81 81 sha256 = "1cgk0a86sbsjbri06gf5z5l4npwkjdxw6fdnwl4vvfmxs2sx9yxw";
82 82 };
83 83 meta = {
84 84 license = [ pkgs.lib.licenses.mit ];
85 85 };
86 86 };
87 87 "babel" = super.buildPythonPackage {
88 88 name = "babel-1.3";
89 89 doCheck = false;
90 90 propagatedBuildInputs = [
91 91 self."pytz"
92 92 ];
93 93 src = fetchurl {
94 94 url = "https://files.pythonhosted.org/packages/33/27/e3978243a03a76398c384c83f7ca879bc6e8f1511233a621fcada135606e/Babel-1.3.tar.gz";
95 95 sha256 = "0bnin777lc53nxd1hp3apq410jj5wx92n08h7h4izpl4f4sx00lz";
96 96 };
97 97 meta = {
98 98 license = [ pkgs.lib.licenses.bsdOriginal ];
99 99 };
100 100 };
101 101 "backports.shutil-get-terminal-size" = super.buildPythonPackage {
102 102 name = "backports.shutil-get-terminal-size-1.0.0";
103 103 doCheck = false;
104 104 src = fetchurl {
105 105 url = "https://files.pythonhosted.org/packages/ec/9c/368086faa9c016efce5da3e0e13ba392c9db79e3ab740b763fe28620b18b/backports.shutil_get_terminal_size-1.0.0.tar.gz";
106 106 sha256 = "107cmn7g3jnbkp826zlj8rrj19fam301qvaqf0f3905f5217lgki";
107 107 };
108 108 meta = {
109 109 license = [ pkgs.lib.licenses.mit ];
110 110 };
111 111 };
112 112 "beaker" = super.buildPythonPackage {
113 113 name = "beaker-1.9.1";
114 114 doCheck = false;
115 115 propagatedBuildInputs = [
116 116 self."funcsigs"
117 117 ];
118 118 src = fetchurl {
119 119 url = "https://files.pythonhosted.org/packages/ca/14/a626188d0d0c7b55dd7cf1902046c2743bd392a7078bb53073e13280eb1e/Beaker-1.9.1.tar.gz";
120 120 sha256 = "08arsn61r255lhz6hcpn2lsiqpg30clla805ysx06wmbhvb6w9rj";
121 121 };
122 122 meta = {
123 123 license = [ pkgs.lib.licenses.bsdOriginal ];
124 124 };
125 125 };
126 126 "beautifulsoup4" = super.buildPythonPackage {
127 127 name = "beautifulsoup4-4.6.3";
128 128 doCheck = false;
129 129 src = fetchurl {
130 130 url = "https://files.pythonhosted.org/packages/88/df/86bffad6309f74f3ff85ea69344a078fc30003270c8df6894fca7a3c72ff/beautifulsoup4-4.6.3.tar.gz";
131 131 sha256 = "041dhalzjciw6qyzzq7a2k4h1yvyk76xigp35hv5ibnn448ydy4h";
132 132 };
133 133 meta = {
134 134 license = [ pkgs.lib.licenses.mit ];
135 135 };
136 136 };
137 137 "billiard" = super.buildPythonPackage {
138 138 name = "billiard-3.5.0.3";
139 139 doCheck = false;
140 140 src = fetchurl {
141 141 url = "https://files.pythonhosted.org/packages/39/ac/f5571210cca2e4f4532e38aaff242f26c8654c5e2436bee966c230647ccc/billiard-3.5.0.3.tar.gz";
142 142 sha256 = "1riwiiwgb141151md4ykx49qrz749akj5k8g290ji9bsqjyj4yqx";
143 143 };
144 144 meta = {
145 145 license = [ pkgs.lib.licenses.bsdOriginal ];
146 146 };
147 147 };
148 148 "bleach" = super.buildPythonPackage {
149 149 name = "bleach-3.0.2";
150 150 doCheck = false;
151 151 propagatedBuildInputs = [
152 152 self."six"
153 153 self."webencodings"
154 154 ];
155 155 src = fetchurl {
156 156 url = "https://files.pythonhosted.org/packages/ae/31/680afc7d44040004296a2d8f0584983c2f2386448cd9d0964197e6c1160e/bleach-3.0.2.tar.gz";
157 157 sha256 = "06474zg7f73hv8h1xw2wcsmvn2ygj73zxgxxqg8zcx8ap1srdls8";
158 158 };
159 159 meta = {
160 160 license = [ pkgs.lib.licenses.asl20 ];
161 161 };
162 162 };
163 163 "bumpversion" = super.buildPythonPackage {
164 164 name = "bumpversion-0.5.3";
165 165 doCheck = false;
166 166 src = fetchurl {
167 167 url = "https://files.pythonhosted.org/packages/14/41/8c9da3549f8e00c84f0432c3a8cf8ed6898374714676aab91501d48760db/bumpversion-0.5.3.tar.gz";
168 168 sha256 = "0zn7694yfipxg35ikkfh7kvgl2fissha3dnqad2c5bvsvmrwhi37";
169 169 };
170 170 meta = {
171 171 license = [ pkgs.lib.licenses.mit ];
172 172 };
173 173 };
174 174 "celery" = super.buildPythonPackage {
175 175 name = "celery-4.1.1";
176 176 doCheck = false;
177 177 propagatedBuildInputs = [
178 178 self."pytz"
179 179 self."billiard"
180 180 self."kombu"
181 181 ];
182 182 src = fetchurl {
183 183 url = "https://files.pythonhosted.org/packages/e9/cf/a4c0597effca20c57eb586324e41d1180bc8f13a933da41e0646cff69f02/celery-4.1.1.tar.gz";
184 184 sha256 = "1xbir4vw42n2ir9lanhwl7w69zpmj7lbi66fxm2b7pyvkcss7wni";
185 185 };
186 186 meta = {
187 187 license = [ pkgs.lib.licenses.bsdOriginal ];
188 188 };
189 189 };
190 190 "chameleon" = super.buildPythonPackage {
191 191 name = "chameleon-2.24";
192 192 doCheck = false;
193 193 src = fetchurl {
194 194 url = "https://files.pythonhosted.org/packages/5a/9e/637379ffa13c5172b5c0e704833ffea6bf51cec7567f93fd6e903d53ed74/Chameleon-2.24.tar.gz";
195 195 sha256 = "0ykqr7syxfa6h9adjfnsv1gdsca2xzm22vmic8859n0f0j09abj5";
196 196 };
197 197 meta = {
198 198 license = [ { fullName = "BSD-like (http://repoze.org/license.html)"; } ];
199 199 };
200 200 };
201 201 "channelstream" = super.buildPythonPackage {
202 202 name = "channelstream-0.5.2";
203 203 doCheck = false;
204 204 propagatedBuildInputs = [
205 205 self."gevent"
206 206 self."ws4py"
207 207 self."pyramid"
208 208 self."pyramid-jinja2"
209 209 self."itsdangerous"
210 210 self."requests"
211 211 self."six"
212 212 ];
213 213 src = fetchurl {
214 214 url = "https://files.pythonhosted.org/packages/2b/31/29a8e085cf5bf97fa88e7b947adabfc581a18a3463adf77fb6dada34a65f/channelstream-0.5.2.tar.gz";
215 215 sha256 = "1qbm4xdl5hfkja683x546bncg3rqq8qv79w1m1a1wd48cqqzb6rm";
216 216 };
217 217 meta = {
218 218 license = [ pkgs.lib.licenses.bsdOriginal ];
219 219 };
220 220 };
221 221 "click" = super.buildPythonPackage {
222 222 name = "click-6.6";
223 223 doCheck = false;
224 224 src = fetchurl {
225 225 url = "https://files.pythonhosted.org/packages/7a/00/c14926d8232b36b08218067bcd5853caefb4737cda3f0a47437151344792/click-6.6.tar.gz";
226 226 sha256 = "1sggipyz52crrybwbr9xvwxd4aqigvplf53k9w3ygxmzivd1jsnc";
227 227 };
228 228 meta = {
229 229 license = [ pkgs.lib.licenses.bsdOriginal ];
230 230 };
231 231 };
232 232 "colander" = super.buildPythonPackage {
233 233 name = "colander-1.5.1";
234 234 doCheck = false;
235 235 propagatedBuildInputs = [
236 236 self."translationstring"
237 237 self."iso8601"
238 238 self."enum34"
239 239 ];
240 240 src = fetchurl {
241 241 url = "https://files.pythonhosted.org/packages/ec/d1/fcca811a0a692c69d27e36b4d11a73acb98b4bab48323442642b6fd4386d/colander-1.5.1.tar.gz";
242 242 sha256 = "18ah4cwwxnpm6qxi6x9ipy51dal4spd343h44s5wd01cnhgrwsyq";
243 243 };
244 244 meta = {
245 245 license = [ { fullName = "BSD-derived (http://www.repoze.org/LICENSE.txt)"; } ];
246 246 };
247 247 };
248 248 "configobj" = super.buildPythonPackage {
249 249 name = "configobj-5.0.6";
250 250 doCheck = false;
251 251 propagatedBuildInputs = [
252 252 self."six"
253 253 ];
254 254 src = fetchurl {
255 255 url = "https://code.rhodecode.com/upstream/configobj/archive/a11ff0a0bd4fbda9e3a91267e720f88329efb4a6.tar.gz?md5=9916c524ea11a6c418217af6b28d4b3c";
256 256 sha256 = "1hhcxirwvg58grlfr177b3awhbq8hlx1l3lh69ifl1ki7lfd1s1x";
257 257 };
258 258 meta = {
259 259 license = [ pkgs.lib.licenses.bsdOriginal ];
260 260 };
261 261 };
262 262 "configparser" = super.buildPythonPackage {
263 263 name = "configparser-3.5.0";
264 264 doCheck = false;
265 265 src = fetchurl {
266 266 url = "https://files.pythonhosted.org/packages/7c/69/c2ce7e91c89dc073eb1aa74c0621c3eefbffe8216b3f9af9d3885265c01c/configparser-3.5.0.tar.gz";
267 267 sha256 = "0fi7vf09vi1588jd8f16a021m5y6ih2hy7rpbjb408xw45qb822k";
268 268 };
269 269 meta = {
270 270 license = [ pkgs.lib.licenses.mit ];
271 271 };
272 272 };
273 273 "cov-core" = super.buildPythonPackage {
274 274 name = "cov-core-1.15.0";
275 275 doCheck = false;
276 276 propagatedBuildInputs = [
277 277 self."coverage"
278 278 ];
279 279 src = fetchurl {
280 280 url = "https://files.pythonhosted.org/packages/4b/87/13e75a47b4ba1be06f29f6d807ca99638bedc6b57fa491cd3de891ca2923/cov-core-1.15.0.tar.gz";
281 281 sha256 = "0k3np9ymh06yv1ib96sb6wfsxjkqhmik8qfsn119vnhga9ywc52a";
282 282 };
283 283 meta = {
284 284 license = [ pkgs.lib.licenses.mit ];
285 285 };
286 286 };
287 287 "coverage" = super.buildPythonPackage {
288 288 name = "coverage-4.5.1";
289 289 doCheck = false;
290 290 src = fetchurl {
291 291 url = "https://files.pythonhosted.org/packages/35/fe/e7df7289d717426093c68d156e0fd9117c8f4872b6588e8a8928a0f68424/coverage-4.5.1.tar.gz";
292 292 sha256 = "1wbrzpxka3xd4nmmkc6q0ir343d91kymwsm8pbmwa0d2a7q4ir2n";
293 293 };
294 294 meta = {
295 295 license = [ pkgs.lib.licenses.asl20 ];
296 296 };
297 297 };
298 298 "cssselect" = super.buildPythonPackage {
299 299 name = "cssselect-1.0.3";
300 300 doCheck = false;
301 301 src = fetchurl {
302 302 url = "https://files.pythonhosted.org/packages/52/ea/f31e1d2e9eb130fda2a631e22eac369dc644e8807345fbed5113f2d6f92b/cssselect-1.0.3.tar.gz";
303 303 sha256 = "011jqa2jhmydhi0iz4v1w3cr540z5zas8g2bw8brdw4s4b2qnv86";
304 304 };
305 305 meta = {
306 306 license = [ pkgs.lib.licenses.bsdOriginal ];
307 307 };
308 308 };
309 309 "decorator" = super.buildPythonPackage {
310 310 name = "decorator-4.1.2";
311 311 doCheck = false;
312 312 src = fetchurl {
313 313 url = "https://files.pythonhosted.org/packages/bb/e0/f6e41e9091e130bf16d4437dabbac3993908e4d6485ecbc985ef1352db94/decorator-4.1.2.tar.gz";
314 314 sha256 = "1d8npb11kxyi36mrvjdpcjij76l5zfyrz2f820brf0l0rcw4vdkw";
315 315 };
316 316 meta = {
317 317 license = [ pkgs.lib.licenses.bsdOriginal { fullName = "new BSD License"; } ];
318 318 };
319 319 };
320 320 "deform" = super.buildPythonPackage {
321 321 name = "deform-2.0.7";
322 322 doCheck = false;
323 323 propagatedBuildInputs = [
324 324 self."chameleon"
325 325 self."colander"
326 326 self."iso8601"
327 327 self."peppercorn"
328 328 self."translationstring"
329 329 self."zope.deprecation"
330 330 ];
331 331 src = fetchurl {
332 332 url = "https://files.pythonhosted.org/packages/cf/a1/bc234527b8f181de9acd80e796483c00007658d1e32b7de78f1c2e004d9a/deform-2.0.7.tar.gz";
333 333 sha256 = "0jnpi0zr2hjvbmiz6nm33yqv976dn9lf51vhlzqc0i75xcr9rwig";
334 334 };
335 335 meta = {
336 336 license = [ { fullName = "Repoze Public License"; } { fullName = "BSD-derived (http://www.repoze.org/LICENSE.txt)"; } ];
337 337 };
338 338 };
339 339 "defusedxml" = super.buildPythonPackage {
340 340 name = "defusedxml-0.5.0";
341 341 doCheck = false;
342 342 src = fetchurl {
343 343 url = "https://files.pythonhosted.org/packages/74/ba/4ba4e89e21b5a2e267d80736ea674609a0a33cc4435a6d748ef04f1f9374/defusedxml-0.5.0.tar.gz";
344 344 sha256 = "1x54n0h8hl92vvwyymx883fbqpqjwn2mc8fb383bcg3z9zwz5mr4";
345 345 };
346 346 meta = {
347 347 license = [ pkgs.lib.licenses.psfl ];
348 348 };
349 349 };
350 350 "dm.xmlsec.binding" = super.buildPythonPackage {
351 351 name = "dm.xmlsec.binding-1.3.7";
352 352 doCheck = false;
353 353 propagatedBuildInputs = [
354 354 self."setuptools"
355 355 self."lxml"
356 356 ];
357 357 src = fetchurl {
358 358 url = "https://files.pythonhosted.org/packages/2c/9e/7651982d50252692991acdae614af821fd6c79bc8dcd598ad71d55be8fc7/dm.xmlsec.binding-1.3.7.tar.gz";
359 359 sha256 = "03jjjscx1pz2nc0dwiw9nia02qbz1c6f0f9zkyr8fmvys2n5jkb3";
360 360 };
361 361 meta = {
362 362 license = [ pkgs.lib.licenses.bsdOriginal ];
363 363 };
364 364 };
365 365 "docutils" = super.buildPythonPackage {
366 366 name = "docutils-0.14";
367 367 doCheck = false;
368 368 src = fetchurl {
369 369 url = "https://files.pythonhosted.org/packages/84/f4/5771e41fdf52aabebbadecc9381d11dea0fa34e4759b4071244fa094804c/docutils-0.14.tar.gz";
370 370 sha256 = "0x22fs3pdmr42kvz6c654756wja305qv6cx1zbhwlagvxgr4xrji";
371 371 };
372 372 meta = {
373 373 license = [ pkgs.lib.licenses.bsdOriginal pkgs.lib.licenses.publicDomain pkgs.lib.licenses.gpl1 { fullName = "public domain, Python, 2-Clause BSD, GPL 3 (see COPYING.txt)"; } pkgs.lib.licenses.psfl ];
374 374 };
375 375 };
376 376 "dogpile.cache" = super.buildPythonPackage {
377 377 name = "dogpile.cache-0.6.7";
378 378 doCheck = false;
379 379 src = fetchurl {
380 380 url = "https://files.pythonhosted.org/packages/ee/bd/440da735a11c6087eed7cc8747fc4b995cbac2464168682f8ee1c8e43844/dogpile.cache-0.6.7.tar.gz";
381 381 sha256 = "1aw8rx8vhb75y7zc6gi67g21sw057jdx7i8m3jq7kf3nqavxx9zw";
382 382 };
383 383 meta = {
384 384 license = [ pkgs.lib.licenses.bsdOriginal ];
385 385 };
386 386 };
387 387 "dogpile.core" = super.buildPythonPackage {
388 388 name = "dogpile.core-0.4.1";
389 389 doCheck = false;
390 390 src = fetchurl {
391 391 url = "https://files.pythonhosted.org/packages/0e/77/e72abc04c22aedf874301861e5c1e761231c288b5de369c18be8f4b5c9bb/dogpile.core-0.4.1.tar.gz";
392 392 sha256 = "0xpdvg4kr1isfkrh1rfsh7za4q5a5s6l2kf9wpvndbwf3aqjyrdy";
393 393 };
394 394 meta = {
395 395 license = [ pkgs.lib.licenses.bsdOriginal ];
396 396 };
397 397 };
398 398 "ecdsa" = super.buildPythonPackage {
399 399 name = "ecdsa-0.13";
400 400 doCheck = false;
401 401 src = fetchurl {
402 402 url = "https://files.pythonhosted.org/packages/f9/e5/99ebb176e47f150ac115ffeda5fedb6a3dbb3c00c74a59fd84ddf12f5857/ecdsa-0.13.tar.gz";
403 403 sha256 = "1yj31j0asmrx4an9xvsaj2icdmzy6pw0glfpqrrkrphwdpi1xkv4";
404 404 };
405 405 meta = {
406 406 license = [ pkgs.lib.licenses.mit ];
407 407 };
408 408 };
409 409 "elasticsearch" = super.buildPythonPackage {
410 name = "elasticsearch-2.3.0";
410 name = "elasticsearch-6.3.1";
411 411 doCheck = false;
412 412 propagatedBuildInputs = [
413 413 self."urllib3"
414 414 ];
415 415 src = fetchurl {
416 url = "https://files.pythonhosted.org/packages/10/35/5fd52c5f0b0ee405ed4b5195e8bce44c5e041787680dc7b94b8071cac600/elasticsearch-2.3.0.tar.gz";
417 sha256 = "10ad2dk73xsys9vajwsncibs69asa63w1hgwz6lz1prjpyi80c5y";
416 url = "https://files.pythonhosted.org/packages/9d/ce/c4664e8380e379a9402ecfbaf158e56396da90d520daba21cfa840e0eb71/elasticsearch-6.3.1.tar.gz";
417 sha256 = "12y93v0yn7a4xmf969239g8gb3l4cdkclfpbk1qc8hx5qkymrnma";
418 418 };
419 419 meta = {
420 420 license = [ pkgs.lib.licenses.asl20 ];
421 421 };
422 422 };
423 423 "elasticsearch-dsl" = super.buildPythonPackage {
424 name = "elasticsearch-dsl-2.2.0";
424 name = "elasticsearch-dsl-6.3.1";
425 425 doCheck = false;
426 426 propagatedBuildInputs = [
427 427 self."six"
428 428 self."python-dateutil"
429 429 self."elasticsearch"
430 self."ipaddress"
431 ];
432 src = fetchurl {
433 url = "https://files.pythonhosted.org/packages/4c/0d/1549f50c591db6bb4e66cbcc8d34a6e537c3d89aa426b167c244fd46420a/elasticsearch-dsl-6.3.1.tar.gz";
434 sha256 = "1gh8a0shqi105k325hgwb9avrpdjh0mc6mxwfg9ba7g6lssb702z";
435 };
436 meta = {
437 license = [ pkgs.lib.licenses.asl20 ];
438 };
439 };
440 "elasticsearch1" = super.buildPythonPackage {
441 name = "elasticsearch1-1.10.0";
442 doCheck = false;
443 propagatedBuildInputs = [
444 self."urllib3"
430 445 ];
431 446 src = fetchurl {
432 url = "https://files.pythonhosted.org/packages/66/2f/52a086968788e58461641570f45c3207a52d46ebbe9b77dc22b6a8ffda66/elasticsearch-dsl-2.2.0.tar.gz";
433 sha256 = "1g4kxzxsdwlsl2a9kscmx11pafgimhj7y8wrfksv8pgvpkfb9fwr";
447 url = "https://files.pythonhosted.org/packages/a6/eb/73e75f9681fa71e3157b8ee878534235d57f24ee64f0e77f8d995fb57076/elasticsearch1-1.10.0.tar.gz";
448 sha256 = "0g89444kd5zwql4vbvyrmi2m6l6dcj6ga98j4hqxyyyz6z20aki2";
449 };
450 meta = {
451 license = [ pkgs.lib.licenses.asl20 ];
452 };
453 };
454 "elasticsearch1-dsl" = super.buildPythonPackage {
455 name = "elasticsearch1-dsl-0.0.12";
456 doCheck = false;
457 propagatedBuildInputs = [
458 self."six"
459 self."python-dateutil"
460 self."elasticsearch1"
461 ];
462 src = fetchurl {
463 url = "https://files.pythonhosted.org/packages/eb/9d/785342775cb10eddc9b8d7457d618a423b4f0b89d8b2b2d1bc27190d71db/elasticsearch1-dsl-0.0.12.tar.gz";
464 sha256 = "0ig1ly39v93hba0z975wnhbmzwj28w6w1sqlr2g7cn5spp732bhk";
465 };
466 meta = {
467 license = [ pkgs.lib.licenses.asl20 ];
468 };
469 };
470 "elasticsearch2" = super.buildPythonPackage {
471 name = "elasticsearch2-2.5.0";
472 doCheck = false;
473 propagatedBuildInputs = [
474 self."urllib3"
475 ];
476 src = fetchurl {
477 url = "https://files.pythonhosted.org/packages/84/77/63cf63d4ba11d913b5278406f2a37b0712bec6fc85edfb6151a33eaeba25/elasticsearch2-2.5.0.tar.gz";
478 sha256 = "0ky0q16lbvz022yv6q3pix7aamf026p1y994537ccjf0p0dxnbxr";
434 479 };
435 480 meta = {
436 481 license = [ pkgs.lib.licenses.asl20 ];
437 482 };
438 483 };
439 484 "entrypoints" = super.buildPythonPackage {
440 485 name = "entrypoints-0.2.2";
441 486 doCheck = false;
442 487 propagatedBuildInputs = [
443 488 self."configparser"
444 489 ];
445 490 src = fetchurl {
446 491 url = "https://code.rhodecode.com/upstream/entrypoints/archive/96e6d645684e1af3d7df5b5272f3fe85a546b233.tar.gz?md5=7db37771aea9ac9fefe093e5d6987313";
447 492 sha256 = "0bihrdp8ahsys437kxdhk52gz6kib8rxjv71i93wkw7594fcaxll";
448 493 };
449 494 meta = {
450 495 license = [ pkgs.lib.licenses.mit ];
451 496 };
452 497 };
453 498 "enum34" = super.buildPythonPackage {
454 499 name = "enum34-1.1.6";
455 500 doCheck = false;
456 501 src = fetchurl {
457 502 url = "https://files.pythonhosted.org/packages/bf/3e/31d502c25302814a7c2f1d3959d2a3b3f78e509002ba91aea64993936876/enum34-1.1.6.tar.gz";
458 503 sha256 = "1cgm5ng2gcfrkrm3hc22brl6chdmv67b9zvva9sfs7gn7dwc9n4a";
459 504 };
460 505 meta = {
461 506 license = [ pkgs.lib.licenses.bsdOriginal ];
462 507 };
463 508 };
464 509 "formencode" = super.buildPythonPackage {
465 510 name = "formencode-1.2.4";
466 511 doCheck = false;
467 512 src = fetchurl {
468 513 url = "https://files.pythonhosted.org/packages/8e/59/0174271a6f004512e0201188593e6d319db139d14cb7490e488bbb078015/FormEncode-1.2.4.tar.gz";
469 514 sha256 = "1fgy04sdy4yry5xcjls3x3xy30dqwj58ycnkndim819jx0788w42";
470 515 };
471 516 meta = {
472 517 license = [ pkgs.lib.licenses.psfl ];
473 518 };
474 519 };
475 520 "funcsigs" = super.buildPythonPackage {
476 521 name = "funcsigs-1.0.2";
477 522 doCheck = false;
478 523 src = fetchurl {
479 524 url = "https://files.pythonhosted.org/packages/94/4a/db842e7a0545de1cdb0439bb80e6e42dfe82aaeaadd4072f2263a4fbed23/funcsigs-1.0.2.tar.gz";
480 525 sha256 = "0l4g5818ffyfmfs1a924811azhjj8ax9xd1cffr1mzd3ycn0zfx7";
481 526 };
482 527 meta = {
483 528 license = [ { fullName = "ASL"; } pkgs.lib.licenses.asl20 ];
484 529 };
485 530 };
486 531 "functools32" = super.buildPythonPackage {
487 532 name = "functools32-3.2.3.post2";
488 533 doCheck = false;
489 534 src = fetchurl {
490 535 url = "https://files.pythonhosted.org/packages/c5/60/6ac26ad05857c601308d8fb9e87fa36d0ebf889423f47c3502ef034365db/functools32-3.2.3-2.tar.gz";
491 536 sha256 = "0v8ya0b58x47wp216n1zamimv4iw57cxz3xxhzix52jkw3xks9gn";
492 537 };
493 538 meta = {
494 539 license = [ pkgs.lib.licenses.psfl ];
495 540 };
496 541 };
497 542 "future" = super.buildPythonPackage {
498 543 name = "future-0.14.3";
499 544 doCheck = false;
500 545 src = fetchurl {
501 546 url = "https://files.pythonhosted.org/packages/83/80/8ef3a11a15f8eaafafa0937b20c1b3f73527e69ab6b3fa1cf94a5a96aabb/future-0.14.3.tar.gz";
502 547 sha256 = "1savk7jx7hal032f522c5ajhh8fra6gmnadrj9adv5qxi18pv1b2";
503 548 };
504 549 meta = {
505 550 license = [ { fullName = "OSI Approved"; } pkgs.lib.licenses.mit ];
506 551 };
507 552 };
508 553 "futures" = super.buildPythonPackage {
509 554 name = "futures-3.0.2";
510 555 doCheck = false;
511 556 src = fetchurl {
512 557 url = "https://files.pythonhosted.org/packages/f8/e7/fc0fcbeb9193ba2d4de00b065e7fd5aecd0679e93ce95a07322b2b1434f4/futures-3.0.2.tar.gz";
513 558 sha256 = "0mz2pbgxbc2nbib1szifi07whjbfs4r02pv2z390z7p410awjgyw";
514 559 };
515 560 meta = {
516 561 license = [ pkgs.lib.licenses.bsdOriginal ];
517 562 };
518 563 };
519 564 "gevent" = super.buildPythonPackage {
520 565 name = "gevent-1.3.7";
521 566 doCheck = false;
522 567 propagatedBuildInputs = [
523 568 self."greenlet"
524 569 ];
525 570 src = fetchurl {
526 571 url = "https://files.pythonhosted.org/packages/10/c1/9499b146bfa43aa4f1e0ed1bab1bd3209a4861d25650c11725036c731cf5/gevent-1.3.7.tar.gz";
527 572 sha256 = "0b0fr04qdk1p4sniv87fh8z5psac60x01pv054kpgi94520g81iz";
528 573 };
529 574 meta = {
530 575 license = [ pkgs.lib.licenses.mit ];
531 576 };
532 577 };
533 578 "gnureadline" = super.buildPythonPackage {
534 579 name = "gnureadline-6.3.8";
535 580 doCheck = false;
536 581 src = fetchurl {
537 582 url = "https://files.pythonhosted.org/packages/50/64/86085c823cd78f9df9d8e33dce0baa71618016f8860460b82cf6610e1eb3/gnureadline-6.3.8.tar.gz";
538 583 sha256 = "0ddhj98x2nv45iz4aadk4b9m0b1kpsn1xhcbypn5cd556knhiqjq";
539 584 };
540 585 meta = {
541 586 license = [ { fullName = "GNU General Public License v3 (GPLv3)"; } pkgs.lib.licenses.gpl1 ];
542 587 };
543 588 };
544 589 "gprof2dot" = super.buildPythonPackage {
545 590 name = "gprof2dot-2017.9.19";
546 591 doCheck = false;
547 592 src = fetchurl {
548 593 url = "https://files.pythonhosted.org/packages/9d/36/f977122502979f3dfb50704979c9ed70e6b620787942b089bf1af15f5aba/gprof2dot-2017.9.19.tar.gz";
549 594 sha256 = "17ih23ld2nzgc3xwgbay911l6lh96jp1zshmskm17n1gg2i7mg6f";
550 595 };
551 596 meta = {
552 597 license = [ { fullName = "GNU Lesser General Public License v3 or later (LGPLv3+)"; } { fullName = "LGPL"; } ];
553 598 };
554 599 };
555 600 "greenlet" = super.buildPythonPackage {
556 601 name = "greenlet-0.4.15";
557 602 doCheck = false;
558 603 src = fetchurl {
559 604 url = "https://files.pythonhosted.org/packages/f8/e8/b30ae23b45f69aa3f024b46064c0ac8e5fcb4f22ace0dca8d6f9c8bbe5e7/greenlet-0.4.15.tar.gz";
560 605 sha256 = "1g4g1wwc472ds89zmqlpyan3fbnzpa8qm48z3z1y6mlk44z485ll";
561 606 };
562 607 meta = {
563 608 license = [ pkgs.lib.licenses.mit ];
564 609 };
565 610 };
566 611 "gunicorn" = super.buildPythonPackage {
567 612 name = "gunicorn-19.9.0";
568 613 doCheck = false;
569 614 src = fetchurl {
570 615 url = "https://files.pythonhosted.org/packages/47/52/68ba8e5e8ba251e54006a49441f7ccabca83b6bef5aedacb4890596c7911/gunicorn-19.9.0.tar.gz";
571 616 sha256 = "1wzlf4xmn6qjirh5w81l6i6kqjnab1n1qqkh7zsj1yb6gh4n49ps";
572 617 };
573 618 meta = {
574 619 license = [ pkgs.lib.licenses.mit ];
575 620 };
576 621 };
577 622 "hupper" = super.buildPythonPackage {
578 623 name = "hupper-1.4.2";
579 624 doCheck = false;
580 625 src = fetchurl {
581 626 url = "https://files.pythonhosted.org/packages/f1/75/1915dc7650b4867fa3049256e24ca8eddb5989998fcec788cf52b9812dfc/hupper-1.4.2.tar.gz";
582 627 sha256 = "16vb9fkiaakdpcp6pn56h3w0dwvm67bxq2k2dv4i382qhqwphdzb";
583 628 };
584 629 meta = {
585 630 license = [ pkgs.lib.licenses.mit ];
586 631 };
587 632 };
588 633 "infrae.cache" = super.buildPythonPackage {
589 634 name = "infrae.cache-1.0.1";
590 635 doCheck = false;
591 636 propagatedBuildInputs = [
592 637 self."beaker"
593 638 self."repoze.lru"
594 639 ];
595 640 src = fetchurl {
596 641 url = "https://files.pythonhosted.org/packages/bb/f0/e7d5e984cf6592fd2807dc7bc44a93f9d18e04e6a61f87fdfb2622422d74/infrae.cache-1.0.1.tar.gz";
597 642 sha256 = "1dvqsjn8vw253wz9d1pz17j79mf4bs53dvp2qxck2qdp1am1njw4";
598 643 };
599 644 meta = {
600 645 license = [ pkgs.lib.licenses.zpl21 ];
601 646 };
602 647 };
603 648 "invoke" = super.buildPythonPackage {
604 649 name = "invoke-0.13.0";
605 650 doCheck = false;
606 651 src = fetchurl {
607 652 url = "https://files.pythonhosted.org/packages/47/bf/d07ef52fa1ac645468858bbac7cb95b246a972a045e821493d17d89c81be/invoke-0.13.0.tar.gz";
608 653 sha256 = "0794vhgxfmkh0vzkkg5cfv1w82g3jc3xr18wim29far9qpx9468s";
609 654 };
610 655 meta = {
611 656 license = [ pkgs.lib.licenses.bsdOriginal ];
612 657 };
613 658 };
614 659 "ipaddress" = super.buildPythonPackage {
615 660 name = "ipaddress-1.0.22";
616 661 doCheck = false;
617 662 src = fetchurl {
618 663 url = "https://files.pythonhosted.org/packages/97/8d/77b8cedcfbf93676148518036c6b1ce7f8e14bf07e95d7fd4ddcb8cc052f/ipaddress-1.0.22.tar.gz";
619 664 sha256 = "0b570bm6xqpjwqis15pvdy6lyvvzfndjvkynilcddjj5x98wfimi";
620 665 };
621 666 meta = {
622 667 license = [ pkgs.lib.licenses.psfl ];
623 668 };
624 669 };
625 670 "ipdb" = super.buildPythonPackage {
626 671 name = "ipdb-0.11";
627 672 doCheck = false;
628 673 propagatedBuildInputs = [
629 674 self."setuptools"
630 675 self."ipython"
631 676 ];
632 677 src = fetchurl {
633 678 url = "https://files.pythonhosted.org/packages/80/fe/4564de08f174f3846364b3add8426d14cebee228f741c27e702b2877e85b/ipdb-0.11.tar.gz";
634 679 sha256 = "02m0l8wrhhd3z7dg3czn5ys1g5pxib516hpshdzp7rxzsxgcd0bh";
635 680 };
636 681 meta = {
637 682 license = [ pkgs.lib.licenses.bsdOriginal ];
638 683 };
639 684 };
640 685 "ipython" = super.buildPythonPackage {
641 686 name = "ipython-5.1.0";
642 687 doCheck = false;
643 688 propagatedBuildInputs = [
644 689 self."setuptools"
645 690 self."decorator"
646 691 self."pickleshare"
647 692 self."simplegeneric"
648 693 self."traitlets"
649 694 self."prompt-toolkit"
650 695 self."pygments"
651 696 self."pexpect"
652 697 self."backports.shutil-get-terminal-size"
653 698 self."pathlib2"
654 699 self."pexpect"
655 700 ];
656 701 src = fetchurl {
657 702 url = "https://files.pythonhosted.org/packages/89/63/a9292f7cd9d0090a0f995e1167f3f17d5889dcbc9a175261719c513b9848/ipython-5.1.0.tar.gz";
658 703 sha256 = "0qdrf6aj9kvjczd5chj1my8y2iq09am9l8bb2a1334a52d76kx3y";
659 704 };
660 705 meta = {
661 706 license = [ pkgs.lib.licenses.bsdOriginal ];
662 707 };
663 708 };
664 709 "ipython-genutils" = super.buildPythonPackage {
665 710 name = "ipython-genutils-0.2.0";
666 711 doCheck = false;
667 712 src = fetchurl {
668 713 url = "https://files.pythonhosted.org/packages/e8/69/fbeffffc05236398ebfcfb512b6d2511c622871dca1746361006da310399/ipython_genutils-0.2.0.tar.gz";
669 714 sha256 = "1a4bc9y8hnvq6cp08qs4mckgm6i6ajpndp4g496rvvzcfmp12bpb";
670 715 };
671 716 meta = {
672 717 license = [ pkgs.lib.licenses.bsdOriginal ];
673 718 };
674 719 };
675 720 "iso8601" = super.buildPythonPackage {
676 721 name = "iso8601-0.1.11";
677 722 doCheck = false;
678 723 src = fetchurl {
679 724 url = "https://files.pythonhosted.org/packages/c0/75/c9209ee4d1b5975eb8c2cba4428bde6b61bd55664a98290dd015cdb18e98/iso8601-0.1.11.tar.gz";
680 725 sha256 = "0c7gh3lsdjds262h0v1sqc66l7hqgfwbakn96qrhdbl0i3vm5yz8";
681 726 };
682 727 meta = {
683 728 license = [ pkgs.lib.licenses.mit ];
684 729 };
685 730 };
686 731 "isodate" = super.buildPythonPackage {
687 732 name = "isodate-0.6.0";
688 733 doCheck = false;
689 734 propagatedBuildInputs = [
690 735 self."six"
691 736 ];
692 737 src = fetchurl {
693 738 url = "https://files.pythonhosted.org/packages/b1/80/fb8c13a4cd38eb5021dc3741a9e588e4d1de88d895c1910c6fc8a08b7a70/isodate-0.6.0.tar.gz";
694 739 sha256 = "1n7jkz68kk5pwni540pr5zdh99bf6ywydk1p5pdrqisrawylldif";
695 740 };
696 741 meta = {
697 742 license = [ pkgs.lib.licenses.bsdOriginal ];
698 743 };
699 744 };
700 745 "itsdangerous" = super.buildPythonPackage {
701 746 name = "itsdangerous-0.24";
702 747 doCheck = false;
703 748 src = fetchurl {
704 749 url = "https://files.pythonhosted.org/packages/dc/b4/a60bcdba945c00f6d608d8975131ab3f25b22f2bcfe1dab221165194b2d4/itsdangerous-0.24.tar.gz";
705 750 sha256 = "06856q6x675ly542ig0plbqcyab6ksfzijlyf1hzhgg3sgwgrcyb";
706 751 };
707 752 meta = {
708 753 license = [ pkgs.lib.licenses.bsdOriginal ];
709 754 };
710 755 };
711 756 "jinja2" = super.buildPythonPackage {
712 757 name = "jinja2-2.9.6";
713 758 doCheck = false;
714 759 propagatedBuildInputs = [
715 760 self."markupsafe"
716 761 ];
717 762 src = fetchurl {
718 763 url = "https://files.pythonhosted.org/packages/90/61/f820ff0076a2599dd39406dcb858ecb239438c02ce706c8e91131ab9c7f1/Jinja2-2.9.6.tar.gz";
719 764 sha256 = "1zzrkywhziqffrzks14kzixz7nd4yh2vc0fb04a68vfd2ai03anx";
720 765 };
721 766 meta = {
722 767 license = [ pkgs.lib.licenses.bsdOriginal ];
723 768 };
724 769 };
725 770 "jsonschema" = super.buildPythonPackage {
726 771 name = "jsonschema-2.6.0";
727 772 doCheck = false;
728 773 propagatedBuildInputs = [
729 774 self."functools32"
730 775 ];
731 776 src = fetchurl {
732 777 url = "https://files.pythonhosted.org/packages/58/b9/171dbb07e18c6346090a37f03c7e74410a1a56123f847efed59af260a298/jsonschema-2.6.0.tar.gz";
733 778 sha256 = "00kf3zmpp9ya4sydffpifn0j0mzm342a2vzh82p6r0vh10cg7xbg";
734 779 };
735 780 meta = {
736 781 license = [ pkgs.lib.licenses.mit ];
737 782 };
738 783 };
739 784 "jupyter-client" = super.buildPythonPackage {
740 785 name = "jupyter-client-5.0.0";
741 786 doCheck = false;
742 787 propagatedBuildInputs = [
743 788 self."traitlets"
744 789 self."jupyter-core"
745 790 self."pyzmq"
746 791 self."python-dateutil"
747 792 ];
748 793 src = fetchurl {
749 794 url = "https://files.pythonhosted.org/packages/e5/6f/65412ed462202b90134b7e761b0b7e7f949e07a549c1755475333727b3d0/jupyter_client-5.0.0.tar.gz";
750 795 sha256 = "0nxw4rqk4wsjhc87gjqd7pv89cb9dnimcfnmcmp85bmrvv1gjri7";
751 796 };
752 797 meta = {
753 798 license = [ pkgs.lib.licenses.bsdOriginal ];
754 799 };
755 800 };
756 801 "jupyter-core" = super.buildPythonPackage {
757 802 name = "jupyter-core-4.4.0";
758 803 doCheck = false;
759 804 propagatedBuildInputs = [
760 805 self."traitlets"
761 806 ];
762 807 src = fetchurl {
763 808 url = "https://files.pythonhosted.org/packages/b6/2d/2804f4de3a95583f65e5dcb4d7c8c7183124882323758996e867f47e72af/jupyter_core-4.4.0.tar.gz";
764 809 sha256 = "1dy083rarba8prn9f9srxq3c7n7vyql02ycrqq306c40lr57aw5s";
765 810 };
766 811 meta = {
767 812 license = [ pkgs.lib.licenses.bsdOriginal ];
768 813 };
769 814 };
770 815 "kombu" = super.buildPythonPackage {
771 816 name = "kombu-4.2.0";
772 817 doCheck = false;
773 818 propagatedBuildInputs = [
774 819 self."amqp"
775 820 ];
776 821 src = fetchurl {
777 822 url = "https://files.pythonhosted.org/packages/ab/b1/46a7a8babf5e60f3b2ca081a100af8edfcf132078a726375f52a054e70cf/kombu-4.2.0.tar.gz";
778 823 sha256 = "1yz19qlqf0inl1mnwlpq9j6kj9r67clpy0xg99phyg4329rw80fn";
779 824 };
780 825 meta = {
781 826 license = [ pkgs.lib.licenses.bsdOriginal ];
782 827 };
783 828 };
784 829 "lxml" = super.buildPythonPackage {
785 830 name = "lxml-4.2.5";
786 831 doCheck = false;
787 832 src = fetchurl {
788 833 url = "https://files.pythonhosted.org/packages/4b/20/ddf5eb3bd5c57582d2b4652b4bbcf8da301bdfe5d805cb94e805f4d7464d/lxml-4.2.5.tar.gz";
789 834 sha256 = "0zw0y9hs0nflxhl9cs6ipwwh53szi3w2x06wl0k9cylyqac0cwin";
790 835 };
791 836 meta = {
792 837 license = [ pkgs.lib.licenses.bsdOriginal ];
793 838 };
794 839 };
795 840 "mako" = super.buildPythonPackage {
796 841 name = "mako-1.0.7";
797 842 doCheck = false;
798 843 propagatedBuildInputs = [
799 844 self."markupsafe"
800 845 ];
801 846 src = fetchurl {
802 847 url = "https://files.pythonhosted.org/packages/eb/f3/67579bb486517c0d49547f9697e36582cd19dafb5df9e687ed8e22de57fa/Mako-1.0.7.tar.gz";
803 848 sha256 = "1bi5gnr8r8dva06qpyx4kgjc6spm2k1y908183nbbaylggjzs0jf";
804 849 };
805 850 meta = {
806 851 license = [ pkgs.lib.licenses.mit ];
807 852 };
808 853 };
809 854 "markdown" = super.buildPythonPackage {
810 855 name = "markdown-2.6.11";
811 856 doCheck = false;
812 857 src = fetchurl {
813 858 url = "https://files.pythonhosted.org/packages/b3/73/fc5c850f44af5889192dff783b7b0d8f3fe8d30b65c8e3f78f8f0265fecf/Markdown-2.6.11.tar.gz";
814 859 sha256 = "108g80ryzykh8bj0i7jfp71510wrcixdi771lf2asyghgyf8cmm8";
815 860 };
816 861 meta = {
817 862 license = [ pkgs.lib.licenses.bsdOriginal ];
818 863 };
819 864 };
820 865 "markupsafe" = super.buildPythonPackage {
821 name = "markupsafe-1.0";
866 name = "markupsafe-1.1.0";
822 867 doCheck = false;
823 868 src = fetchurl {
824 url = "https://files.pythonhosted.org/packages/4d/de/32d741db316d8fdb7680822dd37001ef7a448255de9699ab4bfcbdf4172b/MarkupSafe-1.0.tar.gz";
825 sha256 = "0rdn1s8x9ni7ss8rfiacj7x1085lx8mh2zdwqslnw8xc3l4nkgm6";
869 url = "https://files.pythonhosted.org/packages/ac/7e/1b4c2e05809a4414ebce0892fe1e32c14ace86ca7d50c70f00979ca9b3a3/MarkupSafe-1.1.0.tar.gz";
870 sha256 = "1lxirjypbdd3l9jl4vliilhfnhy7c7f2vlldqg1b0i74khn375sf";
826 871 };
827 872 meta = {
828 873 license = [ pkgs.lib.licenses.bsdOriginal ];
829 874 };
830 875 };
831 876 "meld3" = super.buildPythonPackage {
832 877 name = "meld3-1.0.2";
833 878 doCheck = false;
834 879 src = fetchurl {
835 880 url = "https://files.pythonhosted.org/packages/45/a0/317c6422b26c12fe0161e936fc35f36552069ba8e6f7ecbd99bbffe32a5f/meld3-1.0.2.tar.gz";
836 881 sha256 = "0n4mkwlpsqnmn0dm0wm5hn9nkda0nafl0jdy5sdl5977znh59dzp";
837 882 };
838 883 meta = {
839 884 license = [ { fullName = "BSD-derived (http://www.repoze.org/LICENSE.txt)"; } ];
840 885 };
841 886 };
842 887 "mistune" = super.buildPythonPackage {
843 888 name = "mistune-0.8.4";
844 889 doCheck = false;
845 890 src = fetchurl {
846 891 url = "https://files.pythonhosted.org/packages/2d/a4/509f6e7783ddd35482feda27bc7f72e65b5e7dc910eca4ab2164daf9c577/mistune-0.8.4.tar.gz";
847 892 sha256 = "0vkmsh0x480rni51lhyvigfdf06b9247z868pk3bal1wnnfl58sr";
848 893 };
849 894 meta = {
850 895 license = [ pkgs.lib.licenses.bsdOriginal ];
851 896 };
852 897 };
853 898 "mock" = super.buildPythonPackage {
854 899 name = "mock-1.0.1";
855 900 doCheck = false;
856 901 src = fetchurl {
857 902 url = "https://files.pythonhosted.org/packages/a2/52/7edcd94f0afb721a2d559a5b9aae8af4f8f2c79bc63fdbe8a8a6c9b23bbe/mock-1.0.1.tar.gz";
858 903 sha256 = "0kzlsbki6q0awf89rc287f3aj8x431lrajf160a70z0ikhnxsfdq";
859 904 };
860 905 meta = {
861 906 license = [ pkgs.lib.licenses.bsdOriginal ];
862 907 };
863 908 };
864 909 "more-itertools" = super.buildPythonPackage {
865 910 name = "more-itertools-4.3.0";
866 911 doCheck = false;
867 912 propagatedBuildInputs = [
868 913 self."six"
869 914 ];
870 915 src = fetchurl {
871 916 url = "https://files.pythonhosted.org/packages/88/ff/6d485d7362f39880810278bdc906c13300db05485d9c65971dec1142da6a/more-itertools-4.3.0.tar.gz";
872 917 sha256 = "17h3na0rdh8xq30w4b9pizgkdxmm51896bxw600x84jflg9vaxn4";
873 918 };
874 919 meta = {
875 920 license = [ pkgs.lib.licenses.mit ];
876 921 };
877 922 };
878 923 "msgpack-python" = super.buildPythonPackage {
879 924 name = "msgpack-python-0.5.6";
880 925 doCheck = false;
881 926 src = fetchurl {
882 927 url = "https://files.pythonhosted.org/packages/8a/20/6eca772d1a5830336f84aca1d8198e5a3f4715cd1c7fc36d3cc7f7185091/msgpack-python-0.5.6.tar.gz";
883 928 sha256 = "16wh8qgybmfh4pjp8vfv78mdlkxfmcasg78lzlnm6nslsfkci31p";
884 929 };
885 930 meta = {
886 931 license = [ pkgs.lib.licenses.asl20 ];
887 932 };
888 933 };
889 934 "mysql-python" = super.buildPythonPackage {
890 935 name = "mysql-python-1.2.5";
891 936 doCheck = false;
892 937 src = fetchurl {
893 938 url = "https://files.pythonhosted.org/packages/a5/e9/51b544da85a36a68debe7a7091f068d802fc515a3a202652828c73453cad/MySQL-python-1.2.5.zip";
894 939 sha256 = "0x0c2jg0bb3pp84njaqiic050qkyd7ymwhfvhipnimg58yv40441";
895 940 };
896 941 meta = {
897 942 license = [ pkgs.lib.licenses.gpl1 ];
898 943 };
899 944 };
900 945 "nbconvert" = super.buildPythonPackage {
901 946 name = "nbconvert-5.3.1";
902 947 doCheck = false;
903 948 propagatedBuildInputs = [
904 949 self."mistune"
905 950 self."jinja2"
906 951 self."pygments"
907 952 self."traitlets"
908 953 self."jupyter-core"
909 954 self."nbformat"
910 955 self."entrypoints"
911 956 self."bleach"
912 957 self."pandocfilters"
913 958 self."testpath"
914 959 ];
915 960 src = fetchurl {
916 961 url = "https://files.pythonhosted.org/packages/b9/a4/d0a0938ad6f5eeb4dea4e73d255c617ef94b0b2849d51194c9bbdb838412/nbconvert-5.3.1.tar.gz";
917 962 sha256 = "1f9dkvpx186xjm4xab0qbph588mncp4vqk3fmxrsnqs43mks9c8j";
918 963 };
919 964 meta = {
920 965 license = [ pkgs.lib.licenses.bsdOriginal ];
921 966 };
922 967 };
923 968 "nbformat" = super.buildPythonPackage {
924 969 name = "nbformat-4.4.0";
925 970 doCheck = false;
926 971 propagatedBuildInputs = [
927 972 self."ipython-genutils"
928 973 self."traitlets"
929 974 self."jsonschema"
930 975 self."jupyter-core"
931 976 ];
932 977 src = fetchurl {
933 978 url = "https://files.pythonhosted.org/packages/6e/0e/160754f7ae3e984863f585a3743b0ed1702043a81245907c8fae2d537155/nbformat-4.4.0.tar.gz";
934 979 sha256 = "00nlf08h8yc4q73nphfvfhxrcnilaqanb8z0mdy6nxk0vzq4wjgp";
935 980 };
936 981 meta = {
937 982 license = [ pkgs.lib.licenses.bsdOriginal ];
938 983 };
939 984 };
940 985 "packaging" = super.buildPythonPackage {
941 986 name = "packaging-15.2";
942 987 doCheck = false;
943 988 src = fetchurl {
944 989 url = "https://files.pythonhosted.org/packages/24/c4/185da1304f07047dc9e0c46c31db75c0351bd73458ac3efad7da3dbcfbe1/packaging-15.2.tar.gz";
945 990 sha256 = "1zn60w84bxvw6wypffka18ca66pa1k2cfrq3cq8fnsfja5m3k4ng";
946 991 };
947 992 meta = {
948 993 license = [ pkgs.lib.licenses.asl20 ];
949 994 };
950 995 };
951 996 "pandocfilters" = super.buildPythonPackage {
952 997 name = "pandocfilters-1.4.2";
953 998 doCheck = false;
954 999 src = fetchurl {
955 1000 url = "https://files.pythonhosted.org/packages/4c/ea/236e2584af67bb6df960832731a6e5325fd4441de001767da328c33368ce/pandocfilters-1.4.2.tar.gz";
956 1001 sha256 = "1a8d9b7s48gmq9zj0pmbyv2sivn5i7m6mybgpkk4jm5vd7hp1pdk";
957 1002 };
958 1003 meta = {
959 1004 license = [ pkgs.lib.licenses.bsdOriginal ];
960 1005 };
961 1006 };
962 1007 "paste" = super.buildPythonPackage {
963 1008 name = "paste-3.0.5";
964 1009 doCheck = false;
965 1010 propagatedBuildInputs = [
966 1011 self."six"
967 1012 ];
968 1013 src = fetchurl {
969 1014 url = "https://files.pythonhosted.org/packages/d4/41/91bde422400786b1b06357c1e6e3a5379f54dc3002aeb337cb767233304e/Paste-3.0.5.tar.gz";
970 1015 sha256 = "1a6i8fh1fg8r4x800fvy9r82m15clwjim6yf2g9r4dff0y40dchv";
971 1016 };
972 1017 meta = {
973 1018 license = [ pkgs.lib.licenses.mit ];
974 1019 };
975 1020 };
976 1021 "pastedeploy" = super.buildPythonPackage {
977 1022 name = "pastedeploy-2.0.1";
978 1023 doCheck = false;
979 1024 src = fetchurl {
980 1025 url = "https://files.pythonhosted.org/packages/19/a0/5623701df7e2478a68a1b685d1a84518024eef994cde7e4da8449a31616f/PasteDeploy-2.0.1.tar.gz";
981 1026 sha256 = "02imfbbx1mi2h546f3sr37m47dk9qizaqhzzlhx8bkzxa6fzn8yl";
982 1027 };
983 1028 meta = {
984 1029 license = [ pkgs.lib.licenses.mit ];
985 1030 };
986 1031 };
987 1032 "pastescript" = super.buildPythonPackage {
988 1033 name = "pastescript-3.0.0";
989 1034 doCheck = false;
990 1035 propagatedBuildInputs = [
991 1036 self."paste"
992 1037 self."pastedeploy"
993 1038 self."six"
994 1039 ];
995 1040 src = fetchurl {
996 1041 url = "https://files.pythonhosted.org/packages/08/2a/3797377a884ab9a064ad4d564ed612e54d26d7997caa8229c9c9df4eac31/PasteScript-3.0.0.tar.gz";
997 1042 sha256 = "1hvmyz1sbn7ws1syw567ph7km9fi0wi75r3vlyzx6sk0z26xkm6r";
998 1043 };
999 1044 meta = {
1000 1045 license = [ pkgs.lib.licenses.mit ];
1001 1046 };
1002 1047 };
1003 1048 "pathlib2" = super.buildPythonPackage {
1004 1049 name = "pathlib2-2.3.3";
1005 1050 doCheck = false;
1006 1051 propagatedBuildInputs = [
1007 1052 self."six"
1008 1053 self."scandir"
1009 1054 ];
1010 1055 src = fetchurl {
1011 1056 url = "https://files.pythonhosted.org/packages/bf/d7/a2568f4596b75d2c6e2b4094a7e64f620decc7887f69a1f2811931ea15b9/pathlib2-2.3.3.tar.gz";
1012 1057 sha256 = "0hpp92vqqgcd8h92msm9slv161b1q160igjwnkf2ag6cx0c96695";
1013 1058 };
1014 1059 meta = {
1015 1060 license = [ pkgs.lib.licenses.mit ];
1016 1061 };
1017 1062 };
1018 1063 "peppercorn" = super.buildPythonPackage {
1019 1064 name = "peppercorn-0.6";
1020 1065 doCheck = false;
1021 1066 src = fetchurl {
1022 1067 url = "https://files.pythonhosted.org/packages/e4/77/93085de7108cdf1a0b092ff443872a8f9442c736d7ddebdf2f27627935f4/peppercorn-0.6.tar.gz";
1023 1068 sha256 = "1ip4bfwcpwkq9hz2dai14k2cyabvwrnvcvrcmzxmqm04g8fnimwn";
1024 1069 };
1025 1070 meta = {
1026 1071 license = [ { fullName = "BSD-derived (http://www.repoze.org/LICENSE.txt)"; } ];
1027 1072 };
1028 1073 };
1029 1074 "pexpect" = super.buildPythonPackage {
1030 1075 name = "pexpect-4.6.0";
1031 1076 doCheck = false;
1032 1077 propagatedBuildInputs = [
1033 1078 self."ptyprocess"
1034 1079 ];
1035 1080 src = fetchurl {
1036 1081 url = "https://files.pythonhosted.org/packages/89/43/07d07654ee3e25235d8cea4164cdee0ec39d1fda8e9203156ebe403ffda4/pexpect-4.6.0.tar.gz";
1037 1082 sha256 = "1fla85g47iaxxpjhp9vkxdnv4pgc7rplfy6ja491smrrk0jqi3ia";
1038 1083 };
1039 1084 meta = {
1040 1085 license = [ pkgs.lib.licenses.isc { fullName = "ISC License (ISCL)"; } ];
1041 1086 };
1042 1087 };
1043 1088 "pickleshare" = super.buildPythonPackage {
1044 1089 name = "pickleshare-0.7.5";
1045 1090 doCheck = false;
1046 1091 propagatedBuildInputs = [
1047 1092 self."pathlib2"
1048 1093 ];
1049 1094 src = fetchurl {
1050 1095 url = "https://files.pythonhosted.org/packages/d8/b6/df3c1c9b616e9c0edbc4fbab6ddd09df9535849c64ba51fcb6531c32d4d8/pickleshare-0.7.5.tar.gz";
1051 1096 sha256 = "1jmghg3c53yp1i8cm6pcrm280ayi8621rwyav9fac7awjr3kss47";
1052 1097 };
1053 1098 meta = {
1054 1099 license = [ pkgs.lib.licenses.mit ];
1055 1100 };
1056 1101 };
1057 1102 "plaster" = super.buildPythonPackage {
1058 1103 name = "plaster-1.0";
1059 1104 doCheck = false;
1060 1105 propagatedBuildInputs = [
1061 1106 self."setuptools"
1062 1107 ];
1063 1108 src = fetchurl {
1064 1109 url = "https://files.pythonhosted.org/packages/37/e1/56d04382d718d32751017d32f351214384e529b794084eee20bb52405563/plaster-1.0.tar.gz";
1065 1110 sha256 = "1hy8k0nv2mxq94y5aysk6hjk9ryb4bsd13g83m60hcyzxz3wflc3";
1066 1111 };
1067 1112 meta = {
1068 1113 license = [ pkgs.lib.licenses.mit ];
1069 1114 };
1070 1115 };
1071 1116 "plaster-pastedeploy" = super.buildPythonPackage {
1072 1117 name = "plaster-pastedeploy-0.6";
1073 1118 doCheck = false;
1074 1119 propagatedBuildInputs = [
1075 1120 self."pastedeploy"
1076 1121 self."plaster"
1077 1122 ];
1078 1123 src = fetchurl {
1079 1124 url = "https://files.pythonhosted.org/packages/3f/e7/6a6833158d2038ec40085433308a1e164fd1dac595513f6dd556d5669bb8/plaster_pastedeploy-0.6.tar.gz";
1080 1125 sha256 = "1bkggk18f4z2bmsmxyxabvf62znvjwbivzh880419r3ap0616cf2";
1081 1126 };
1082 1127 meta = {
1083 1128 license = [ pkgs.lib.licenses.mit ];
1084 1129 };
1085 1130 };
1086 1131 "pluggy" = super.buildPythonPackage {
1087 1132 name = "pluggy-0.8.0";
1088 1133 doCheck = false;
1089 1134 src = fetchurl {
1090 1135 url = "https://files.pythonhosted.org/packages/65/25/81d0de17cd00f8ca994a4e74e3c4baf7cd25072c0b831dad5c7d9d6138f8/pluggy-0.8.0.tar.gz";
1091 1136 sha256 = "1580p47l2zqzsza8jcnw1h2wh3vvmygk6ly8bvi4w0g8j14sjys4";
1092 1137 };
1093 1138 meta = {
1094 1139 license = [ pkgs.lib.licenses.mit ];
1095 1140 };
1096 1141 };
1097 1142 "prompt-toolkit" = super.buildPythonPackage {
1098 1143 name = "prompt-toolkit-1.0.15";
1099 1144 doCheck = false;
1100 1145 propagatedBuildInputs = [
1101 1146 self."six"
1102 1147 self."wcwidth"
1103 1148 ];
1104 1149 src = fetchurl {
1105 1150 url = "https://files.pythonhosted.org/packages/8a/ad/cf6b128866e78ad6d7f1dc5b7f99885fb813393d9860778b2984582e81b5/prompt_toolkit-1.0.15.tar.gz";
1106 1151 sha256 = "05v9h5nydljwpj5nm8n804ms0glajwfy1zagrzqrg91wk3qqi1c5";
1107 1152 };
1108 1153 meta = {
1109 1154 license = [ pkgs.lib.licenses.bsdOriginal ];
1110 1155 };
1111 1156 };
1112 1157 "psutil" = super.buildPythonPackage {
1113 1158 name = "psutil-5.4.7";
1114 1159 doCheck = false;
1115 1160 src = fetchurl {
1116 1161 url = "https://files.pythonhosted.org/packages/7d/9a/1e93d41708f8ed2b564395edfa3389f0fd6d567597401c2e5e2775118d8b/psutil-5.4.7.tar.gz";
1117 1162 sha256 = "0fsgmvzwbdbszkwfnqhib8jcxm4w6zyhvlxlcda0rfm5cyqj4qsv";
1118 1163 };
1119 1164 meta = {
1120 1165 license = [ pkgs.lib.licenses.bsdOriginal ];
1121 1166 };
1122 1167 };
1123 1168 "psycopg2" = super.buildPythonPackage {
1124 1169 name = "psycopg2-2.7.5";
1125 1170 doCheck = false;
1126 1171 src = fetchurl {
1127 1172 url = "https://files.pythonhosted.org/packages/b2/c1/7bf6c464e903ffc4f3f5907c389e5a4199666bf57f6cd6bf46c17912a1f9/psycopg2-2.7.5.tar.gz";
1128 1173 sha256 = "17klx964gw8z0znl0raz3by8vdc7cq5gxj4pdcrfcina84nrdkzc";
1129 1174 };
1130 1175 meta = {
1131 1176 license = [ pkgs.lib.licenses.zpl21 { fullName = "GNU Library or Lesser General Public License (LGPL)"; } { fullName = "LGPL with exceptions or ZPL"; } ];
1132 1177 };
1133 1178 };
1134 1179 "ptyprocess" = super.buildPythonPackage {
1135 1180 name = "ptyprocess-0.6.0";
1136 1181 doCheck = false;
1137 1182 src = fetchurl {
1138 1183 url = "https://files.pythonhosted.org/packages/7d/2d/e4b8733cf79b7309d84c9081a4ab558c89d8c89da5961bf4ddb050ca1ce0/ptyprocess-0.6.0.tar.gz";
1139 1184 sha256 = "1h4lcd3w5nrxnsk436ar7fwkiy5rfn5wj2xwy9l0r4mdqnf2jgwj";
1140 1185 };
1141 1186 meta = {
1142 1187 license = [ ];
1143 1188 };
1144 1189 };
1145 1190 "py" = super.buildPythonPackage {
1146 1191 name = "py-1.6.0";
1147 1192 doCheck = false;
1148 1193 src = fetchurl {
1149 1194 url = "https://files.pythonhosted.org/packages/4f/38/5f427d1eedae73063ce4da680d2bae72014995f9fdeaa57809df61c968cd/py-1.6.0.tar.gz";
1150 1195 sha256 = "1wcs3zv9wl5m5x7p16avqj2gsrviyb23yvc3pr330isqs0sh98q6";
1151 1196 };
1152 1197 meta = {
1153 1198 license = [ pkgs.lib.licenses.mit ];
1154 1199 };
1155 1200 };
1156 1201 "py-bcrypt" = super.buildPythonPackage {
1157 1202 name = "py-bcrypt-0.4";
1158 1203 doCheck = false;
1159 1204 src = fetchurl {
1160 1205 url = "https://files.pythonhosted.org/packages/68/b1/1c3068c5c4d2e35c48b38dcc865301ebfdf45f54507086ac65ced1fd3b3d/py-bcrypt-0.4.tar.gz";
1161 1206 sha256 = "0y6smdggwi5s72v6p1nn53dg6w05hna3d264cq6kas0lap73p8az";
1162 1207 };
1163 1208 meta = {
1164 1209 license = [ pkgs.lib.licenses.bsdOriginal ];
1165 1210 };
1166 1211 };
1167 1212 "py-gfm" = super.buildPythonPackage {
1168 1213 name = "py-gfm-0.1.4";
1169 1214 doCheck = false;
1170 1215 propagatedBuildInputs = [
1171 1216 self."setuptools"
1172 1217 self."markdown"
1173 1218 ];
1174 1219 src = fetchurl {
1175 1220 url = "https://files.pythonhosted.org/packages/06/ee/004a03a1d92bb386dae44f6dd087db541bc5093374f1637d4d4ae5596cc2/py-gfm-0.1.4.tar.gz";
1176 1221 sha256 = "0zip06g2isivx8fzgqd4n9qzsa22c25jas1rsb7m2rnjg72m0rzg";
1177 1222 };
1178 1223 meta = {
1179 1224 license = [ pkgs.lib.licenses.bsdOriginal ];
1180 1225 };
1181 1226 };
1182 1227 "pyasn1" = super.buildPythonPackage {
1183 1228 name = "pyasn1-0.4.4";
1184 1229 doCheck = false;
1185 1230 src = fetchurl {
1186 1231 url = "https://files.pythonhosted.org/packages/10/46/059775dc8e50f722d205452bced4b3cc965d27e8c3389156acd3b1123ae3/pyasn1-0.4.4.tar.gz";
1187 1232 sha256 = "0drilmx5j25aplfr5wrml0030cs5fgxp9yp94fhllxgx28yjm3zm";
1188 1233 };
1189 1234 meta = {
1190 1235 license = [ pkgs.lib.licenses.bsdOriginal ];
1191 1236 };
1192 1237 };
1193 1238 "pyasn1-modules" = super.buildPythonPackage {
1194 1239 name = "pyasn1-modules-0.2.2";
1195 1240 doCheck = false;
1196 1241 propagatedBuildInputs = [
1197 1242 self."pyasn1"
1198 1243 ];
1199 1244 src = fetchurl {
1200 1245 url = "https://files.pythonhosted.org/packages/37/33/74ebdc52be534e683dc91faf263931bc00ae05c6073909fde53999088541/pyasn1-modules-0.2.2.tar.gz";
1201 1246 sha256 = "0ivm850yi7ajjbi8j115qpsj95bgxdsx48nbjzg0zip788c3xkx0";
1202 1247 };
1203 1248 meta = {
1204 1249 license = [ pkgs.lib.licenses.bsdOriginal ];
1205 1250 };
1206 1251 };
1207 1252 "pycrypto" = super.buildPythonPackage {
1208 1253 name = "pycrypto-2.6.1";
1209 1254 doCheck = false;
1210 1255 src = fetchurl {
1211 1256 url = "https://files.pythonhosted.org/packages/60/db/645aa9af249f059cc3a368b118de33889219e0362141e75d4eaf6f80f163/pycrypto-2.6.1.tar.gz";
1212 1257 sha256 = "0g0ayql5b9mkjam8hym6zyg6bv77lbh66rv1fyvgqb17kfc1xkpj";
1213 1258 };
1214 1259 meta = {
1215 1260 license = [ pkgs.lib.licenses.publicDomain ];
1216 1261 };
1217 1262 };
1218 1263 "pycurl" = super.buildPythonPackage {
1219 1264 name = "pycurl-7.43.0.2";
1220 1265 doCheck = false;
1221 1266 src = fetchurl {
1222 1267 url = "https://files.pythonhosted.org/packages/e8/e4/0dbb8735407189f00b33d84122b9be52c790c7c3b25286826f4e1bdb7bde/pycurl-7.43.0.2.tar.gz";
1223 1268 sha256 = "1915kb04k1j4y6k1dx1sgnbddxrl9r1n4q928if2lkrdm73xy30g";
1224 1269 };
1225 1270 meta = {
1226 1271 license = [ pkgs.lib.licenses.mit { fullName = "LGPL/MIT"; } { fullName = "GNU Library or Lesser General Public License (LGPL)"; } ];
1227 1272 };
1228 1273 };
1229 1274 "pyflakes" = super.buildPythonPackage {
1230 1275 name = "pyflakes-0.8.1";
1231 1276 doCheck = false;
1232 1277 src = fetchurl {
1233 1278 url = "https://files.pythonhosted.org/packages/75/22/a90ec0252f4f87f3ffb6336504de71fe16a49d69c4538dae2f12b9360a38/pyflakes-0.8.1.tar.gz";
1234 1279 sha256 = "0sbpq6pqm1i9wqi41mlfrsc5rk92jv4mskvlyxmnhlbdnc80ma1z";
1235 1280 };
1236 1281 meta = {
1237 1282 license = [ pkgs.lib.licenses.mit ];
1238 1283 };
1239 1284 };
1240 1285 "pygments" = super.buildPythonPackage {
1241 1286 name = "pygments-2.3.0";
1242 1287 doCheck = false;
1243 1288 src = fetchurl {
1244 1289 url = "https://files.pythonhosted.org/packages/63/a2/91c31c4831853dedca2a08a0f94d788fc26a48f7281c99a303769ad2721b/Pygments-2.3.0.tar.gz";
1245 1290 sha256 = "1z34ms51dh4jq4h3cizp7vd1dmsxcbvffkjsd2xxfav22nn6lrl2";
1246 1291 };
1247 1292 meta = {
1248 1293 license = [ pkgs.lib.licenses.bsdOriginal ];
1249 1294 };
1250 1295 };
1251 1296 "pymysql" = super.buildPythonPackage {
1252 1297 name = "pymysql-0.8.1";
1253 1298 doCheck = false;
1254 1299 src = fetchurl {
1255 1300 url = "https://files.pythonhosted.org/packages/44/39/6bcb83cae0095a31b6be4511707fdf2009d3e29903a55a0494d3a9a2fac0/PyMySQL-0.8.1.tar.gz";
1256 1301 sha256 = "0a96crz55bw4h6myh833skrli7b0ck89m3x673y2z2ryy7zrpq9l";
1257 1302 };
1258 1303 meta = {
1259 1304 license = [ pkgs.lib.licenses.mit ];
1260 1305 };
1261 1306 };
1262 1307 "pyotp" = super.buildPythonPackage {
1263 1308 name = "pyotp-2.2.7";
1264 1309 doCheck = false;
1265 1310 src = fetchurl {
1266 1311 url = "https://files.pythonhosted.org/packages/b1/ab/477cda97b6ca7baced5106471cb1ac1fe698d1b035983b9f8ee3422989eb/pyotp-2.2.7.tar.gz";
1267 1312 sha256 = "00p69nw431f0s2ilg0hnd77p1l22m06p9rq4f8zfapmavnmzw3xy";
1268 1313 };
1269 1314 meta = {
1270 1315 license = [ pkgs.lib.licenses.mit ];
1271 1316 };
1272 1317 };
1273 1318 "pyparsing" = super.buildPythonPackage {
1274 name = "pyparsing-1.5.7";
1319 name = "pyparsing-2.3.0";
1275 1320 doCheck = false;
1276 1321 src = fetchurl {
1277 url = "https://files.pythonhosted.org/packages/6f/2c/47457771c02a8ff0f302b695e094ec309e30452232bd79198ee94fda689f/pyparsing-1.5.7.tar.gz";
1278 sha256 = "17z7ws076z977sclj628fvwrp8y9j2rvdjcsq42v129n1gwi8vk4";
1322 url = "https://files.pythonhosted.org/packages/d0/09/3e6a5eeb6e04467b737d55f8bba15247ac0876f98fae659e58cd744430c6/pyparsing-2.3.0.tar.gz";
1323 sha256 = "14k5v7n3xqw8kzf42x06bzp184spnlkya2dpjyflax6l3yrallzk";
1279 1324 };
1280 1325 meta = {
1281 1326 license = [ pkgs.lib.licenses.mit ];
1282 1327 };
1283 1328 };
1284 1329 "pyramid" = super.buildPythonPackage {
1285 1330 name = "pyramid-1.10.1";
1286 1331 doCheck = false;
1287 1332 propagatedBuildInputs = [
1288 1333 self."hupper"
1289 1334 self."plaster"
1290 1335 self."plaster-pastedeploy"
1291 1336 self."setuptools"
1292 1337 self."translationstring"
1293 1338 self."venusian"
1294 1339 self."webob"
1295 1340 self."zope.deprecation"
1296 1341 self."zope.interface"
1297 1342 self."repoze.lru"
1298 1343 ];
1299 1344 src = fetchurl {
1300 1345 url = "https://files.pythonhosted.org/packages/0a/3e/22e3ac9be1b70a01139adba8906ee4b8f628bb469fea3c52f6c97b73063c/pyramid-1.10.1.tar.gz";
1301 1346 sha256 = "1h5105nfh6rsrfjiyw20aavyibj36la3hajy6vh1fa77xb4y3hrp";
1302 1347 };
1303 1348 meta = {
1304 1349 license = [ { fullName = "Repoze Public License"; } { fullName = "BSD-derived (http://www.repoze.org/LICENSE.txt)"; } ];
1305 1350 };
1306 1351 };
1307 1352 "pyramid-beaker" = super.buildPythonPackage {
1308 1353 name = "pyramid-beaker-0.8";
1309 1354 doCheck = false;
1310 1355 propagatedBuildInputs = [
1311 1356 self."pyramid"
1312 1357 self."beaker"
1313 1358 ];
1314 1359 src = fetchurl {
1315 1360 url = "https://files.pythonhosted.org/packages/d9/6e/b85426e00fd3d57f4545f74e1c3828552d8700f13ededeef9233f7bca8be/pyramid_beaker-0.8.tar.gz";
1316 1361 sha256 = "0hflx3qkcdml1mwpq53sz46s7jickpfn0zy0ns2c7j445j66bp3p";
1317 1362 };
1318 1363 meta = {
1319 1364 license = [ { fullName = "BSD-derived (http://www.repoze.org/LICENSE.txt)"; } ];
1320 1365 };
1321 1366 };
1322 1367 "pyramid-debugtoolbar" = super.buildPythonPackage {
1323 1368 name = "pyramid-debugtoolbar-4.4";
1324 1369 doCheck = false;
1325 1370 propagatedBuildInputs = [
1326 1371 self."pyramid"
1327 1372 self."pyramid-mako"
1328 1373 self."repoze.lru"
1329 1374 self."pygments"
1330 1375 self."ipaddress"
1331 1376 ];
1332 1377 src = fetchurl {
1333 1378 url = "https://files.pythonhosted.org/packages/00/6f/c04eb4e715a7a5a4b24079ab7ffd1dceb1f70b2e24fc17686a2922dbac0a/pyramid_debugtoolbar-4.4.tar.gz";
1334 1379 sha256 = "17p7nxvapvy2hab1rah3ndq2kbs4v83pixj8x2n4m7008ai9lxsz";
1335 1380 };
1336 1381 meta = {
1337 1382 license = [ { fullName = "Repoze Public License"; } pkgs.lib.licenses.bsdOriginal ];
1338 1383 };
1339 1384 };
1340 1385 "pyramid-jinja2" = super.buildPythonPackage {
1341 1386 name = "pyramid-jinja2-2.7";
1342 1387 doCheck = false;
1343 1388 propagatedBuildInputs = [
1344 1389 self."pyramid"
1345 1390 self."zope.deprecation"
1346 1391 self."jinja2"
1347 1392 self."markupsafe"
1348 1393 ];
1349 1394 src = fetchurl {
1350 1395 url = "https://files.pythonhosted.org/packages/d8/80/d60a7233823de22ce77bd864a8a83736a1fe8b49884b08303a2e68b2c853/pyramid_jinja2-2.7.tar.gz";
1351 1396 sha256 = "1sz5s0pp5jqhf4w22w9527yz8hgdi4mhr6apd6vw1gm5clghh8aw";
1352 1397 };
1353 1398 meta = {
1354 1399 license = [ { fullName = "Repoze Public License"; } { fullName = "BSD-derived (http://www.repoze.org/LICENSE.txt)"; } ];
1355 1400 };
1356 1401 };
1357 1402 "pyramid-mailer" = super.buildPythonPackage {
1358 1403 name = "pyramid-mailer-0.15.1";
1359 1404 doCheck = false;
1360 1405 propagatedBuildInputs = [
1361 1406 self."pyramid"
1362 1407 self."repoze.sendmail"
1363 1408 self."transaction"
1364 1409 ];
1365 1410 src = fetchurl {
1366 1411 url = "https://files.pythonhosted.org/packages/a0/f2/6febf5459dff4d7e653314d575469ad2e11b9d2af2c3606360e1c67202f2/pyramid_mailer-0.15.1.tar.gz";
1367 1412 sha256 = "16vg8jb203jgb7b0hd6wllfqvp542qh2ry1gjai2m6qpv5agy2pc";
1368 1413 };
1369 1414 meta = {
1370 1415 license = [ pkgs.lib.licenses.bsdOriginal ];
1371 1416 };
1372 1417 };
1373 1418 "pyramid-mako" = super.buildPythonPackage {
1374 1419 name = "pyramid-mako-1.0.2";
1375 1420 doCheck = false;
1376 1421 propagatedBuildInputs = [
1377 1422 self."pyramid"
1378 1423 self."mako"
1379 1424 ];
1380 1425 src = fetchurl {
1381 1426 url = "https://files.pythonhosted.org/packages/f1/92/7e69bcf09676d286a71cb3bbb887b16595b96f9ba7adbdc239ffdd4b1eb9/pyramid_mako-1.0.2.tar.gz";
1382 1427 sha256 = "18gk2vliq8z4acblsl6yzgbvnr9rlxjlcqir47km7kvlk1xri83d";
1383 1428 };
1384 1429 meta = {
1385 1430 license = [ { fullName = "Repoze Public License"; } { fullName = "BSD-derived (http://www.repoze.org/LICENSE.txt)"; } ];
1386 1431 };
1387 1432 };
1388 1433 "pysqlite" = super.buildPythonPackage {
1389 1434 name = "pysqlite-2.8.3";
1390 1435 doCheck = false;
1391 1436 src = fetchurl {
1392 1437 url = "https://files.pythonhosted.org/packages/42/02/981b6703e3c83c5b25a829c6e77aad059f9481b0bbacb47e6e8ca12bd731/pysqlite-2.8.3.tar.gz";
1393 1438 sha256 = "1424gwq9sil2ffmnizk60q36vydkv8rxs6m7xs987kz8cdc37lqp";
1394 1439 };
1395 1440 meta = {
1396 1441 license = [ { fullName = "zlib/libpng License"; } { fullName = "zlib/libpng license"; } ];
1397 1442 };
1398 1443 };
1399 1444 "pytest" = super.buildPythonPackage {
1400 1445 name = "pytest-3.8.2";
1401 1446 doCheck = false;
1402 1447 propagatedBuildInputs = [
1403 1448 self."py"
1404 1449 self."six"
1405 1450 self."setuptools"
1406 1451 self."attrs"
1407 1452 self."more-itertools"
1408 1453 self."atomicwrites"
1409 1454 self."pluggy"
1410 1455 self."funcsigs"
1411 1456 self."pathlib2"
1412 1457 ];
1413 1458 src = fetchurl {
1414 1459 url = "https://files.pythonhosted.org/packages/5f/d2/7f77f406ac505abda02ab4afb50d06ebf304f6ea42fca34f8f37529106b2/pytest-3.8.2.tar.gz";
1415 1460 sha256 = "18nrwzn61kph2y6gxwfz9ms68rfvr9d4vcffsxng9p7jk9z18clk";
1416 1461 };
1417 1462 meta = {
1418 1463 license = [ pkgs.lib.licenses.mit ];
1419 1464 };
1420 1465 };
1421 1466 "pytest-cov" = super.buildPythonPackage {
1422 1467 name = "pytest-cov-2.6.0";
1423 1468 doCheck = false;
1424 1469 propagatedBuildInputs = [
1425 1470 self."pytest"
1426 1471 self."coverage"
1427 1472 ];
1428 1473 src = fetchurl {
1429 1474 url = "https://files.pythonhosted.org/packages/d9/e2/58f90a316fbd94dd50bf5c826a23f3f5d079fb3cc448c1e9f0e3c33a3d2a/pytest-cov-2.6.0.tar.gz";
1430 1475 sha256 = "0qnpp9y3ygx4jk4pf5ad71fh2skbvnr6gl54m7rg5qysnx4g0q73";
1431 1476 };
1432 1477 meta = {
1433 1478 license = [ pkgs.lib.licenses.bsdOriginal pkgs.lib.licenses.mit ];
1434 1479 };
1435 1480 };
1436 1481 "pytest-profiling" = super.buildPythonPackage {
1437 1482 name = "pytest-profiling-1.3.0";
1438 1483 doCheck = false;
1439 1484 propagatedBuildInputs = [
1440 1485 self."six"
1441 1486 self."pytest"
1442 1487 self."gprof2dot"
1443 1488 ];
1444 1489 src = fetchurl {
1445 1490 url = "https://files.pythonhosted.org/packages/f5/34/4626126e041a51ef50a80d0619519b18d20aef249aac25b0d0fdd47e57ee/pytest-profiling-1.3.0.tar.gz";
1446 1491 sha256 = "08r5afx5z22yvpmsnl91l4amsy1yxn8qsmm61mhp06mz8zjs51kb";
1447 1492 };
1448 1493 meta = {
1449 1494 license = [ pkgs.lib.licenses.mit ];
1450 1495 };
1451 1496 };
1452 1497 "pytest-runner" = super.buildPythonPackage {
1453 1498 name = "pytest-runner-4.2";
1454 1499 doCheck = false;
1455 1500 src = fetchurl {
1456 1501 url = "https://files.pythonhosted.org/packages/9e/b7/fe6e8f87f9a756fd06722216f1b6698ccba4d269eac6329d9f0c441d0f93/pytest-runner-4.2.tar.gz";
1457 1502 sha256 = "1gkpyphawxz38ni1gdq1fmwyqcg02m7ypzqvv46z06crwdxi2gyj";
1458 1503 };
1459 1504 meta = {
1460 1505 license = [ pkgs.lib.licenses.mit ];
1461 1506 };
1462 1507 };
1463 1508 "pytest-sugar" = super.buildPythonPackage {
1464 1509 name = "pytest-sugar-0.9.1";
1465 1510 doCheck = false;
1466 1511 propagatedBuildInputs = [
1467 1512 self."pytest"
1468 1513 self."termcolor"
1469 1514 ];
1470 1515 src = fetchurl {
1471 1516 url = "https://files.pythonhosted.org/packages/3e/6a/a3f909083079d03bde11d06ab23088886bbe25f2c97fbe4bb865e2bf05bc/pytest-sugar-0.9.1.tar.gz";
1472 1517 sha256 = "0b4av40dv30727m54v211r0nzwjp2ajkjgxix6j484qjmwpw935b";
1473 1518 };
1474 1519 meta = {
1475 1520 license = [ pkgs.lib.licenses.bsdOriginal ];
1476 1521 };
1477 1522 };
1478 1523 "pytest-timeout" = super.buildPythonPackage {
1479 1524 name = "pytest-timeout-1.3.2";
1480 1525 doCheck = false;
1481 1526 propagatedBuildInputs = [
1482 1527 self."pytest"
1483 1528 ];
1484 1529 src = fetchurl {
1485 1530 url = "https://files.pythonhosted.org/packages/8c/3e/1b6a319d12ae7baa3acb7c18ff2c8630a09471a0319d43535c683b4d03eb/pytest-timeout-1.3.2.tar.gz";
1486 1531 sha256 = "09wnmzvnls2mnsdz7x3c3sk2zdp6jl4dryvyj5i8hqz16q2zq5qi";
1487 1532 };
1488 1533 meta = {
1489 1534 license = [ pkgs.lib.licenses.mit { fullName = "DFSG approved"; } ];
1490 1535 };
1491 1536 };
1492 1537 "python-dateutil" = super.buildPythonPackage {
1493 1538 name = "python-dateutil-2.7.5";
1494 1539 doCheck = false;
1495 1540 propagatedBuildInputs = [
1496 1541 self."six"
1497 1542 ];
1498 1543 src = fetchurl {
1499 1544 url = "https://files.pythonhosted.org/packages/0e/01/68747933e8d12263d41ce08119620d9a7e5eb72c876a3442257f74490da0/python-dateutil-2.7.5.tar.gz";
1500 1545 sha256 = "00ngwcdw36w5b37b51mdwn3qxid9zdf3kpffv2q6n9kl05y2iyc8";
1501 1546 };
1502 1547 meta = {
1503 1548 license = [ pkgs.lib.licenses.bsdOriginal pkgs.lib.licenses.asl20 { fullName = "Dual License"; } ];
1504 1549 };
1505 1550 };
1506 1551 "python-editor" = super.buildPythonPackage {
1507 1552 name = "python-editor-1.0.3";
1508 1553 doCheck = false;
1509 1554 src = fetchurl {
1510 1555 url = "https://files.pythonhosted.org/packages/65/1e/adf6e000ea5dc909aa420352d6ba37f16434c8a3c2fa030445411a1ed545/python-editor-1.0.3.tar.gz";
1511 1556 sha256 = "0rf5xz8vw93v7mhdcvind7fkykipzga430wkcd7wk892xsn6dh53";
1512 1557 };
1513 1558 meta = {
1514 1559 license = [ pkgs.lib.licenses.asl20 { fullName = "Apache"; } ];
1515 1560 };
1516 1561 };
1517 1562 "python-ldap" = super.buildPythonPackage {
1518 1563 name = "python-ldap-3.1.0";
1519 1564 doCheck = false;
1520 1565 propagatedBuildInputs = [
1521 1566 self."pyasn1"
1522 1567 self."pyasn1-modules"
1523 1568 ];
1524 1569 src = fetchurl {
1525 1570 url = "https://files.pythonhosted.org/packages/7f/1c/28d721dff2fcd2fef9d55b40df63a00be26ec8a11e8c6fc612ae642f9cfd/python-ldap-3.1.0.tar.gz";
1526 1571 sha256 = "1i97nwfnraylyn0myxlf3vciicrf5h6fymrcff9c00k581wmx5s1";
1527 1572 };
1528 1573 meta = {
1529 1574 license = [ pkgs.lib.licenses.psfl ];
1530 1575 };
1531 1576 };
1532 1577 "python-memcached" = super.buildPythonPackage {
1533 1578 name = "python-memcached-1.59";
1534 1579 doCheck = false;
1535 1580 propagatedBuildInputs = [
1536 1581 self."six"
1537 1582 ];
1538 1583 src = fetchurl {
1539 1584 url = "https://files.pythonhosted.org/packages/90/59/5faf6e3cd8a568dd4f737ddae4f2e54204fd8c51f90bf8df99aca6c22318/python-memcached-1.59.tar.gz";
1540 1585 sha256 = "0kvyapavbirk2x3n1jx4yb9nyigrj1s3x15nm3qhpvhkpqvqdqm2";
1541 1586 };
1542 1587 meta = {
1543 1588 license = [ pkgs.lib.licenses.psfl ];
1544 1589 };
1545 1590 };
1546 1591 "python-pam" = super.buildPythonPackage {
1547 1592 name = "python-pam-1.8.4";
1548 1593 doCheck = false;
1549 1594 src = fetchurl {
1550 1595 url = "https://files.pythonhosted.org/packages/01/16/544d01cae9f28e0292dbd092b6b8b0bf222b528f362ee768a5bed2140111/python-pam-1.8.4.tar.gz";
1551 1596 sha256 = "16whhc0vr7gxsbzvsnq65nq8fs3wwmx755cavm8kkczdkz4djmn8";
1552 1597 };
1553 1598 meta = {
1554 1599 license = [ { fullName = "License :: OSI Approved :: MIT License"; } pkgs.lib.licenses.mit ];
1555 1600 };
1556 1601 };
1557 1602 "python-saml" = super.buildPythonPackage {
1558 1603 name = "python-saml-2.4.2";
1559 1604 doCheck = false;
1560 1605 propagatedBuildInputs = [
1561 1606 self."dm.xmlsec.binding"
1562 1607 self."isodate"
1563 1608 self."defusedxml"
1564 1609 ];
1565 1610 src = fetchurl {
1566 1611 url = "https://files.pythonhosted.org/packages/79/a8/a6611017e0883102fd5e2b73c9d90691b8134e38247c04ee1531d3dc647c/python-saml-2.4.2.tar.gz";
1567 1612 sha256 = "0dls4hwvf13yg7x5yfjrghbywg8g38vn5vr0rsf70hli3ydbfm43";
1568 1613 };
1569 1614 meta = {
1570 1615 license = [ pkgs.lib.licenses.mit ];
1571 1616 };
1572 1617 };
1573 1618 "pytz" = super.buildPythonPackage {
1574 1619 name = "pytz-2018.4";
1575 1620 doCheck = false;
1576 1621 src = fetchurl {
1577 1622 url = "https://files.pythonhosted.org/packages/10/76/52efda4ef98e7544321fd8d5d512e11739c1df18b0649551aeccfb1c8376/pytz-2018.4.tar.gz";
1578 1623 sha256 = "0jgpqx3kk2rhv81j1izjxvmx8d0x7hzs1857pgqnixic5wq2ar60";
1579 1624 };
1580 1625 meta = {
1581 1626 license = [ pkgs.lib.licenses.mit ];
1582 1627 };
1583 1628 };
1584 1629 "pyzmq" = super.buildPythonPackage {
1585 1630 name = "pyzmq-14.6.0";
1586 1631 doCheck = false;
1587 1632 src = fetchurl {
1588 1633 url = "https://files.pythonhosted.org/packages/8a/3b/5463d5a9d712cd8bbdac335daece0d69f6a6792da4e3dd89956c0db4e4e6/pyzmq-14.6.0.tar.gz";
1589 1634 sha256 = "1frmbjykvhmdg64g7sn20c9fpamrsfxwci1nhhg8q7jgz5pq0ikp";
1590 1635 };
1591 1636 meta = {
1592 1637 license = [ pkgs.lib.licenses.bsdOriginal { fullName = "LGPL+BSD"; } { fullName = "GNU Library or Lesser General Public License (LGPL)"; } ];
1593 1638 };
1594 1639 };
1595 1640 "redis" = super.buildPythonPackage {
1596 1641 name = "redis-2.10.6";
1597 1642 doCheck = false;
1598 1643 src = fetchurl {
1599 1644 url = "https://files.pythonhosted.org/packages/09/8d/6d34b75326bf96d4139a2ddd8e74b80840f800a0a79f9294399e212cb9a7/redis-2.10.6.tar.gz";
1600 1645 sha256 = "03vcgklykny0g0wpvqmy8p6azi2s078317wgb2xjv5m2rs9sjb52";
1601 1646 };
1602 1647 meta = {
1603 1648 license = [ pkgs.lib.licenses.mit ];
1604 1649 };
1605 1650 };
1606 1651 "repoze.lru" = super.buildPythonPackage {
1607 1652 name = "repoze.lru-0.7";
1608 1653 doCheck = false;
1609 1654 src = fetchurl {
1610 1655 url = "https://files.pythonhosted.org/packages/12/bc/595a77c4b5e204847fdf19268314ef59c85193a9dc9f83630fc459c0fee5/repoze.lru-0.7.tar.gz";
1611 1656 sha256 = "0xzz1aw2smy8hdszrq8yhnklx6w1r1mf55061kalw3iq35gafa84";
1612 1657 };
1613 1658 meta = {
1614 1659 license = [ { fullName = "Repoze Public License"; } { fullName = "BSD-derived (http://www.repoze.org/LICENSE.txt)"; } ];
1615 1660 };
1616 1661 };
1617 1662 "repoze.sendmail" = super.buildPythonPackage {
1618 1663 name = "repoze.sendmail-4.4.1";
1619 1664 doCheck = false;
1620 1665 propagatedBuildInputs = [
1621 1666 self."setuptools"
1622 1667 self."zope.interface"
1623 1668 self."transaction"
1624 1669 ];
1625 1670 src = fetchurl {
1626 1671 url = "https://files.pythonhosted.org/packages/12/4e/8ef1fd5c42765d712427b9c391419a77bd48877886d2cbc5e9f23c8cad9b/repoze.sendmail-4.4.1.tar.gz";
1627 1672 sha256 = "096ln02jr2afk7ab9j2czxqv2ryqq7m86ah572nqplx52iws73ks";
1628 1673 };
1629 1674 meta = {
1630 1675 license = [ pkgs.lib.licenses.zpl21 ];
1631 1676 };
1632 1677 };
1633 1678 "requests" = super.buildPythonPackage {
1634 1679 name = "requests-2.9.1";
1635 1680 doCheck = false;
1636 1681 src = fetchurl {
1637 1682 url = "https://files.pythonhosted.org/packages/f9/6d/07c44fb1ebe04d069459a189e7dab9e4abfe9432adcd4477367c25332748/requests-2.9.1.tar.gz";
1638 1683 sha256 = "0zsqrzlybf25xscgi7ja4s48y2abf9wvjkn47wh984qgs1fq2xy5";
1639 1684 };
1640 1685 meta = {
1641 1686 license = [ pkgs.lib.licenses.asl20 ];
1642 1687 };
1643 1688 };
1644 1689 "rhodecode-enterprise-ce" = super.buildPythonPackage {
1645 name = "rhodecode-enterprise-ce-4.15.0";
1690 name = "rhodecode-enterprise-ce-4.16.0";
1646 1691 buildInputs = [
1647 1692 self."pytest"
1648 1693 self."py"
1649 1694 self."pytest-cov"
1650 1695 self."pytest-sugar"
1651 1696 self."pytest-runner"
1652 1697 self."pytest-profiling"
1653 1698 self."pytest-timeout"
1654 1699 self."gprof2dot"
1655 1700 self."mock"
1656 1701 self."cov-core"
1657 1702 self."coverage"
1658 1703 self."webtest"
1659 1704 self."configobj"
1660 1705 ];
1661 1706 doCheck = true;
1662 1707 propagatedBuildInputs = [
1663 1708 self."setuptools-scm"
1664 1709 self."amqp"
1665 1710 self."authomatic"
1666 1711 self."atomicwrites"
1667 1712 self."attrs"
1668 1713 self."babel"
1669 1714 self."beaker"
1670 1715 self."bleach"
1671 1716 self."celery"
1672 1717 self."chameleon"
1673 1718 self."channelstream"
1674 1719 self."click"
1675 1720 self."colander"
1676 1721 self."configobj"
1677 1722 self."cssselect"
1678 1723 self."decorator"
1679 1724 self."deform"
1680 1725 self."docutils"
1681 1726 self."dogpile.cache"
1682 1727 self."dogpile.core"
1683 1728 self."ecdsa"
1684 1729 self."formencode"
1685 1730 self."future"
1686 1731 self."futures"
1687 1732 self."gnureadline"
1688 1733 self."infrae.cache"
1689 1734 self."iso8601"
1690 1735 self."itsdangerous"
1691 1736 self."jinja2"
1692 1737 self."billiard"
1693 1738 self."kombu"
1694 1739 self."lxml"
1695 1740 self."mako"
1696 1741 self."markdown"
1697 1742 self."markupsafe"
1698 1743 self."msgpack-python"
1699 1744 self."pyotp"
1700 1745 self."packaging"
1701 1746 self."paste"
1702 1747 self."pastedeploy"
1703 1748 self."pastescript"
1704 1749 self."pathlib2"
1705 1750 self."peppercorn"
1706 1751 self."psutil"
1707 1752 self."py-bcrypt"
1708 1753 self."pycrypto"
1709 1754 self."pycurl"
1710 1755 self."pyflakes"
1711 1756 self."pygments"
1712 1757 self."pyparsing"
1713 1758 self."pyramid-beaker"
1714 1759 self."pyramid-debugtoolbar"
1715 1760 self."pyramid-jinja2"
1716 1761 self."pyramid-mako"
1717 1762 self."pyramid"
1718 1763 self."pyramid-mailer"
1719 1764 self."python-dateutil"
1720 1765 self."python-ldap"
1721 1766 self."python-memcached"
1722 1767 self."python-pam"
1723 1768 self."python-saml"
1724 1769 self."pytz"
1725 1770 self."tzlocal"
1726 1771 self."pyzmq"
1727 1772 self."py-gfm"
1728 1773 self."redis"
1729 1774 self."repoze.lru"
1730 1775 self."requests"
1731 1776 self."routes"
1732 1777 self."simplejson"
1733 1778 self."six"
1734 1779 self."sqlalchemy"
1735 1780 self."sshpubkeys"
1736 1781 self."subprocess32"
1737 1782 self."supervisor"
1738 1783 self."tempita"
1739 1784 self."translationstring"
1740 1785 self."urllib3"
1741 1786 self."urlobject"
1742 1787 self."venusian"
1743 1788 self."weberror"
1744 1789 self."webhelpers2"
1745 1790 self."webhelpers"
1746 1791 self."webob"
1747 1792 self."whoosh"
1748 1793 self."wsgiref"
1749 1794 self."zope.cachedescriptors"
1750 1795 self."zope.deprecation"
1751 1796 self."zope.event"
1752 1797 self."zope.interface"
1753 1798 self."mysql-python"
1754 1799 self."pymysql"
1755 1800 self."pysqlite"
1756 1801 self."psycopg2"
1757 1802 self."nbconvert"
1758 1803 self."nbformat"
1759 1804 self."jupyter-client"
1760 1805 self."alembic"
1761 1806 self."invoke"
1762 1807 self."bumpversion"
1763 1808 self."gevent"
1764 1809 self."greenlet"
1765 1810 self."gunicorn"
1766 1811 self."waitress"
1767 1812 self."setproctitle"
1768 1813 self."ipdb"
1769 1814 self."ipython"
1770 1815 self."rhodecode-tools"
1771 1816 self."appenlight-client"
1772 1817 self."pytest"
1773 1818 self."py"
1774 1819 self."pytest-cov"
1775 1820 self."pytest-sugar"
1776 1821 self."pytest-runner"
1777 1822 self."pytest-profiling"
1778 1823 self."pytest-timeout"
1779 1824 self."gprof2dot"
1780 1825 self."mock"
1781 1826 self."cov-core"
1782 1827 self."coverage"
1783 1828 self."webtest"
1784 1829 ];
1785 1830 src = ./.;
1786 1831 meta = {
1787 1832 license = [ { fullName = "Affero GNU General Public License v3 or later (AGPLv3+)"; } { fullName = "AGPLv3, and Commercial License"; } ];
1788 1833 };
1789 1834 };
1790 1835 "rhodecode-tools" = super.buildPythonPackage {
1791 name = "rhodecode-tools-1.0.1";
1836 name = "rhodecode-tools-1.1.0";
1792 1837 doCheck = false;
1793 1838 propagatedBuildInputs = [
1794 1839 self."click"
1795 1840 self."future"
1796 1841 self."six"
1797 1842 self."mako"
1798 1843 self."markupsafe"
1799 1844 self."requests"
1800 self."elasticsearch"
1801 self."elasticsearch-dsl"
1802 1845 self."urllib3"
1803 1846 self."whoosh"
1847 self."elasticsearch"
1848 self."elasticsearch-dsl"
1849 self."elasticsearch2"
1850 self."elasticsearch1-dsl"
1804 1851 ];
1805 1852 src = fetchurl {
1806 url = "https://code.rhodecode.com/rhodecode-tools-ce/archive/v1.0.1.tar.gz?md5=ffb5d6bcb855305b93cfe23ad42e500b";
1807 sha256 = "0nr300s4sg685qs4wgbwlplwriawrwi6jq79z37frcnpyc89gpvm";
1853 url = "https://code.rhodecode.com/rhodecode-tools-ce/archive/v1.1.0.tar.gz?md5=cc320c277cb2add546220290ac9be626";
1854 sha256 = "1wbnnfrzyp0d4ys55vj5vnfrzfhwlqgdhc8yv8i6kwinizf8hfrn";
1808 1855 };
1809 1856 meta = {
1810 1857 license = [ { fullName = "Apache 2.0 and Proprietary"; } ];
1811 1858 };
1812 1859 };
1813 1860 "routes" = super.buildPythonPackage {
1814 1861 name = "routes-2.4.1";
1815 1862 doCheck = false;
1816 1863 propagatedBuildInputs = [
1817 1864 self."six"
1818 1865 self."repoze.lru"
1819 1866 ];
1820 1867 src = fetchurl {
1821 1868 url = "https://files.pythonhosted.org/packages/33/38/ea827837e68d9c7dde4cff7ec122a93c319f0effc08ce92a17095576603f/Routes-2.4.1.tar.gz";
1822 1869 sha256 = "1zamff3m0kc4vyfniyhxpkkcqv1rrgnmh37ykxv34nna1ws47vi6";
1823 1870 };
1824 1871 meta = {
1825 1872 license = [ pkgs.lib.licenses.mit ];
1826 1873 };
1827 1874 };
1828 1875 "scandir" = super.buildPythonPackage {
1829 1876 name = "scandir-1.9.0";
1830 1877 doCheck = false;
1831 1878 src = fetchurl {
1832 1879 url = "https://files.pythonhosted.org/packages/16/2a/557af1181e6b4e30254d5a6163b18f5053791ca66e251e77ab08887e8fe3/scandir-1.9.0.tar.gz";
1833 1880 sha256 = "0r3hvf1a9jm1rkqgx40gxkmccknkaiqjavs8lccgq9s8khh5x5s4";
1834 1881 };
1835 1882 meta = {
1836 1883 license = [ pkgs.lib.licenses.bsdOriginal { fullName = "New BSD License"; } ];
1837 1884 };
1838 1885 };
1839 1886 "setproctitle" = super.buildPythonPackage {
1840 1887 name = "setproctitle-1.1.10";
1841 1888 doCheck = false;
1842 1889 src = fetchurl {
1843 1890 url = "https://files.pythonhosted.org/packages/5a/0d/dc0d2234aacba6cf1a729964383e3452c52096dc695581248b548786f2b3/setproctitle-1.1.10.tar.gz";
1844 1891 sha256 = "163kplw9dcrw0lffq1bvli5yws3rngpnvrxrzdw89pbphjjvg0v2";
1845 1892 };
1846 1893 meta = {
1847 1894 license = [ pkgs.lib.licenses.bsdOriginal ];
1848 1895 };
1849 1896 };
1850 1897 "setuptools" = super.buildPythonPackage {
1851 name = "setuptools-40.6.2";
1898 name = "setuptools-40.6.3";
1852 1899 doCheck = false;
1853 1900 src = fetchurl {
1854 url = "https://files.pythonhosted.org/packages/b0/d1/8acb42f391cba52e35b131e442e80deffbb8d0676b93261d761b1f0ef8fb/setuptools-40.6.2.zip";
1855 sha256 = "0r2c5hapirlzm34h7pl1lgkm6gk7bcrlrdj28qgsvaqg3f74vfw6";
1901 url = "https://files.pythonhosted.org/packages/37/1b/b25507861991beeade31473868463dad0e58b1978c209de27384ae541b0b/setuptools-40.6.3.zip";
1902 sha256 = "1y085dnk574sxw9aymdng9gijvrsbw86hsv9hqnhv7y4d6nlsirv";
1856 1903 };
1857 1904 meta = {
1858 1905 license = [ pkgs.lib.licenses.mit ];
1859 1906 };
1860 1907 };
1861 1908 "setuptools-scm" = super.buildPythonPackage {
1862 1909 name = "setuptools-scm-2.1.0";
1863 1910 doCheck = false;
1864 1911 src = fetchurl {
1865 1912 url = "https://files.pythonhosted.org/packages/e5/62/f9e1ac314464eb5945c97542acb6bf6f3381dfa5d7a658de7730c36f31a1/setuptools_scm-2.1.0.tar.gz";
1866 1913 sha256 = "0yb364cgk15sfw3x8ln4ssh98z1dj6n8iiz4r2rw1cfsxhgi8rx7";
1867 1914 };
1868 1915 meta = {
1869 1916 license = [ pkgs.lib.licenses.mit ];
1870 1917 };
1871 1918 };
1872 1919 "simplegeneric" = super.buildPythonPackage {
1873 1920 name = "simplegeneric-0.8.1";
1874 1921 doCheck = false;
1875 1922 src = fetchurl {
1876 1923 url = "https://files.pythonhosted.org/packages/3d/57/4d9c9e3ae9a255cd4e1106bb57e24056d3d0709fc01b2e3e345898e49d5b/simplegeneric-0.8.1.zip";
1877 1924 sha256 = "0wwi1c6md4vkbcsfsf8dklf3vr4mcdj4mpxkanwgb6jb1432x5yw";
1878 1925 };
1879 1926 meta = {
1880 1927 license = [ pkgs.lib.licenses.zpl21 ];
1881 1928 };
1882 1929 };
1883 1930 "simplejson" = super.buildPythonPackage {
1884 1931 name = "simplejson-3.11.1";
1885 1932 doCheck = false;
1886 1933 src = fetchurl {
1887 1934 url = "https://files.pythonhosted.org/packages/08/48/c97b668d6da7d7bebe7ea1817a6f76394b0ec959cb04214ca833c34359df/simplejson-3.11.1.tar.gz";
1888 1935 sha256 = "1rr58dppsq73p0qcd9bsw066cdd3v63sqv7j6sqni8frvm4jv8h1";
1889 1936 };
1890 1937 meta = {
1891 1938 license = [ { fullName = "Academic Free License (AFL)"; } pkgs.lib.licenses.mit ];
1892 1939 };
1893 1940 };
1894 1941 "six" = super.buildPythonPackage {
1895 1942 name = "six-1.11.0";
1896 1943 doCheck = false;
1897 1944 src = fetchurl {
1898 1945 url = "https://files.pythonhosted.org/packages/16/d8/bc6316cf98419719bd59c91742194c111b6f2e85abac88e496adefaf7afe/six-1.11.0.tar.gz";
1899 1946 sha256 = "1scqzwc51c875z23phj48gircqjgnn3af8zy2izjwmnlxrxsgs3h";
1900 1947 };
1901 1948 meta = {
1902 1949 license = [ pkgs.lib.licenses.mit ];
1903 1950 };
1904 1951 };
1905 1952 "sqlalchemy" = super.buildPythonPackage {
1906 1953 name = "sqlalchemy-1.1.18";
1907 1954 doCheck = false;
1908 1955 src = fetchurl {
1909 1956 url = "https://files.pythonhosted.org/packages/cc/4d/96d93ff77cd67aca7618e402191eee3490d8f5f245d6ab7622d35fe504f4/SQLAlchemy-1.1.18.tar.gz";
1910 1957 sha256 = "1ab4ysip6irajfbxl9wy27kv76miaz8h6759hfx92499z4dcf3lb";
1911 1958 };
1912 1959 meta = {
1913 1960 license = [ pkgs.lib.licenses.mit ];
1914 1961 };
1915 1962 };
1916 1963 "sshpubkeys" = super.buildPythonPackage {
1917 1964 name = "sshpubkeys-2.2.0";
1918 1965 doCheck = false;
1919 1966 propagatedBuildInputs = [
1920 1967 self."pycrypto"
1921 1968 self."ecdsa"
1922 1969 ];
1923 1970 src = fetchurl {
1924 1971 url = "https://files.pythonhosted.org/packages/27/da/337fabeb3dca6b62039a93ceaa636f25065e0ae92b575b1235342076cf0a/sshpubkeys-2.2.0.tar.gz";
1925 1972 sha256 = "0r4kpwzmg96a2x56pllik7dmc3fnqk189v3sfgsi07q2ryrhr6xm";
1926 1973 };
1927 1974 meta = {
1928 1975 license = [ pkgs.lib.licenses.bsdOriginal ];
1929 1976 };
1930 1977 };
1931 1978 "subprocess32" = super.buildPythonPackage {
1932 1979 name = "subprocess32-3.5.2";
1933 1980 doCheck = false;
1934 1981 src = fetchurl {
1935 1982 url = "https://files.pythonhosted.org/packages/c3/5f/7117737fc7114061837a4f51670d863dd7f7f9c762a6546fa8a0dcfe61c8/subprocess32-3.5.2.tar.gz";
1936 1983 sha256 = "11v62shwmdys48g7ncs3a8jwwnkcl8d4zcwy6dk73z1zy2f9hazb";
1937 1984 };
1938 1985 meta = {
1939 1986 license = [ pkgs.lib.licenses.psfl ];
1940 1987 };
1941 1988 };
1942 1989 "supervisor" = super.buildPythonPackage {
1943 1990 name = "supervisor-3.3.4";
1944 1991 doCheck = false;
1945 1992 propagatedBuildInputs = [
1946 1993 self."meld3"
1947 1994 ];
1948 1995 src = fetchurl {
1949 1996 url = "https://files.pythonhosted.org/packages/44/60/698e54b4a4a9b956b2d709b4b7b676119c833d811d53ee2500f1b5e96dc3/supervisor-3.3.4.tar.gz";
1950 1997 sha256 = "0wp62z9xprvz2krg02xnbwcnq6pxfq3byd8cxx8c2d8xznih28i1";
1951 1998 };
1952 1999 meta = {
1953 2000 license = [ { fullName = "BSD-derived (http://www.repoze.org/LICENSE.txt)"; } ];
1954 2001 };
1955 2002 };
1956 2003 "tempita" = super.buildPythonPackage {
1957 2004 name = "tempita-0.5.2";
1958 2005 doCheck = false;
1959 2006 src = fetchurl {
1960 2007 url = "https://files.pythonhosted.org/packages/56/c8/8ed6eee83dbddf7b0fc64dd5d4454bc05e6ccaafff47991f73f2894d9ff4/Tempita-0.5.2.tar.gz";
1961 2008 sha256 = "177wwq45slfyajd8csy477bmdmzipyw0dm7i85k3akb7m85wzkna";
1962 2009 };
1963 2010 meta = {
1964 2011 license = [ pkgs.lib.licenses.mit ];
1965 2012 };
1966 2013 };
1967 2014 "termcolor" = super.buildPythonPackage {
1968 2015 name = "termcolor-1.1.0";
1969 2016 doCheck = false;
1970 2017 src = fetchurl {
1971 2018 url = "https://files.pythonhosted.org/packages/8a/48/a76be51647d0eb9f10e2a4511bf3ffb8cc1e6b14e9e4fab46173aa79f981/termcolor-1.1.0.tar.gz";
1972 2019 sha256 = "0fv1vq14rpqwgazxg4981904lfyp84mnammw7y046491cv76jv8x";
1973 2020 };
1974 2021 meta = {
1975 2022 license = [ pkgs.lib.licenses.mit ];
1976 2023 };
1977 2024 };
1978 2025 "testpath" = super.buildPythonPackage {
1979 2026 name = "testpath-0.4.2";
1980 2027 doCheck = false;
1981 2028 src = fetchurl {
1982 2029 url = "https://files.pythonhosted.org/packages/06/30/9a7e917066d851d8b4117e85794b5f14516419ea714a8a2681ec6aa8a981/testpath-0.4.2.tar.gz";
1983 2030 sha256 = "1y40hywscnnyb734pnzm55nd8r8kp1072bjxbil83gcd53cv755n";
1984 2031 };
1985 2032 meta = {
1986 2033 license = [ ];
1987 2034 };
1988 2035 };
1989 2036 "traitlets" = super.buildPythonPackage {
1990 2037 name = "traitlets-4.3.2";
1991 2038 doCheck = false;
1992 2039 propagatedBuildInputs = [
1993 2040 self."ipython-genutils"
1994 2041 self."six"
1995 2042 self."decorator"
1996 2043 self."enum34"
1997 2044 ];
1998 2045 src = fetchurl {
1999 2046 url = "https://files.pythonhosted.org/packages/a5/98/7f5ef2fe9e9e071813aaf9cb91d1a732e0a68b6c44a32b38cb8e14c3f069/traitlets-4.3.2.tar.gz";
2000 2047 sha256 = "0dbq7sx26xqz5ixs711k5nc88p8a0nqyz6162pwks5dpcz9d4jww";
2001 2048 };
2002 2049 meta = {
2003 2050 license = [ pkgs.lib.licenses.bsdOriginal ];
2004 2051 };
2005 2052 };
2006 2053 "transaction" = super.buildPythonPackage {
2007 2054 name = "transaction-2.4.0";
2008 2055 doCheck = false;
2009 2056 propagatedBuildInputs = [
2010 2057 self."zope.interface"
2011 2058 ];
2012 2059 src = fetchurl {
2013 2060 url = "https://files.pythonhosted.org/packages/9d/7d/0e8af0d059e052b9dcf2bb5a08aad20ae3e238746bdd3f8701a60969b363/transaction-2.4.0.tar.gz";
2014 2061 sha256 = "17wz1y524ca07vr03yddy8dv0gbscs06dbdywmllxv5rc725jq3j";
2015 2062 };
2016 2063 meta = {
2017 2064 license = [ pkgs.lib.licenses.zpl21 ];
2018 2065 };
2019 2066 };
2020 2067 "translationstring" = super.buildPythonPackage {
2021 2068 name = "translationstring-1.3";
2022 2069 doCheck = false;
2023 2070 src = fetchurl {
2024 2071 url = "https://files.pythonhosted.org/packages/5e/eb/bee578cc150b44c653b63f5ebe258b5d0d812ddac12497e5f80fcad5d0b4/translationstring-1.3.tar.gz";
2025 2072 sha256 = "0bdpcnd9pv0131dl08h4zbcwmgc45lyvq3pa224xwan5b3x4rr2f";
2026 2073 };
2027 2074 meta = {
2028 2075 license = [ { fullName = "BSD-like (http://repoze.org/license.html)"; } ];
2029 2076 };
2030 2077 };
2031 2078 "tzlocal" = super.buildPythonPackage {
2032 2079 name = "tzlocal-1.5.1";
2033 2080 doCheck = false;
2034 2081 propagatedBuildInputs = [
2035 2082 self."pytz"
2036 2083 ];
2037 2084 src = fetchurl {
2038 2085 url = "https://files.pythonhosted.org/packages/cb/89/e3687d3ed99bc882793f82634e9824e62499fdfdc4b1ae39e211c5b05017/tzlocal-1.5.1.tar.gz";
2039 2086 sha256 = "0kiciwiqx0bv0fbc913idxibc4ygg4cb7f8rcpd9ij2shi4bigjf";
2040 2087 };
2041 2088 meta = {
2042 2089 license = [ pkgs.lib.licenses.mit ];
2043 2090 };
2044 2091 };
2045 2092 "urllib3" = super.buildPythonPackage {
2046 name = "urllib3-1.21";
2093 name = "urllib3-1.24.1";
2047 2094 doCheck = false;
2048 2095 src = fetchurl {
2049 url = "https://files.pythonhosted.org/packages/34/95/7b28259d0006ed681c424cd71a668363265eac92b67dddd018eb9a22bff8/urllib3-1.21.tar.gz";
2050 sha256 = "0irnj4wvh2y36s4q3l2vas9qr9m766w6w418nb490j3mf8a8zw6h";
2096 url = "https://files.pythonhosted.org/packages/b1/53/37d82ab391393565f2f831b8eedbffd57db5a718216f82f1a8b4d381a1c1/urllib3-1.24.1.tar.gz";
2097 sha256 = "08lwd9f3hqznyf32vnzwvp87pchx062nkbgyrf67rwlkgj0jk5fy";
2051 2098 };
2052 2099 meta = {
2053 2100 license = [ pkgs.lib.licenses.mit ];
2054 2101 };
2055 2102 };
2056 2103 "urlobject" = super.buildPythonPackage {
2057 2104 name = "urlobject-2.4.3";
2058 2105 doCheck = false;
2059 2106 src = fetchurl {
2060 2107 url = "https://files.pythonhosted.org/packages/e2/b8/1d0a916f4b34c4618846e6da0e4eeaa8fcb4a2f39e006434fe38acb74b34/URLObject-2.4.3.tar.gz";
2061 2108 sha256 = "1ahc8ficzfvr2avln71immfh4ls0zyv6cdaa5xmkdj5rd87f5cj7";
2062 2109 };
2063 2110 meta = {
2064 2111 license = [ pkgs.lib.licenses.publicDomain ];
2065 2112 };
2066 2113 };
2067 2114 "venusian" = super.buildPythonPackage {
2068 2115 name = "venusian-1.1.0";
2069 2116 doCheck = false;
2070 2117 src = fetchurl {
2071 2118 url = "https://files.pythonhosted.org/packages/38/24/b4b470ab9e0a2e2e9b9030c7735828c8934b4c6b45befd1bb713ec2aeb2d/venusian-1.1.0.tar.gz";
2072 2119 sha256 = "0zapz131686qm0gazwy8bh11vr57pr89jbwbl50s528sqy9f80lr";
2073 2120 };
2074 2121 meta = {
2075 2122 license = [ { fullName = "BSD-derived (http://www.repoze.org/LICENSE.txt)"; } ];
2076 2123 };
2077 2124 };
2078 2125 "vine" = super.buildPythonPackage {
2079 2126 name = "vine-1.1.4";
2080 2127 doCheck = false;
2081 2128 src = fetchurl {
2082 2129 url = "https://files.pythonhosted.org/packages/32/23/36284986e011f3c130d802c3c66abd8f1aef371eae110ddf80c5ae22e1ff/vine-1.1.4.tar.gz";
2083 2130 sha256 = "0wkskb2hb494v9gixqnf4bl972p4ibcmxdykzpwjlfa5picns4aj";
2084 2131 };
2085 2132 meta = {
2086 2133 license = [ pkgs.lib.licenses.bsdOriginal ];
2087 2134 };
2088 2135 };
2089 2136 "waitress" = super.buildPythonPackage {
2090 2137 name = "waitress-1.1.0";
2091 2138 doCheck = false;
2092 2139 src = fetchurl {
2093 2140 url = "https://files.pythonhosted.org/packages/3c/68/1c10dd5c556872ceebe88483b0436140048d39de83a84a06a8baa8136f4f/waitress-1.1.0.tar.gz";
2094 2141 sha256 = "1a85gyji0kajc3p0s1pwwfm06w4wfxjkvvl4rnrz3h164kbd6g6k";
2095 2142 };
2096 2143 meta = {
2097 2144 license = [ pkgs.lib.licenses.zpl21 ];
2098 2145 };
2099 2146 };
2100 2147 "wcwidth" = super.buildPythonPackage {
2101 2148 name = "wcwidth-0.1.7";
2102 2149 doCheck = false;
2103 2150 src = fetchurl {
2104 2151 url = "https://files.pythonhosted.org/packages/55/11/e4a2bb08bb450fdbd42cc709dd40de4ed2c472cf0ccb9e64af22279c5495/wcwidth-0.1.7.tar.gz";
2105 2152 sha256 = "0pn6dflzm609m4r3i8ik5ni9ijjbb5fa3vg1n7hn6vkd49r77wrx";
2106 2153 };
2107 2154 meta = {
2108 2155 license = [ pkgs.lib.licenses.mit ];
2109 2156 };
2110 2157 };
2111 2158 "webencodings" = super.buildPythonPackage {
2112 2159 name = "webencodings-0.5.1";
2113 2160 doCheck = false;
2114 2161 src = fetchurl {
2115 2162 url = "https://files.pythonhosted.org/packages/0b/02/ae6ceac1baeda530866a85075641cec12989bd8d31af6d5ab4a3e8c92f47/webencodings-0.5.1.tar.gz";
2116 2163 sha256 = "08qrgrc4hrximb2gqnl69g01s93rhf2842jfxdjljc1dbwj1qsmk";
2117 2164 };
2118 2165 meta = {
2119 2166 license = [ pkgs.lib.licenses.bsdOriginal ];
2120 2167 };
2121 2168 };
2122 2169 "weberror" = super.buildPythonPackage {
2123 2170 name = "weberror-0.10.3";
2124 2171 doCheck = false;
2125 2172 propagatedBuildInputs = [
2126 2173 self."webob"
2127 2174 self."tempita"
2128 2175 self."pygments"
2129 2176 self."paste"
2130 2177 ];
2131 2178 src = fetchurl {
2132 2179 url = "https://files.pythonhosted.org/packages/35/76/e7e5c2ce7e9c7f31b54c1ff295a495886d1279a002557d74dd8957346a79/WebError-0.10.3.tar.gz";
2133 2180 sha256 = "0frg4kvycqpj5bi8asfqfs6bxsr2cvjvb6b56c4d1ai1z57kbjx6";
2134 2181 };
2135 2182 meta = {
2136 2183 license = [ pkgs.lib.licenses.mit ];
2137 2184 };
2138 2185 };
2139 2186 "webhelpers" = super.buildPythonPackage {
2140 2187 name = "webhelpers-1.3";
2141 2188 doCheck = false;
2142 2189 propagatedBuildInputs = [
2143 2190 self."markupsafe"
2144 2191 ];
2145 2192 src = fetchurl {
2146 2193 url = "https://files.pythonhosted.org/packages/ee/68/4d07672821d514184357f1552f2dad923324f597e722de3b016ca4f7844f/WebHelpers-1.3.tar.gz";
2147 2194 sha256 = "10x5i82qdkrvyw18gsybwggfhfpl869siaab89vnndi9x62g51pa";
2148 2195 };
2149 2196 meta = {
2150 2197 license = [ pkgs.lib.licenses.bsdOriginal ];
2151 2198 };
2152 2199 };
2153 2200 "webhelpers2" = super.buildPythonPackage {
2154 2201 name = "webhelpers2-2.0";
2155 2202 doCheck = false;
2156 2203 propagatedBuildInputs = [
2157 2204 self."markupsafe"
2158 2205 self."six"
2159 2206 ];
2160 2207 src = fetchurl {
2161 2208 url = "https://files.pythonhosted.org/packages/ff/30/56342c6ea522439e3662427c8d7b5e5b390dff4ff2dc92d8afcb8ab68b75/WebHelpers2-2.0.tar.gz";
2162 2209 sha256 = "0aphva1qmxh83n01p53f5fd43m4srzbnfbz5ajvbx9aj2aipwmcs";
2163 2210 };
2164 2211 meta = {
2165 2212 license = [ pkgs.lib.licenses.mit ];
2166 2213 };
2167 2214 };
2168 2215 "webob" = super.buildPythonPackage {
2169 2216 name = "webob-1.8.4";
2170 2217 doCheck = false;
2171 2218 src = fetchurl {
2172 2219 url = "https://files.pythonhosted.org/packages/e4/6c/99e322c3d4cc11d9060a67a9bf2f7c9c581f40988c11fffe89bb8c36bc5e/WebOb-1.8.4.tar.gz";
2173 2220 sha256 = "16cfg5y4n6sihz59vsmns2yqbfm0gfsn3l5xgz2g0pdhilaib0x4";
2174 2221 };
2175 2222 meta = {
2176 2223 license = [ pkgs.lib.licenses.mit ];
2177 2224 };
2178 2225 };
2179 2226 "webtest" = super.buildPythonPackage {
2180 2227 name = "webtest-2.0.32";
2181 2228 doCheck = false;
2182 2229 propagatedBuildInputs = [
2183 2230 self."six"
2184 2231 self."webob"
2185 2232 self."waitress"
2186 2233 self."beautifulsoup4"
2187 2234 ];
2188 2235 src = fetchurl {
2189 2236 url = "https://files.pythonhosted.org/packages/27/9f/9e74449d272ffbef4fb3012e6dbc53c0b24822d545e7a33a342f80131e59/WebTest-2.0.32.tar.gz";
2190 2237 sha256 = "0qp0nnbazzm4ibjiyqfcn6f230svk09i4g58zg2i9x1ga06h48a2";
2191 2238 };
2192 2239 meta = {
2193 2240 license = [ pkgs.lib.licenses.mit ];
2194 2241 };
2195 2242 };
2196 2243 "whoosh" = super.buildPythonPackage {
2197 2244 name = "whoosh-2.7.4";
2198 2245 doCheck = false;
2199 2246 src = fetchurl {
2200 2247 url = "https://files.pythonhosted.org/packages/25/2b/6beed2107b148edc1321da0d489afc4617b9ed317ef7b72d4993cad9b684/Whoosh-2.7.4.tar.gz";
2201 2248 sha256 = "10qsqdjpbc85fykc1vgcs8xwbgn4l2l52c8d83xf1q59pwyn79bw";
2202 2249 };
2203 2250 meta = {
2204 2251 license = [ pkgs.lib.licenses.bsdOriginal pkgs.lib.licenses.bsd2 ];
2205 2252 };
2206 2253 };
2207 2254 "ws4py" = super.buildPythonPackage {
2208 2255 name = "ws4py-0.5.1";
2209 2256 doCheck = false;
2210 2257 src = fetchurl {
2211 2258 url = "https://files.pythonhosted.org/packages/53/20/4019a739b2eefe9282d3822ef6a225250af964b117356971bd55e274193c/ws4py-0.5.1.tar.gz";
2212 2259 sha256 = "10slbbf2jm4hpr92jx7kh7mhf48sjl01v2w4d8z3f1p0ybbp7l19";
2213 2260 };
2214 2261 meta = {
2215 2262 license = [ pkgs.lib.licenses.bsdOriginal ];
2216 2263 };
2217 2264 };
2218 2265 "wsgiref" = super.buildPythonPackage {
2219 2266 name = "wsgiref-0.1.2";
2220 2267 doCheck = false;
2221 2268 src = fetchurl {
2222 2269 url = "https://files.pythonhosted.org/packages/41/9e/309259ce8dff8c596e8c26df86dbc4e848b9249fd36797fd60be456f03fc/wsgiref-0.1.2.zip";
2223 2270 sha256 = "0y8fyjmpq7vwwm4x732w97qbkw78rjwal5409k04cw4m03411rn7";
2224 2271 };
2225 2272 meta = {
2226 2273 license = [ { fullName = "PSF or ZPL"; } ];
2227 2274 };
2228 2275 };
2229 2276 "zope.cachedescriptors" = super.buildPythonPackage {
2230 2277 name = "zope.cachedescriptors-4.3.1";
2231 2278 doCheck = false;
2232 2279 propagatedBuildInputs = [
2233 2280 self."setuptools"
2234 2281 ];
2235 2282 src = fetchurl {
2236 2283 url = "https://files.pythonhosted.org/packages/2f/89/ebe1890cc6d3291ebc935558fa764d5fffe571018dbbee200e9db78762cb/zope.cachedescriptors-4.3.1.tar.gz";
2237 2284 sha256 = "0jhr3m5p74c6r7k8iv0005b8bfsialih9d7zl5vx38rf5xq1lk8z";
2238 2285 };
2239 2286 meta = {
2240 2287 license = [ pkgs.lib.licenses.zpl21 ];
2241 2288 };
2242 2289 };
2243 2290 "zope.deprecation" = super.buildPythonPackage {
2244 2291 name = "zope.deprecation-4.3.0";
2245 2292 doCheck = false;
2246 2293 propagatedBuildInputs = [
2247 2294 self."setuptools"
2248 2295 ];
2249 2296 src = fetchurl {
2250 2297 url = "https://files.pythonhosted.org/packages/a1/18/2dc5e6bfe64fdc3b79411b67464c55bb0b43b127051a20f7f492ab767758/zope.deprecation-4.3.0.tar.gz";
2251 2298 sha256 = "095jas41wbxgmw95kwdxqhbc3bgihw2hzj9b3qpdg85apcsf2lkx";
2252 2299 };
2253 2300 meta = {
2254 2301 license = [ pkgs.lib.licenses.zpl21 ];
2255 2302 };
2256 2303 };
2257 2304 "zope.event" = super.buildPythonPackage {
2258 2305 name = "zope.event-4.3.0";
2259 2306 doCheck = false;
2260 2307 propagatedBuildInputs = [
2261 2308 self."setuptools"
2262 2309 ];
2263 2310 src = fetchurl {
2264 2311 url = "https://files.pythonhosted.org/packages/9e/d0/54ba59f19a0635f6591b74be259cf6fbf67e73f4edda27b5cd0cf4d26efa/zope.event-4.3.0.tar.gz";
2265 2312 sha256 = "1rrkyx42bcq8dkpj23c2v99kczlrg8d39c06q5qpr0vs4hjfmv70";
2266 2313 };
2267 2314 meta = {
2268 2315 license = [ pkgs.lib.licenses.zpl21 ];
2269 2316 };
2270 2317 };
2271 2318 "zope.interface" = super.buildPythonPackage {
2272 2319 name = "zope.interface-4.5.0";
2273 2320 doCheck = false;
2274 2321 propagatedBuildInputs = [
2275 2322 self."setuptools"
2276 2323 ];
2277 2324 src = fetchurl {
2278 2325 url = "https://files.pythonhosted.org/packages/ac/8a/657532df378c2cd2a1fe6b12be3b4097521570769d4852ec02c24bd3594e/zope.interface-4.5.0.tar.gz";
2279 2326 sha256 = "0k67m60ij06wkg82n15qgyn96waf4pmrkhv0njpkfzpmv5q89hsp";
2280 2327 };
2281 2328 meta = {
2282 2329 license = [ pkgs.lib.licenses.zpl21 ];
2283 2330 };
2284 2331 };
2285 2332
2286 2333 ### Test requirements
2287 2334
2288 2335
2289 2336 }
@@ -1,135 +1,135 b''
1 1 ## dependencies
2 2
3 3 setuptools-scm==2.1.0
4 4 amqp==2.3.1
5 5 # not released authomatic that has updated some oauth providers
6 6 https://code.rhodecode.com/upstream/authomatic/archive/90a9ce60cc405ae8a2bf5c3713acd5d78579a04e.tar.gz?md5=3c68720a1322b25254009518d1ff6801#egg=authomatic==0.1.0.post1
7 7 atomicwrites==1.2.1
8 8 attrs==18.2.0
9 9 babel==1.3
10 10 beaker==1.9.1
11 11 bleach==3.0.2
12 12 celery==4.1.1
13 13 chameleon==2.24
14 14 channelstream==0.5.2
15 15 click==6.6
16 16 colander==1.5.1
17 17 # our custom configobj
18 18 https://code.rhodecode.com/upstream/configobj/archive/a11ff0a0bd4fbda9e3a91267e720f88329efb4a6.tar.gz?md5=9916c524ea11a6c418217af6b28d4b3c#egg=configobj==5.0.6
19 19 cssselect==1.0.3
20 20 decorator==4.1.2
21 21 deform==2.0.7
22 22 docutils==0.14.0
23 23 dogpile.cache==0.6.7
24 24 dogpile.core==0.4.1
25 25 ecdsa==0.13
26 26 formencode==1.2.4
27 27 future==0.14.3
28 28 futures==3.0.2
29 29 gnureadline==6.3.8
30 30 infrae.cache==1.0.1
31 31 iso8601==0.1.11
32 32 itsdangerous==0.24
33 33 jinja2==2.9.6
34 34 billiard==3.5.0.3
35 35 kombu==4.2.0
36 36 lxml==4.2.5
37 37 mako==1.0.7
38 38 markdown==2.6.11
39 markupsafe==1.0.0
39 markupsafe==1.1.0
40 40 msgpack-python==0.5.6
41 41 pyotp==2.2.7
42 42 packaging==15.2
43 43 paste==3.0.5
44 44 pastedeploy==2.0.1
45 45 pastescript==3.0.0
46 46 pathlib2==2.3.3
47 47 peppercorn==0.6
48 48 psutil==5.4.7
49 49 py-bcrypt==0.4
50 50 pycrypto==2.6.1
51 51 pycurl==7.43.0.2
52 52 pyflakes==0.8.1
53 53 pygments==2.3.0
54 pyparsing==1.5.7
54 pyparsing==2.3.0
55 55 pyramid-beaker==0.8
56 56 pyramid-debugtoolbar==4.4.0
57 57 pyramid-jinja2==2.7
58 58 pyramid-mako==1.0.2
59 59 pyramid==1.10.1
60 60 pyramid_mailer==0.15.1
61 61 python-dateutil
62 62 python-ldap==3.1.0
63 63 python-memcached==1.59
64 64 python-pam==1.8.4
65 65 python-saml==2.4.2
66 66 pytz==2018.4
67 67 tzlocal==1.5.1
68 68 pyzmq==14.6.0
69 69 py-gfm==0.1.4
70 70 redis==2.10.6
71 71 repoze.lru==0.7
72 72 requests==2.9.1
73 73 routes==2.4.1
74 74 simplejson==3.11.1
75 75 six==1.11.0
76 76 sqlalchemy==1.1.18
77 77 sshpubkeys==2.2.0
78 78 subprocess32==3.5.2
79 79 supervisor==3.3.4
80 80 tempita==0.5.2
81 81 translationstring==1.3
82 urllib3==1.21
82 urllib3==1.24.1
83 83 urlobject==2.4.3
84 84 venusian==1.1.0
85 85 weberror==0.10.3
86 86 webhelpers2==2.0
87 87 webhelpers==1.3
88 88 webob==1.8.4
89 89 whoosh==2.7.4
90 90 wsgiref==0.1.2
91 91 zope.cachedescriptors==4.3.1
92 92 zope.deprecation==4.3.0
93 93 zope.event==4.3.0
94 94 zope.interface==4.5.0
95 95
96 96 # DB drivers
97 97 mysql-python==1.2.5
98 98 pymysql==0.8.1
99 99 pysqlite==2.8.3
100 100 psycopg2==2.7.5
101 101
102 102 # IPYTHON RENDERING
103 103 # entrypoints backport, pypi version doesn't support egg installs
104 104 https://code.rhodecode.com/upstream/entrypoints/archive/96e6d645684e1af3d7df5b5272f3fe85a546b233.tar.gz?md5=7db37771aea9ac9fefe093e5d6987313#egg=entrypoints==0.2.2.rhodecode-upstream1
105 105 nbconvert==5.3.1
106 106 nbformat==4.4.0
107 107 jupyter_client==5.0.0
108 108
109 109 ## cli tools
110 110 alembic==1.0.5
111 111 invoke==0.13.0
112 112 bumpversion==0.5.3
113 113
114 114 ## http servers
115 115 gevent==1.3.7
116 116 greenlet==0.4.15
117 117 gunicorn==19.9.0
118 118 waitress==1.1.0
119 119 setproctitle==1.1.10
120 120
121 121 ## debug
122 122 ipdb==0.11.0
123 123 ipython==5.1.0
124 124
125 125 ## rhodecode-tools, special case
126 https://code.rhodecode.com/rhodecode-tools-ce/archive/v1.0.1.tar.gz?md5=ffb5d6bcb855305b93cfe23ad42e500b#egg=rhodecode-tools==1.0.1
126 https://code.rhodecode.com/rhodecode-tools-ce/archive/v1.1.0.tar.gz?md5=cc320c277cb2add546220290ac9be626#egg=rhodecode-tools==1.1.0
127 127
128 128 ## appenlight
129 129 appenlight-client==0.6.26
130 130
131 131 ## test related requirements
132 132 -r requirements_test.txt
133 133
134 134 ## uncomment to add the debug libraries
135 135 #-r requirements_debug.txt
@@ -1,780 +1,780 b''
1 1 # -*- coding: utf-8 -*-
2 2
3 3 # Copyright (C) 2010-2018 RhodeCode GmbH
4 4 #
5 5 # This program is free software: you can redistribute it and/or modify
6 6 # it under the terms of the GNU Affero General Public License, version 3
7 7 # (only), as published by the Free Software Foundation.
8 8 #
9 9 # This program is distributed in the hope that it will be useful,
10 10 # but WITHOUT ANY WARRANTY; without even the implied warranty of
11 11 # MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
12 12 # GNU General Public License for more details.
13 13 #
14 14 # You should have received a copy of the GNU Affero General Public License
15 15 # along with this program. If not, see <http://www.gnu.org/licenses/>.
16 16 #
17 17 # This program is dual-licensed. If you wish to learn more about the
18 18 # RhodeCode Enterprise Edition, including its added features, Support services,
19 19 # and proprietary license terms, please see https://rhodecode.com/licenses/
20 20
21 21
22 22 import logging
23 23 import collections
24 24
25 25 import datetime
26 26 import formencode
27 27 import formencode.htmlfill
28 28
29 29 import rhodecode
30 30 from pyramid.view import view_config
31 31 from pyramid.httpexceptions import HTTPFound, HTTPNotFound
32 32 from pyramid.renderers import render
33 33 from pyramid.response import Response
34 34
35 35 from rhodecode.apps._base import BaseAppView
36 36 from rhodecode.apps._base.navigation import navigation_list
37 37 from rhodecode.apps.svn_support.config_keys import generate_config
38 38 from rhodecode.lib import helpers as h
39 39 from rhodecode.lib.auth import (
40 40 LoginRequired, HasPermissionAllDecorator, CSRFRequired)
41 41 from rhodecode.lib.celerylib import tasks, run_task
42 42 from rhodecode.lib.utils import repo2db_mapper
43 43 from rhodecode.lib.utils2 import str2bool, safe_unicode, AttributeDict
44 44 from rhodecode.lib.index import searcher_from_config
45 45
46 46 from rhodecode.model.db import RhodeCodeUi, Repository
47 47 from rhodecode.model.forms import (ApplicationSettingsForm,
48 48 ApplicationUiSettingsForm, ApplicationVisualisationForm,
49 49 LabsSettingsForm, IssueTrackerPatternsForm)
50 50 from rhodecode.model.repo_group import RepoGroupModel
51 51
52 52 from rhodecode.model.scm import ScmModel
53 53 from rhodecode.model.notification import EmailNotificationModel
54 54 from rhodecode.model.meta import Session
55 55 from rhodecode.model.settings import (
56 56 IssueTrackerSettingsModel, VcsSettingsModel, SettingNotFound,
57 57 SettingsModel)
58 58
59 59
60 60 log = logging.getLogger(__name__)
61 61
62 62
63 63 class AdminSettingsView(BaseAppView):
64 64
65 65 def load_default_context(self):
66 66 c = self._get_local_tmpl_context()
67 67 c.labs_active = str2bool(
68 68 rhodecode.CONFIG.get('labs_settings_active', 'true'))
69 69 c.navlist = navigation_list(self.request)
70 70
71 71 return c
72 72
73 73 @classmethod
74 74 def _get_ui_settings(cls):
75 75 ret = RhodeCodeUi.query().all()
76 76
77 77 if not ret:
78 78 raise Exception('Could not get application ui settings !')
79 79 settings = {}
80 80 for each in ret:
81 81 k = each.ui_key
82 82 v = each.ui_value
83 83 if k == '/':
84 84 k = 'root_path'
85 85
86 86 if k in ['push_ssl', 'publish', 'enabled']:
87 87 v = str2bool(v)
88 88
89 89 if k.find('.') != -1:
90 90 k = k.replace('.', '_')
91 91
92 92 if each.ui_section in ['hooks', 'extensions']:
93 93 v = each.ui_active
94 94
95 95 settings[each.ui_section + '_' + k] = v
96 96 return settings
97 97
98 98 @classmethod
99 99 def _form_defaults(cls):
100 100 defaults = SettingsModel().get_all_settings()
101 101 defaults.update(cls._get_ui_settings())
102 102
103 103 defaults.update({
104 104 'new_svn_branch': '',
105 105 'new_svn_tag': '',
106 106 })
107 107 return defaults
108 108
109 109 @LoginRequired()
110 110 @HasPermissionAllDecorator('hg.admin')
111 111 @view_config(
112 112 route_name='admin_settings_vcs', request_method='GET',
113 113 renderer='rhodecode:templates/admin/settings/settings.mako')
114 114 def settings_vcs(self):
115 115 c = self.load_default_context()
116 116 c.active = 'vcs'
117 117 model = VcsSettingsModel()
118 118 c.svn_branch_patterns = model.get_global_svn_branch_patterns()
119 119 c.svn_tag_patterns = model.get_global_svn_tag_patterns()
120 120
121 121 settings = self.request.registry.settings
122 122 c.svn_proxy_generate_config = settings[generate_config]
123 123
124 124 defaults = self._form_defaults()
125 125
126 126 model.create_largeobjects_dirs_if_needed(defaults['paths_root_path'])
127 127
128 128 data = render('rhodecode:templates/admin/settings/settings.mako',
129 129 self._get_template_context(c), self.request)
130 130 html = formencode.htmlfill.render(
131 131 data,
132 132 defaults=defaults,
133 133 encoding="UTF-8",
134 134 force_defaults=False
135 135 )
136 136 return Response(html)
137 137
138 138 @LoginRequired()
139 139 @HasPermissionAllDecorator('hg.admin')
140 140 @CSRFRequired()
141 141 @view_config(
142 142 route_name='admin_settings_vcs_update', request_method='POST',
143 143 renderer='rhodecode:templates/admin/settings/settings.mako')
144 144 def settings_vcs_update(self):
145 145 _ = self.request.translate
146 146 c = self.load_default_context()
147 147 c.active = 'vcs'
148 148
149 149 model = VcsSettingsModel()
150 150 c.svn_branch_patterns = model.get_global_svn_branch_patterns()
151 151 c.svn_tag_patterns = model.get_global_svn_tag_patterns()
152 152
153 153 settings = self.request.registry.settings
154 154 c.svn_proxy_generate_config = settings[generate_config]
155 155
156 156 application_form = ApplicationUiSettingsForm(self.request.translate)()
157 157
158 158 try:
159 159 form_result = application_form.to_python(dict(self.request.POST))
160 160 except formencode.Invalid as errors:
161 161 h.flash(
162 162 _("Some form inputs contain invalid data."),
163 163 category='error')
164 164 data = render('rhodecode:templates/admin/settings/settings.mako',
165 165 self._get_template_context(c), self.request)
166 166 html = formencode.htmlfill.render(
167 167 data,
168 168 defaults=errors.value,
169 169 errors=errors.error_dict or {},
170 170 prefix_error=False,
171 171 encoding="UTF-8",
172 172 force_defaults=False
173 173 )
174 174 return Response(html)
175 175
176 176 try:
177 177 if c.visual.allow_repo_location_change:
178 178 model.update_global_path_setting(form_result['paths_root_path'])
179 179
180 180 model.update_global_ssl_setting(form_result['web_push_ssl'])
181 181 model.update_global_hook_settings(form_result)
182 182
183 183 model.create_or_update_global_svn_settings(form_result)
184 184 model.create_or_update_global_hg_settings(form_result)
185 185 model.create_or_update_global_git_settings(form_result)
186 186 model.create_or_update_global_pr_settings(form_result)
187 187 except Exception:
188 188 log.exception("Exception while updating settings")
189 189 h.flash(_('Error occurred during updating '
190 190 'application settings'), category='error')
191 191 else:
192 192 Session().commit()
193 193 h.flash(_('Updated VCS settings'), category='success')
194 194 raise HTTPFound(h.route_path('admin_settings_vcs'))
195 195
196 196 data = render('rhodecode:templates/admin/settings/settings.mako',
197 197 self._get_template_context(c), self.request)
198 198 html = formencode.htmlfill.render(
199 199 data,
200 200 defaults=self._form_defaults(),
201 201 encoding="UTF-8",
202 202 force_defaults=False
203 203 )
204 204 return Response(html)
205 205
206 206 @LoginRequired()
207 207 @HasPermissionAllDecorator('hg.admin')
208 208 @CSRFRequired()
209 209 @view_config(
210 210 route_name='admin_settings_vcs_svn_pattern_delete', request_method='POST',
211 211 renderer='json_ext', xhr=True)
212 212 def settings_vcs_delete_svn_pattern(self):
213 213 delete_pattern_id = self.request.POST.get('delete_svn_pattern')
214 214 model = VcsSettingsModel()
215 215 try:
216 216 model.delete_global_svn_pattern(delete_pattern_id)
217 217 except SettingNotFound:
218 218 log.exception(
219 219 'Failed to delete svn_pattern with id %s', delete_pattern_id)
220 220 raise HTTPNotFound()
221 221
222 222 Session().commit()
223 223 return True
224 224
225 225 @LoginRequired()
226 226 @HasPermissionAllDecorator('hg.admin')
227 227 @view_config(
228 228 route_name='admin_settings_mapping', request_method='GET',
229 229 renderer='rhodecode:templates/admin/settings/settings.mako')
230 230 def settings_mapping(self):
231 231 c = self.load_default_context()
232 232 c.active = 'mapping'
233 233
234 234 data = render('rhodecode:templates/admin/settings/settings.mako',
235 235 self._get_template_context(c), self.request)
236 236 html = formencode.htmlfill.render(
237 237 data,
238 238 defaults=self._form_defaults(),
239 239 encoding="UTF-8",
240 240 force_defaults=False
241 241 )
242 242 return Response(html)
243 243
244 244 @LoginRequired()
245 245 @HasPermissionAllDecorator('hg.admin')
246 246 @CSRFRequired()
247 247 @view_config(
248 248 route_name='admin_settings_mapping_update', request_method='POST',
249 249 renderer='rhodecode:templates/admin/settings/settings.mako')
250 250 def settings_mapping_update(self):
251 251 _ = self.request.translate
252 252 c = self.load_default_context()
253 253 c.active = 'mapping'
254 254 rm_obsolete = self.request.POST.get('destroy', False)
255 255 invalidate_cache = self.request.POST.get('invalidate', False)
256 256 log.debug(
257 257 'rescanning repo location with destroy obsolete=%s', rm_obsolete)
258 258
259 259 if invalidate_cache:
260 260 log.debug('invalidating all repositories cache')
261 261 for repo in Repository.get_all():
262 262 ScmModel().mark_for_invalidation(repo.repo_name, delete=True)
263 263
264 264 filesystem_repos = ScmModel().repo_scan()
265 265 added, removed = repo2db_mapper(filesystem_repos, rm_obsolete)
266 266 _repr = lambda l: ', '.join(map(safe_unicode, l)) or '-'
267 267 h.flash(_('Repositories successfully '
268 268 'rescanned added: %s ; removed: %s') %
269 269 (_repr(added), _repr(removed)),
270 270 category='success')
271 271 raise HTTPFound(h.route_path('admin_settings_mapping'))
272 272
273 273 @LoginRequired()
274 274 @HasPermissionAllDecorator('hg.admin')
275 275 @view_config(
276 276 route_name='admin_settings', request_method='GET',
277 277 renderer='rhodecode:templates/admin/settings/settings.mako')
278 278 @view_config(
279 279 route_name='admin_settings_global', request_method='GET',
280 280 renderer='rhodecode:templates/admin/settings/settings.mako')
281 281 def settings_global(self):
282 282 c = self.load_default_context()
283 283 c.active = 'global'
284 284 c.personal_repo_group_default_pattern = RepoGroupModel()\
285 285 .get_personal_group_name_pattern()
286 286
287 287 data = render('rhodecode:templates/admin/settings/settings.mako',
288 288 self._get_template_context(c), self.request)
289 289 html = formencode.htmlfill.render(
290 290 data,
291 291 defaults=self._form_defaults(),
292 292 encoding="UTF-8",
293 293 force_defaults=False
294 294 )
295 295 return Response(html)
296 296
297 297 @LoginRequired()
298 298 @HasPermissionAllDecorator('hg.admin')
299 299 @CSRFRequired()
300 300 @view_config(
301 301 route_name='admin_settings_update', request_method='POST',
302 302 renderer='rhodecode:templates/admin/settings/settings.mako')
303 303 @view_config(
304 304 route_name='admin_settings_global_update', request_method='POST',
305 305 renderer='rhodecode:templates/admin/settings/settings.mako')
306 306 def settings_global_update(self):
307 307 _ = self.request.translate
308 308 c = self.load_default_context()
309 309 c.active = 'global'
310 310 c.personal_repo_group_default_pattern = RepoGroupModel()\
311 311 .get_personal_group_name_pattern()
312 312 application_form = ApplicationSettingsForm(self.request.translate)()
313 313 try:
314 314 form_result = application_form.to_python(dict(self.request.POST))
315 315 except formencode.Invalid as errors:
316 316 h.flash(
317 317 _("Some form inputs contain invalid data."),
318 318 category='error')
319 319 data = render('rhodecode:templates/admin/settings/settings.mako',
320 320 self._get_template_context(c), self.request)
321 321 html = formencode.htmlfill.render(
322 322 data,
323 323 defaults=errors.value,
324 324 errors=errors.error_dict or {},
325 325 prefix_error=False,
326 326 encoding="UTF-8",
327 327 force_defaults=False
328 328 )
329 329 return Response(html)
330 330
331 331 settings = [
332 332 ('title', 'rhodecode_title', 'unicode'),
333 333 ('realm', 'rhodecode_realm', 'unicode'),
334 334 ('pre_code', 'rhodecode_pre_code', 'unicode'),
335 335 ('post_code', 'rhodecode_post_code', 'unicode'),
336 336 ('captcha_public_key', 'rhodecode_captcha_public_key', 'unicode'),
337 337 ('captcha_private_key', 'rhodecode_captcha_private_key', 'unicode'),
338 338 ('create_personal_repo_group', 'rhodecode_create_personal_repo_group', 'bool'),
339 339 ('personal_repo_group_pattern', 'rhodecode_personal_repo_group_pattern', 'unicode'),
340 340 ]
341 341 try:
342 342 for setting, form_key, type_ in settings:
343 343 sett = SettingsModel().create_or_update_setting(
344 344 setting, form_result[form_key], type_)
345 345 Session().add(sett)
346 346
347 347 Session().commit()
348 348 SettingsModel().invalidate_settings_cache()
349 349 h.flash(_('Updated application settings'), category='success')
350 350 except Exception:
351 351 log.exception("Exception while updating application settings")
352 352 h.flash(
353 353 _('Error occurred during updating application settings'),
354 354 category='error')
355 355
356 356 raise HTTPFound(h.route_path('admin_settings_global'))
357 357
358 358 @LoginRequired()
359 359 @HasPermissionAllDecorator('hg.admin')
360 360 @view_config(
361 361 route_name='admin_settings_visual', request_method='GET',
362 362 renderer='rhodecode:templates/admin/settings/settings.mako')
363 363 def settings_visual(self):
364 364 c = self.load_default_context()
365 365 c.active = 'visual'
366 366
367 367 data = render('rhodecode:templates/admin/settings/settings.mako',
368 368 self._get_template_context(c), self.request)
369 369 html = formencode.htmlfill.render(
370 370 data,
371 371 defaults=self._form_defaults(),
372 372 encoding="UTF-8",
373 373 force_defaults=False
374 374 )
375 375 return Response(html)
376 376
377 377 @LoginRequired()
378 378 @HasPermissionAllDecorator('hg.admin')
379 379 @CSRFRequired()
380 380 @view_config(
381 381 route_name='admin_settings_visual_update', request_method='POST',
382 382 renderer='rhodecode:templates/admin/settings/settings.mako')
383 383 def settings_visual_update(self):
384 384 _ = self.request.translate
385 385 c = self.load_default_context()
386 386 c.active = 'visual'
387 387 application_form = ApplicationVisualisationForm(self.request.translate)()
388 388 try:
389 389 form_result = application_form.to_python(dict(self.request.POST))
390 390 except formencode.Invalid as errors:
391 391 h.flash(
392 392 _("Some form inputs contain invalid data."),
393 393 category='error')
394 394 data = render('rhodecode:templates/admin/settings/settings.mako',
395 395 self._get_template_context(c), self.request)
396 396 html = formencode.htmlfill.render(
397 397 data,
398 398 defaults=errors.value,
399 399 errors=errors.error_dict or {},
400 400 prefix_error=False,
401 401 encoding="UTF-8",
402 402 force_defaults=False
403 403 )
404 404 return Response(html)
405 405
406 406 try:
407 407 settings = [
408 408 ('show_public_icon', 'rhodecode_show_public_icon', 'bool'),
409 409 ('show_private_icon', 'rhodecode_show_private_icon', 'bool'),
410 410 ('stylify_metatags', 'rhodecode_stylify_metatags', 'bool'),
411 411 ('repository_fields', 'rhodecode_repository_fields', 'bool'),
412 412 ('dashboard_items', 'rhodecode_dashboard_items', 'int'),
413 413 ('admin_grid_items', 'rhodecode_admin_grid_items', 'int'),
414 414 ('show_version', 'rhodecode_show_version', 'bool'),
415 415 ('use_gravatar', 'rhodecode_use_gravatar', 'bool'),
416 416 ('markup_renderer', 'rhodecode_markup_renderer', 'unicode'),
417 417 ('gravatar_url', 'rhodecode_gravatar_url', 'unicode'),
418 418 ('clone_uri_tmpl', 'rhodecode_clone_uri_tmpl', 'unicode'),
419 419 ('clone_uri_ssh_tmpl', 'rhodecode_clone_uri_ssh_tmpl', 'unicode'),
420 420 ('support_url', 'rhodecode_support_url', 'unicode'),
421 421 ('show_revision_number', 'rhodecode_show_revision_number', 'bool'),
422 422 ('show_sha_length', 'rhodecode_show_sha_length', 'int'),
423 423 ]
424 424 for setting, form_key, type_ in settings:
425 425 sett = SettingsModel().create_or_update_setting(
426 426 setting, form_result[form_key], type_)
427 427 Session().add(sett)
428 428
429 429 Session().commit()
430 430 SettingsModel().invalidate_settings_cache()
431 431 h.flash(_('Updated visualisation settings'), category='success')
432 432 except Exception:
433 433 log.exception("Exception updating visualization settings")
434 434 h.flash(_('Error occurred during updating '
435 435 'visualisation settings'),
436 436 category='error')
437 437
438 438 raise HTTPFound(h.route_path('admin_settings_visual'))
439 439
440 440 @LoginRequired()
441 441 @HasPermissionAllDecorator('hg.admin')
442 442 @view_config(
443 443 route_name='admin_settings_issuetracker', request_method='GET',
444 444 renderer='rhodecode:templates/admin/settings/settings.mako')
445 445 def settings_issuetracker(self):
446 446 c = self.load_default_context()
447 447 c.active = 'issuetracker'
448 448 defaults = SettingsModel().get_all_settings()
449 449
450 450 entry_key = 'rhodecode_issuetracker_pat_'
451 451
452 452 c.issuetracker_entries = {}
453 453 for k, v in defaults.items():
454 454 if k.startswith(entry_key):
455 455 uid = k[len(entry_key):]
456 456 c.issuetracker_entries[uid] = None
457 457
458 458 for uid in c.issuetracker_entries:
459 459 c.issuetracker_entries[uid] = AttributeDict({
460 460 'pat': defaults.get('rhodecode_issuetracker_pat_' + uid),
461 461 'url': defaults.get('rhodecode_issuetracker_url_' + uid),
462 462 'pref': defaults.get('rhodecode_issuetracker_pref_' + uid),
463 463 'desc': defaults.get('rhodecode_issuetracker_desc_' + uid),
464 464 })
465 465
466 466 return self._get_template_context(c)
467 467
468 468 @LoginRequired()
469 469 @HasPermissionAllDecorator('hg.admin')
470 470 @CSRFRequired()
471 471 @view_config(
472 472 route_name='admin_settings_issuetracker_test', request_method='POST',
473 473 renderer='string', xhr=True)
474 474 def settings_issuetracker_test(self):
475 475 return h.urlify_commit_message(
476 476 self.request.POST.get('test_text', ''),
477 477 'repo_group/test_repo1')
478 478
479 479 @LoginRequired()
480 480 @HasPermissionAllDecorator('hg.admin')
481 481 @CSRFRequired()
482 482 @view_config(
483 483 route_name='admin_settings_issuetracker_update', request_method='POST',
484 484 renderer='rhodecode:templates/admin/settings/settings.mako')
485 485 def settings_issuetracker_update(self):
486 486 _ = self.request.translate
487 487 self.load_default_context()
488 488 settings_model = IssueTrackerSettingsModel()
489 489
490 490 try:
491 491 form = IssueTrackerPatternsForm(self.request.translate)()
492 492 data = form.to_python(self.request.POST)
493 493 except formencode.Invalid as errors:
494 494 log.exception('Failed to add new pattern')
495 495 error = errors
496 496 h.flash(_('Invalid issue tracker pattern: {}'.format(error)),
497 497 category='error')
498 498 raise HTTPFound(h.route_path('admin_settings_issuetracker'))
499 499
500 500 if data:
501 501 for uid in data.get('delete_patterns', []):
502 502 settings_model.delete_entries(uid)
503 503
504 504 for pattern in data.get('patterns', []):
505 505 for setting, value, type_ in pattern:
506 506 sett = settings_model.create_or_update_setting(
507 507 setting, value, type_)
508 508 Session().add(sett)
509 509
510 510 Session().commit()
511 511
512 512 SettingsModel().invalidate_settings_cache()
513 513 h.flash(_('Updated issue tracker entries'), category='success')
514 514 raise HTTPFound(h.route_path('admin_settings_issuetracker'))
515 515
516 516 @LoginRequired()
517 517 @HasPermissionAllDecorator('hg.admin')
518 518 @CSRFRequired()
519 519 @view_config(
520 520 route_name='admin_settings_issuetracker_delete', request_method='POST',
521 521 renderer='rhodecode:templates/admin/settings/settings.mako')
522 522 def settings_issuetracker_delete(self):
523 523 _ = self.request.translate
524 524 self.load_default_context()
525 525 uid = self.request.POST.get('uid')
526 526 try:
527 527 IssueTrackerSettingsModel().delete_entries(uid)
528 528 except Exception:
529 529 log.exception('Failed to delete issue tracker setting %s', uid)
530 530 raise HTTPNotFound()
531 531 h.flash(_('Removed issue tracker entry'), category='success')
532 532 raise HTTPFound(h.route_path('admin_settings_issuetracker'))
533 533
534 534 @LoginRequired()
535 535 @HasPermissionAllDecorator('hg.admin')
536 536 @view_config(
537 537 route_name='admin_settings_email', request_method='GET',
538 538 renderer='rhodecode:templates/admin/settings/settings.mako')
539 539 def settings_email(self):
540 540 c = self.load_default_context()
541 541 c.active = 'email'
542 542 c.rhodecode_ini = rhodecode.CONFIG
543 543
544 544 data = render('rhodecode:templates/admin/settings/settings.mako',
545 545 self._get_template_context(c), self.request)
546 546 html = formencode.htmlfill.render(
547 547 data,
548 548 defaults=self._form_defaults(),
549 549 encoding="UTF-8",
550 550 force_defaults=False
551 551 )
552 552 return Response(html)
553 553
554 554 @LoginRequired()
555 555 @HasPermissionAllDecorator('hg.admin')
556 556 @CSRFRequired()
557 557 @view_config(
558 558 route_name='admin_settings_email_update', request_method='POST',
559 559 renderer='rhodecode:templates/admin/settings/settings.mako')
560 560 def settings_email_update(self):
561 561 _ = self.request.translate
562 562 c = self.load_default_context()
563 563 c.active = 'email'
564 564
565 565 test_email = self.request.POST.get('test_email')
566 566
567 567 if not test_email:
568 568 h.flash(_('Please enter email address'), category='error')
569 569 raise HTTPFound(h.route_path('admin_settings_email'))
570 570
571 571 email_kwargs = {
572 572 'date': datetime.datetime.now(),
573 573 'user': c.rhodecode_user,
574 574 'rhodecode_version': c.rhodecode_version
575 575 }
576 576
577 577 (subject, headers, email_body,
578 578 email_body_plaintext) = EmailNotificationModel().render_email(
579 579 EmailNotificationModel.TYPE_EMAIL_TEST, **email_kwargs)
580 580
581 581 recipients = [test_email] if test_email else None
582 582
583 583 run_task(tasks.send_email, recipients, subject,
584 584 email_body_plaintext, email_body)
585 585
586 586 h.flash(_('Send email task created'), category='success')
587 587 raise HTTPFound(h.route_path('admin_settings_email'))
588 588
589 589 @LoginRequired()
590 590 @HasPermissionAllDecorator('hg.admin')
591 591 @view_config(
592 592 route_name='admin_settings_hooks', request_method='GET',
593 593 renderer='rhodecode:templates/admin/settings/settings.mako')
594 594 def settings_hooks(self):
595 595 c = self.load_default_context()
596 596 c.active = 'hooks'
597 597
598 598 model = SettingsModel()
599 599 c.hooks = model.get_builtin_hooks()
600 600 c.custom_hooks = model.get_custom_hooks()
601 601
602 602 data = render('rhodecode:templates/admin/settings/settings.mako',
603 603 self._get_template_context(c), self.request)
604 604 html = formencode.htmlfill.render(
605 605 data,
606 606 defaults=self._form_defaults(),
607 607 encoding="UTF-8",
608 608 force_defaults=False
609 609 )
610 610 return Response(html)
611 611
612 612 @LoginRequired()
613 613 @HasPermissionAllDecorator('hg.admin')
614 614 @CSRFRequired()
615 615 @view_config(
616 616 route_name='admin_settings_hooks_update', request_method='POST',
617 617 renderer='rhodecode:templates/admin/settings/settings.mako')
618 618 @view_config(
619 619 route_name='admin_settings_hooks_delete', request_method='POST',
620 620 renderer='rhodecode:templates/admin/settings/settings.mako')
621 621 def settings_hooks_update(self):
622 622 _ = self.request.translate
623 623 c = self.load_default_context()
624 624 c.active = 'hooks'
625 625 if c.visual.allow_custom_hooks_settings:
626 626 ui_key = self.request.POST.get('new_hook_ui_key')
627 627 ui_value = self.request.POST.get('new_hook_ui_value')
628 628
629 629 hook_id = self.request.POST.get('hook_id')
630 630 new_hook = False
631 631
632 632 model = SettingsModel()
633 633 try:
634 634 if ui_value and ui_key:
635 635 model.create_or_update_hook(ui_key, ui_value)
636 636 h.flash(_('Added new hook'), category='success')
637 637 new_hook = True
638 638 elif hook_id:
639 639 RhodeCodeUi.delete(hook_id)
640 640 Session().commit()
641 641
642 642 # check for edits
643 643 update = False
644 644 _d = self.request.POST.dict_of_lists()
645 645 for k, v in zip(_d.get('hook_ui_key', []),
646 646 _d.get('hook_ui_value_new', [])):
647 647 model.create_or_update_hook(k, v)
648 648 update = True
649 649
650 650 if update and not new_hook:
651 651 h.flash(_('Updated hooks'), category='success')
652 652 Session().commit()
653 653 except Exception:
654 654 log.exception("Exception during hook creation")
655 655 h.flash(_('Error occurred during hook creation'),
656 656 category='error')
657 657
658 658 raise HTTPFound(h.route_path('admin_settings_hooks'))
659 659
660 660 @LoginRequired()
661 661 @HasPermissionAllDecorator('hg.admin')
662 662 @view_config(
663 663 route_name='admin_settings_search', request_method='GET',
664 664 renderer='rhodecode:templates/admin/settings/settings.mako')
665 665 def settings_search(self):
666 666 c = self.load_default_context()
667 667 c.active = 'search'
668 668
669 searcher = searcher_from_config(self.request.registry.settings)
670 c.statistics = searcher.statistics(self.request.translate)
669 c.searcher = searcher_from_config(self.request.registry.settings)
670 c.statistics = c.searcher.statistics(self.request.translate)
671 671
672 672 return self._get_template_context(c)
673 673
674 674 @LoginRequired()
675 675 @HasPermissionAllDecorator('hg.admin')
676 676 @view_config(
677 677 route_name='admin_settings_automation', request_method='GET',
678 678 renderer='rhodecode:templates/admin/settings/settings.mako')
679 679 def settings_automation(self):
680 680 c = self.load_default_context()
681 681 c.active = 'automation'
682 682
683 683 return self._get_template_context(c)
684 684
685 685 @LoginRequired()
686 686 @HasPermissionAllDecorator('hg.admin')
687 687 @view_config(
688 688 route_name='admin_settings_labs', request_method='GET',
689 689 renderer='rhodecode:templates/admin/settings/settings.mako')
690 690 def settings_labs(self):
691 691 c = self.load_default_context()
692 692 if not c.labs_active:
693 693 raise HTTPFound(h.route_path('admin_settings'))
694 694
695 695 c.active = 'labs'
696 696 c.lab_settings = _LAB_SETTINGS
697 697
698 698 data = render('rhodecode:templates/admin/settings/settings.mako',
699 699 self._get_template_context(c), self.request)
700 700 html = formencode.htmlfill.render(
701 701 data,
702 702 defaults=self._form_defaults(),
703 703 encoding="UTF-8",
704 704 force_defaults=False
705 705 )
706 706 return Response(html)
707 707
708 708 @LoginRequired()
709 709 @HasPermissionAllDecorator('hg.admin')
710 710 @CSRFRequired()
711 711 @view_config(
712 712 route_name='admin_settings_labs_update', request_method='POST',
713 713 renderer='rhodecode:templates/admin/settings/settings.mako')
714 714 def settings_labs_update(self):
715 715 _ = self.request.translate
716 716 c = self.load_default_context()
717 717 c.active = 'labs'
718 718
719 719 application_form = LabsSettingsForm(self.request.translate)()
720 720 try:
721 721 form_result = application_form.to_python(dict(self.request.POST))
722 722 except formencode.Invalid as errors:
723 723 h.flash(
724 724 _("Some form inputs contain invalid data."),
725 725 category='error')
726 726 data = render('rhodecode:templates/admin/settings/settings.mako',
727 727 self._get_template_context(c), self.request)
728 728 html = formencode.htmlfill.render(
729 729 data,
730 730 defaults=errors.value,
731 731 errors=errors.error_dict or {},
732 732 prefix_error=False,
733 733 encoding="UTF-8",
734 734 force_defaults=False
735 735 )
736 736 return Response(html)
737 737
738 738 try:
739 739 session = Session()
740 740 for setting in _LAB_SETTINGS:
741 741 setting_name = setting.key[len('rhodecode_'):]
742 742 sett = SettingsModel().create_or_update_setting(
743 743 setting_name, form_result[setting.key], setting.type)
744 744 session.add(sett)
745 745
746 746 except Exception:
747 747 log.exception('Exception while updating lab settings')
748 748 h.flash(_('Error occurred during updating labs settings'),
749 749 category='error')
750 750 else:
751 751 Session().commit()
752 752 SettingsModel().invalidate_settings_cache()
753 753 h.flash(_('Updated Labs settings'), category='success')
754 754 raise HTTPFound(h.route_path('admin_settings_labs'))
755 755
756 756 data = render('rhodecode:templates/admin/settings/settings.mako',
757 757 self._get_template_context(c), self.request)
758 758 html = formencode.htmlfill.render(
759 759 data,
760 760 defaults=self._form_defaults(),
761 761 encoding="UTF-8",
762 762 force_defaults=False
763 763 )
764 764 return Response(html)
765 765
766 766
767 767 # :param key: name of the setting including the 'rhodecode_' prefix
768 768 # :param type: the RhodeCodeSetting type to use.
769 769 # :param group: the i18ned group in which we should dispaly this setting
770 770 # :param label: the i18ned label we should display for this setting
771 771 # :param help: the i18ned help we should dispaly for this setting
772 772 LabSetting = collections.namedtuple(
773 773 'LabSetting', ('key', 'type', 'group', 'label', 'help'))
774 774
775 775
776 776 # This list has to be kept in sync with the form
777 777 # rhodecode.model.forms.LabsSettingsForm.
778 778 _LAB_SETTINGS = [
779 779
780 780 ]
@@ -1,462 +1,534 b''
1 1 # -*- coding: utf-8 -*-
2 2
3 3 # Copyright (C) 2016-2018 RhodeCode GmbH
4 4 #
5 5 # This program is free software: you can redistribute it and/or modify
6 6 # it under the terms of the GNU Affero General Public License, version 3
7 7 # (only), as published by the Free Software Foundation.
8 8 #
9 9 # This program is distributed in the hope that it will be useful,
10 10 # but WITHOUT ANY WARRANTY; without even the implied warranty of
11 11 # MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
12 12 # GNU General Public License for more details.
13 13 #
14 14 # You should have received a copy of the GNU Affero General Public License
15 15 # along with this program. If not, see <http://www.gnu.org/licenses/>.
16 16 #
17 17 # This program is dual-licensed. If you wish to learn more about the
18 18 # RhodeCode Enterprise Edition, including its added features, Support services,
19 19 # and proprietary license terms, please see https://rhodecode.com/licenses/
20 20
21 21 import re
22 22 import logging
23 23 import collections
24 24
25 25 from pyramid.view import view_config
26 26
27 27 from rhodecode.apps._base import BaseAppView
28 28 from rhodecode.lib import helpers as h
29 29 from rhodecode.lib.auth import (
30 30 LoginRequired, NotAnonymous, HasRepoGroupPermissionAnyDecorator,
31 31 CSRFRequired)
32 32 from rhodecode.lib.index import searcher_from_config
33 33 from rhodecode.lib.utils2 import safe_unicode, str2bool, safe_int
34 34 from rhodecode.lib.ext_json import json
35 35 from rhodecode.model.db import (
36 36 func, true, or_, in_filter_generator, Repository, RepoGroup, User, UserGroup)
37 37 from rhodecode.model.repo import RepoModel
38 38 from rhodecode.model.repo_group import RepoGroupModel
39 39 from rhodecode.model.scm import RepoGroupList, RepoList
40 40 from rhodecode.model.user import UserModel
41 41 from rhodecode.model.user_group import UserGroupModel
42 42
43 43 log = logging.getLogger(__name__)
44 44
45 45
46 46 class HomeView(BaseAppView):
47 47
48 48 def load_default_context(self):
49 49 c = self._get_local_tmpl_context()
50 50 c.user = c.auth_user.get_instance()
51 51
52 52 return c
53 53
54 54 @LoginRequired()
55 55 @view_config(
56 56 route_name='user_autocomplete_data', request_method='GET',
57 57 renderer='json_ext', xhr=True)
58 58 def user_autocomplete_data(self):
59 59 self.load_default_context()
60 60 query = self.request.GET.get('query')
61 61 active = str2bool(self.request.GET.get('active') or True)
62 62 include_groups = str2bool(self.request.GET.get('user_groups'))
63 63 expand_groups = str2bool(self.request.GET.get('user_groups_expand'))
64 64 skip_default_user = str2bool(self.request.GET.get('skip_default_user'))
65 65
66 66 log.debug('generating user list, query:%s, active:%s, with_groups:%s',
67 67 query, active, include_groups)
68 68
69 69 _users = UserModel().get_users(
70 70 name_contains=query, only_active=active)
71 71
72 72 def maybe_skip_default_user(usr):
73 73 if skip_default_user and usr['username'] == UserModel.cls.DEFAULT_USER:
74 74 return False
75 75 return True
76 76 _users = filter(maybe_skip_default_user, _users)
77 77
78 78 if include_groups:
79 79 # extend with user groups
80 80 _user_groups = UserGroupModel().get_user_groups(
81 81 name_contains=query, only_active=active,
82 82 expand_groups=expand_groups)
83 83 _users = _users + _user_groups
84 84
85 85 return {'suggestions': _users}
86 86
87 87 @LoginRequired()
88 88 @NotAnonymous()
89 89 @view_config(
90 90 route_name='user_group_autocomplete_data', request_method='GET',
91 91 renderer='json_ext', xhr=True)
92 92 def user_group_autocomplete_data(self):
93 93 self.load_default_context()
94 94 query = self.request.GET.get('query')
95 95 active = str2bool(self.request.GET.get('active') or True)
96 96 expand_groups = str2bool(self.request.GET.get('user_groups_expand'))
97 97
98 98 log.debug('generating user group list, query:%s, active:%s',
99 99 query, active)
100 100
101 101 _user_groups = UserGroupModel().get_user_groups(
102 102 name_contains=query, only_active=active,
103 103 expand_groups=expand_groups)
104 104 _user_groups = _user_groups
105 105
106 106 return {'suggestions': _user_groups}
107 107
108 108 def _get_repo_list(self, name_contains=None, repo_type=None, limit=20):
109 109 org_query = name_contains
110 110 allowed_ids = self._rhodecode_user.repo_acl_ids(
111 111 ['repository.read', 'repository.write', 'repository.admin'],
112 112 cache=False, name_filter=name_contains) or [-1]
113 113
114 114 query = Repository.query()\
115 115 .order_by(func.length(Repository.repo_name))\
116 116 .order_by(Repository.repo_name)\
117 117 .filter(Repository.archived.isnot(true()))\
118 118 .filter(or_(
119 119 # generate multiple IN to fix limitation problems
120 120 *in_filter_generator(Repository.repo_id, allowed_ids)
121 121 ))
122 122
123 123 if repo_type:
124 124 query = query.filter(Repository.repo_type == repo_type)
125 125
126 126 if name_contains:
127 127 ilike_expression = u'%{}%'.format(safe_unicode(name_contains))
128 128 query = query.filter(
129 129 Repository.repo_name.ilike(ilike_expression))
130 130 query = query.limit(limit)
131 131
132 132 acl_iter = query
133 133
134 134 return [
135 135 {
136 136 'id': obj.repo_name,
137 137 'value': org_query,
138 138 'value_display': obj.repo_name,
139 139 'text': obj.repo_name,
140 140 'type': 'repo',
141 141 'repo_id': obj.repo_id,
142 142 'repo_type': obj.repo_type,
143 143 'private': obj.private,
144 144 'url': h.route_path('repo_summary', repo_name=obj.repo_name)
145 145 }
146 146 for obj in acl_iter]
147 147
148 148 def _get_repo_group_list(self, name_contains=None, limit=20):
149 149 org_query = name_contains
150 150 allowed_ids = self._rhodecode_user.repo_group_acl_ids(
151 151 ['group.read', 'group.write', 'group.admin'],
152 152 cache=False, name_filter=name_contains) or [-1]
153 153
154 154 query = RepoGroup.query()\
155 155 .order_by(func.length(RepoGroup.group_name))\
156 156 .order_by(RepoGroup.group_name) \
157 157 .filter(or_(
158 158 # generate multiple IN to fix limitation problems
159 159 *in_filter_generator(RepoGroup.group_id, allowed_ids)
160 160 ))
161 161
162 162 if name_contains:
163 163 ilike_expression = u'%{}%'.format(safe_unicode(name_contains))
164 164 query = query.filter(
165 165 RepoGroup.group_name.ilike(ilike_expression))
166 166 query = query.limit(limit)
167 167
168 168 acl_iter = query
169 169
170 170 return [
171 171 {
172 172 'id': obj.group_name,
173 173 'value': org_query,
174 174 'value_display': obj.group_name,
175 175 'type': 'repo_group',
176 176 'url': h.route_path(
177 177 'repo_group_home', repo_group_name=obj.group_name)
178 178 }
179 179 for obj in acl_iter]
180 180
181 181 def _get_user_list(self, name_contains=None, limit=20):
182 182 org_query = name_contains
183 183 if not name_contains:
184 184 return []
185 185
186 186 name_contains = re.compile('(?:user:)(.+)').findall(name_contains)
187 187 if len(name_contains) != 1:
188 188 return []
189 189 name_contains = name_contains[0]
190 190
191 191 query = User.query()\
192 192 .order_by(func.length(User.username))\
193 193 .order_by(User.username) \
194 194 .filter(User.username != User.DEFAULT_USER)
195 195
196 196 if name_contains:
197 197 ilike_expression = u'%{}%'.format(safe_unicode(name_contains))
198 198 query = query.filter(
199 199 User.username.ilike(ilike_expression))
200 200 query = query.limit(limit)
201 201
202 202 acl_iter = query
203 203
204 204 return [
205 205 {
206 206 'id': obj.user_id,
207 207 'value': org_query,
208 208 'value_display': obj.username,
209 209 'type': 'user',
210 210 'icon_link': h.gravatar_url(obj.email, 30),
211 211 'url': h.route_path(
212 212 'user_profile', username=obj.username)
213 213 }
214 214 for obj in acl_iter]
215 215
216 216 def _get_user_groups_list(self, name_contains=None, limit=20):
217 217 org_query = name_contains
218 218 if not name_contains:
219 219 return []
220 220
221 221 name_contains = re.compile('(?:user_group:)(.+)').findall(name_contains)
222 222 if len(name_contains) != 1:
223 223 return []
224 224 name_contains = name_contains[0]
225 225
226 226 query = UserGroup.query()\
227 227 .order_by(func.length(UserGroup.users_group_name))\
228 228 .order_by(UserGroup.users_group_name)
229 229
230 230 if name_contains:
231 231 ilike_expression = u'%{}%'.format(safe_unicode(name_contains))
232 232 query = query.filter(
233 233 UserGroup.users_group_name.ilike(ilike_expression))
234 234 query = query.limit(limit)
235 235
236 236 acl_iter = query
237 237
238 238 return [
239 239 {
240 240 'id': obj.users_group_id,
241 241 'value': org_query,
242 242 'value_display': obj.users_group_name,
243 243 'type': 'user_group',
244 244 'url': h.route_path(
245 245 'user_group_profile', user_group_name=obj.users_group_name)
246 246 }
247 247 for obj in acl_iter]
248 248
249 def _get_hash_commit_list(self, auth_user, query):
249 def _get_hash_commit_list(self, auth_user, searcher, query):
250 250 org_query = query
251 if not query or len(query) < 3:
251 if not query or len(query) < 3 or not searcher:
252 252 return []
253 253
254 254 commit_hashes = re.compile('(?:commit:)([0-9a-f]{2,40})').findall(query)
255 255
256 256 if len(commit_hashes) != 1:
257 257 return []
258 258 commit_hash = commit_hashes[0]
259 259
260 searcher = searcher_from_config(self.request.registry.settings)
261 260 result = searcher.search(
262 'commit_id:%s*' % commit_hash, 'commit', auth_user,
261 'commit_id:{}*'.format(commit_hash), 'commit', auth_user,
263 262 raise_on_exc=False)
264 263
265 264 return [
266 265 {
267 266 'id': entry['commit_id'],
268 267 'value': org_query,
269 268 'value_display': 'repo `{}` commit: {}'.format(
270 269 entry['repository'], entry['commit_id']),
271 270 'type': 'commit',
272 271 'repo': entry['repository'],
273 272 'url': h.route_path(
274 273 'repo_commit',
275 274 repo_name=entry['repository'], commit_id=entry['commit_id'])
276 275 }
277 276 for entry in result['results']]
278 277
279 278 @LoginRequired()
280 279 @view_config(
281 280 route_name='repo_list_data', request_method='GET',
282 281 renderer='json_ext', xhr=True)
283 282 def repo_list_data(self):
284 283 _ = self.request.translate
285 284 self.load_default_context()
286 285
287 286 query = self.request.GET.get('query')
288 287 repo_type = self.request.GET.get('repo_type')
289 288 log.debug('generating repo list, query:%s, repo_type:%s',
290 289 query, repo_type)
291 290
292 291 res = []
293 292 repos = self._get_repo_list(query, repo_type=repo_type)
294 293 if repos:
295 294 res.append({
296 295 'text': _('Repositories'),
297 296 'children': repos
298 297 })
299 298
300 299 data = {
301 300 'more': False,
302 301 'results': res
303 302 }
304 303 return data
305 304
305 def _get_default_search_queries(self, search_context, searcher, query):
306 if not searcher:
307 return []
308 is_es_6 = searcher.is_es_6
309
310 queries = []
311 repo_group_name, repo_name, repo_context = None, None, None
312
313 # repo group context
314 if search_context.get('search_context[repo_group_name]'):
315 repo_group_name = search_context.get('search_context[repo_group_name]')
316 if search_context.get('search_context[repo_name]'):
317 repo_name = search_context.get('search_context[repo_name]')
318 repo_context = search_context.get('search_context[repo_view_type]')
319
320 if is_es_6 and repo_name:
321 def query_modifier():
322 qry = '{} repo_name.raw:{} '.format(
323 query, searcher.escape_specials(repo_name))
324 return {'q': qry, 'type': 'content'}
325 label = u'Search for `{}` through files in this repository.'.format(query)
326 queries.append(
327 {
328 'id': -10,
329 'value': query,
330 'value_display': label,
331 'type': 'search',
332 'url': h.route_path(
333 'search_repo', repo_name=repo_name, _query=query_modifier())
334 }
335 )
336
337 def query_modifier():
338 qry = '{} repo_name.raw:{} '.format(
339 query, searcher.escape_specials(repo_name))
340 return {'q': qry, 'type': 'commit'}
341 label = u'Search for `{}` through commits in this repository.'.format(query)
342 queries.append(
343 {
344 'id': -10,
345 'value': query,
346 'value_display': label,
347 'type': 'search',
348 'url': h.route_path(
349 'search_repo', repo_name=repo_name, _query=query_modifier())
350 }
351 )
352
353 elif is_es_6 and repo_group_name:
354 def query_modifier():
355 qry = '{} repo_name.raw:{} '.format(
356 query, searcher.escape_specials(repo_group_name + '/*'))
357 return {'q': qry, 'type': 'content'}
358 label = u'Search for `{}` through files in this repository group'.format(query)
359 queries.append(
360 {
361 'id': -20,
362 'value': query,
363 'value_display': label,
364 'type': 'search',
365 'url': h.route_path('search', _query=query_modifier())
366 }
367 )
368
369 if not queries:
370 queries.append(
371 {
372 'id': -1,
373 'value': query,
374 'value_display': u'Search for: `{}`'.format(query),
375 'type': 'search',
376 'url': h.route_path('search',
377 _query={'q': query, 'type': 'content'})
378 }
379 )
380
381 return queries
382
306 383 @LoginRequired()
307 384 @view_config(
308 385 route_name='goto_switcher_data', request_method='GET',
309 386 renderer='json_ext', xhr=True)
310 387 def goto_switcher_data(self):
311 388 c = self.load_default_context()
312 389
313 390 _ = self.request.translate
314 391
315 392 query = self.request.GET.get('query')
316 393 log.debug('generating main filter data, query %s', query)
317 394
318 default_search_val = u'Full text search for: `{}`'.format(query)
319 395 res = []
320 396 if not query:
321 397 return {'suggestions': res}
322 398
323 res.append({
324 'id': -1,
325 'value': query,
326 'value_display': default_search_val,
327 'type': 'search',
328 'url': h.route_path(
329 'search', _query={'q': query})
330 })
331 repo_group_id = safe_int(self.request.GET.get('repo_group_id'))
399 searcher = searcher_from_config(self.request.registry.settings)
400 for _q in self._get_default_search_queries(self.request.GET, searcher, query):
401 res.append(_q)
402
403 repo_group_id = safe_int(self.request.GET.get('search_context[repo_group_id]'))
332 404 if repo_group_id:
333 405 repo_group = RepoGroup.get(repo_group_id)
334 406 composed_hint = '{}/{}'.format(repo_group.group_name, query)
335 407 show_hint = not query.startswith(repo_group.group_name)
336 408 if repo_group and show_hint:
337 hint = u'Group search: `{}`'.format(composed_hint)
409 hint = u'Repository search inside: `{}`'.format(composed_hint)
338 410 res.append({
339 411 'id': -1,
340 412 'value': composed_hint,
341 413 'value_display': hint,
342 414 'type': 'hint',
343 415 'url': ""
344 416 })
345 417
346 418 repo_groups = self._get_repo_group_list(query)
347 419 for serialized_repo_group in repo_groups:
348 420 res.append(serialized_repo_group)
349 421
350 422 repos = self._get_repo_list(query)
351 423 for serialized_repo in repos:
352 424 res.append(serialized_repo)
353 425
354 # TODO(marcink): permissions for that ?
426 # TODO(marcink): should all logged in users be allowed to search others?
355 427 allowed_user_search = self._rhodecode_user.username != User.DEFAULT_USER
356 428 if allowed_user_search:
357 429 users = self._get_user_list(query)
358 430 for serialized_user in users:
359 431 res.append(serialized_user)
360 432
361 433 user_groups = self._get_user_groups_list(query)
362 434 for serialized_user_group in user_groups:
363 435 res.append(serialized_user_group)
364 436
365 commits = self._get_hash_commit_list(c.auth_user, query)
437 commits = self._get_hash_commit_list(c.auth_user, searcher, query)
366 438 if commits:
367 439 unique_repos = collections.OrderedDict()
368 440 for commit in commits:
369 441 repo_name = commit['repo']
370 442 unique_repos.setdefault(repo_name, []).append(commit)
371 443
372 444 for repo, commits in unique_repos.items():
373 445 for commit in commits:
374 446 res.append(commit)
375 447
376 448 return {'suggestions': res}
377 449
378 450 def _get_groups_and_repos(self, repo_group_id=None):
379 451 # repo groups groups
380 452 repo_group_list = RepoGroup.get_all_repo_groups(group_id=repo_group_id)
381 453 _perms = ['group.read', 'group.write', 'group.admin']
382 454 repo_group_list_acl = RepoGroupList(repo_group_list, perm_set=_perms)
383 455 repo_group_data = RepoGroupModel().get_repo_groups_as_dict(
384 456 repo_group_list=repo_group_list_acl, admin=False)
385 457
386 458 # repositories
387 459 repo_list = Repository.get_all_repos(group_id=repo_group_id)
388 460 _perms = ['repository.read', 'repository.write', 'repository.admin']
389 461 repo_list_acl = RepoList(repo_list, perm_set=_perms)
390 462 repo_data = RepoModel().get_repos_as_dict(
391 463 repo_list=repo_list_acl, admin=False)
392 464
393 465 return repo_data, repo_group_data
394 466
395 467 @LoginRequired()
396 468 @view_config(
397 469 route_name='home', request_method='GET',
398 470 renderer='rhodecode:templates/index.mako')
399 471 def main_page(self):
400 472 c = self.load_default_context()
401 473 c.repo_group = None
402 474
403 475 repo_data, repo_group_data = self._get_groups_and_repos()
404 476 # json used to render the grids
405 477 c.repos_data = json.dumps(repo_data)
406 478 c.repo_groups_data = json.dumps(repo_group_data)
407 479
408 480 return self._get_template_context(c)
409 481
410 482 @LoginRequired()
411 483 @HasRepoGroupPermissionAnyDecorator(
412 484 'group.read', 'group.write', 'group.admin')
413 485 @view_config(
414 486 route_name='repo_group_home', request_method='GET',
415 487 renderer='rhodecode:templates/index_repo_group.mako')
416 488 @view_config(
417 489 route_name='repo_group_home_slash', request_method='GET',
418 490 renderer='rhodecode:templates/index_repo_group.mako')
419 491 def repo_group_main_page(self):
420 492 c = self.load_default_context()
421 493 c.repo_group = self.request.db_repo_group
422 494 repo_data, repo_group_data = self._get_groups_and_repos(
423 495 c.repo_group.group_id)
424 496
425 497 # json used to render the grids
426 498 c.repos_data = json.dumps(repo_data)
427 499 c.repo_groups_data = json.dumps(repo_group_data)
428 500
429 501 return self._get_template_context(c)
430 502
431 503 @LoginRequired()
432 504 @CSRFRequired()
433 505 @view_config(
434 506 route_name='markup_preview', request_method='POST',
435 507 renderer='string', xhr=True)
436 508 def markup_preview(self):
437 509 # Technically a CSRF token is not needed as no state changes with this
438 510 # call. However, as this is a POST is better to have it, so automated
439 511 # tools don't flag it as potential CSRF.
440 512 # Post is required because the payload could be bigger than the maximum
441 513 # allowed by GET.
442 514
443 515 text = self.request.POST.get('text')
444 516 renderer = self.request.POST.get('renderer') or 'rst'
445 517 if text:
446 518 return h.render(text, renderer=renderer, mentions=True)
447 519 return ''
448 520
449 521 @LoginRequired()
450 522 @CSRFRequired()
451 523 @view_config(
452 524 route_name='store_user_session_value', request_method='POST',
453 525 renderer='string', xhr=True)
454 526 def store_user_session_attr(self):
455 527 key = self.request.POST.get('key')
456 528 val = self.request.POST.get('val')
457 529
458 530 existing_value = self.request.session.get(key)
459 531 if existing_value != val:
460 532 self.request.session[key] = val
461 533
462 534 return 'stored:{}'.format(key)
@@ -1,134 +1,138 b''
1 1 # -*- coding: utf-8 -*-
2 2
3 3 # Copyright (C) 2011-2018 RhodeCode GmbH
4 4 #
5 5 # This program is free software: you can redistribute it and/or modify
6 6 # it under the terms of the GNU Affero General Public License, version 3
7 7 # (only), as published by the Free Software Foundation.
8 8 #
9 9 # This program is distributed in the hope that it will be useful,
10 10 # but WITHOUT ANY WARRANTY; without even the implied warranty of
11 11 # MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
12 12 # GNU General Public License for more details.
13 13 #
14 14 # You should have received a copy of the GNU Affero General Public License
15 15 # along with this program. If not, see <http://www.gnu.org/licenses/>.
16 16 #
17 17 # This program is dual-licensed. If you wish to learn more about the
18 18 # RhodeCode Enterprise Edition, including its added features, Support services,
19 19 # and proprietary license terms, please see https://rhodecode.com/licenses/
20 20
21 21 import logging
22 22 import urllib
23 23 from pyramid.view import view_config
24 24 from webhelpers.util import update_params
25 25
26 26 from rhodecode.apps._base import BaseAppView, RepoAppView
27 27 from rhodecode.lib.auth import (LoginRequired, HasRepoPermissionAnyDecorator)
28 28 from rhodecode.lib.helpers import Page
29 29 from rhodecode.lib.utils2 import safe_str
30 30 from rhodecode.lib.index import searcher_from_config
31 31 from rhodecode.model import validation_schema
32 32 from rhodecode.model.validation_schema.schemas import search_schema
33 33
34 34 log = logging.getLogger(__name__)
35 35
36 36
37 37 def search(request, tmpl_context, repo_name):
38 38 searcher = searcher_from_config(request.registry.settings)
39 39 formatted_results = []
40 40 execution_time = ''
41 41
42 42 schema = search_schema.SearchParamsSchema()
43 43
44 44 search_params = {}
45 45 errors = []
46 46 try:
47 47 search_params = schema.deserialize(
48 dict(search_query=request.GET.get('q'),
49 search_type=request.GET.get('type'),
50 search_sort=request.GET.get('sort'),
51 page_limit=request.GET.get('page_limit'),
52 requested_page=request.GET.get('page'))
48 dict(
49 search_query=request.GET.get('q'),
50 search_type=request.GET.get('type'),
51 search_sort=request.GET.get('sort'),
52 search_max_lines=request.GET.get('max_lines'),
53 page_limit=request.GET.get('page_limit'),
54 requested_page=request.GET.get('page'),
55 )
53 56 )
54 57 except validation_schema.Invalid as e:
55 58 errors = e.children
56 59
57 60 def url_generator(**kw):
58 61 q = urllib.quote(safe_str(search_query))
59 62 return update_params(
60 "?q=%s&type=%s" % (q, safe_str(search_type)), **kw)
63 "?q=%s&type=%s&max_lines=%s" % (q, safe_str(search_type), search_max_lines), **kw)
61 64
62 65 c = tmpl_context
63 66 search_query = search_params.get('search_query')
64 67 search_type = search_params.get('search_type')
65 68 search_sort = search_params.get('search_sort')
69 search_max_lines = search_params.get('search_max_lines')
66 70 if search_params.get('search_query'):
67 71 page_limit = search_params['page_limit']
68 72 requested_page = search_params['requested_page']
69 73
70 74 try:
71 75 search_result = searcher.search(
72 76 search_query, search_type, c.auth_user, repo_name,
73 77 requested_page, page_limit, search_sort)
74 78
75 79 formatted_results = Page(
76 80 search_result['results'], page=requested_page,
77 81 item_count=search_result['count'],
78 82 items_per_page=page_limit, url=url_generator)
79 83 finally:
80 84 searcher.cleanup()
81 85
82 86 if not search_result['error']:
83 87 execution_time = '%s results (%.3f seconds)' % (
84 88 search_result['count'],
85 89 search_result['runtime'])
86 90 elif not errors:
87 91 node = schema['search_query']
88 92 errors = [
89 93 validation_schema.Invalid(node, search_result['error'])]
90 94
91 95 c.perm_user = c.auth_user
92 96 c.repo_name = repo_name
93 97 c.sort = search_sort
94 98 c.url_generator = url_generator
95 99 c.errors = errors
96 100 c.formatted_results = formatted_results
97 101 c.runtime = execution_time
98 102 c.cur_query = search_query
99 103 c.search_type = search_type
100 104 c.searcher = searcher
101 105
102 106
103 107 class SearchView(BaseAppView):
104 108 def load_default_context(self):
105 109 c = self._get_local_tmpl_context()
106 110
107 111 return c
108 112
109 113 @LoginRequired()
110 114 @view_config(
111 115 route_name='search', request_method='GET',
112 116 renderer='rhodecode:templates/search/search.mako')
113 117 def search(self):
114 118 c = self.load_default_context()
115 119 search(self.request, c, repo_name=None)
116 120 return self._get_template_context(c)
117 121
118 122
119 123 class SearchRepoView(RepoAppView):
120 124 def load_default_context(self):
121 125 c = self._get_local_tmpl_context()
122 126
123 127 return c
124 128
125 129 @LoginRequired()
126 130 @HasRepoPermissionAnyDecorator(
127 131 'repository.read', 'repository.write', 'repository.admin')
128 132 @view_config(
129 133 route_name='search_repo', request_method='GET',
130 134 renderer='rhodecode:templates/search/search.mako')
131 135 def search_repo(self):
132 136 c = self.load_default_context()
133 137 search(self.request, c, repo_name=self.db_repo_name)
134 138 return self._get_template_context(c)
@@ -1,2109 +1,2017 b''
1 1 # -*- coding: utf-8 -*-
2 2
3 3 # Copyright (C) 2010-2018 RhodeCode GmbH
4 4 #
5 5 # This program is free software: you can redistribute it and/or modify
6 6 # it under the terms of the GNU Affero General Public License, version 3
7 7 # (only), as published by the Free Software Foundation.
8 8 #
9 9 # This program is distributed in the hope that it will be useful,
10 10 # but WITHOUT ANY WARRANTY; without even the implied warranty of
11 11 # MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
12 12 # GNU General Public License for more details.
13 13 #
14 14 # You should have received a copy of the GNU Affero General Public License
15 15 # along with this program. If not, see <http://www.gnu.org/licenses/>.
16 16 #
17 17 # This program is dual-licensed. If you wish to learn more about the
18 18 # RhodeCode Enterprise Edition, including its added features, Support services,
19 19 # and proprietary license terms, please see https://rhodecode.com/licenses/
20 20
21 21 """
22 22 Helper functions
23 23
24 24 Consists of functions to typically be used within templates, but also
25 25 available to Controllers. This module is available to both as 'h'.
26 26 """
27 27
28 28 import os
29 29 import random
30 30 import hashlib
31 31 import StringIO
32 32 import textwrap
33 33 import urllib
34 34 import math
35 35 import logging
36 36 import re
37 37 import urlparse
38 38 import time
39 39 import string
40 40 import hashlib
41 41 from collections import OrderedDict
42 42
43 43 import pygments
44 44 import itertools
45 45 import fnmatch
46 46 import bleach
47 47
48 48 from datetime import datetime
49 49 from functools import partial
50 50 from pygments.formatters.html import HtmlFormatter
51 from pygments import highlight as code_highlight
52 51 from pygments.lexers import (
53 52 get_lexer_by_name, get_lexer_for_filename, get_lexer_for_mimetype)
54 53
55 54 from pyramid.threadlocal import get_current_request
56 55
57 56 from webhelpers.html import literal, HTML, escape
58 57 from webhelpers.html.tools import *
59 58 from webhelpers.html.builder import make_tag
60 59 from webhelpers.html.tags import auto_discovery_link, checkbox, css_classes, \
61 60 end_form, file, form as wh_form, hidden, image, javascript_link, link_to, \
62 61 link_to_if, link_to_unless, ol, required_legend, select, stylesheet_link, \
63 62 submit, text, password, textarea, title, ul, xml_declaration, radio
64 63 from webhelpers.html.tools import auto_link, button_to, highlight, \
65 64 js_obfuscate, mail_to, strip_links, strip_tags, tag_re
66 65 from webhelpers.text import chop_at, collapse, convert_accented_entities, \
67 66 convert_misc_entities, lchop, plural, rchop, remove_formatting, \
68 67 replace_whitespace, urlify, truncate, wrap_paragraphs
69 68 from webhelpers.date import time_ago_in_words
70 69 from webhelpers.paginate import Page as _Page
71 70 from webhelpers.html.tags import _set_input_attrs, _set_id_attr, \
72 71 convert_boolean_attrs, NotGiven, _make_safe_id_component
73 72 from webhelpers2.number import format_byte_size
74 73
75 74 from rhodecode.lib.action_parser import action_parser
76 75 from rhodecode.lib.ext_json import json
77 76 from rhodecode.lib.utils import repo_name_slug, get_custom_lexer
78 77 from rhodecode.lib.utils2 import str2bool, safe_unicode, safe_str, \
79 78 get_commit_safe, datetime_to_time, time_to_datetime, time_to_utcdatetime, \
80 79 AttributeDict, safe_int, md5, md5_safe
81 80 from rhodecode.lib.markup_renderer import MarkupRenderer, relative_links
82 81 from rhodecode.lib.vcs.exceptions import CommitDoesNotExistError
83 82 from rhodecode.lib.vcs.backends.base import BaseChangeset, EmptyCommit
83 from rhodecode.lib.index.search_utils import get_matching_line_offsets
84 84 from rhodecode.config.conf import DATE_FORMAT, DATETIME_FORMAT
85 85 from rhodecode.model.changeset_status import ChangesetStatusModel
86 86 from rhodecode.model.db import Permission, User, Repository
87 87 from rhodecode.model.repo_group import RepoGroupModel
88 88 from rhodecode.model.settings import IssueTrackerSettingsModel
89 89
90
90 91 log = logging.getLogger(__name__)
91 92
92 93
93 94 DEFAULT_USER = User.DEFAULT_USER
94 95 DEFAULT_USER_EMAIL = User.DEFAULT_USER_EMAIL
95 96
96 97
97 98 def asset(path, ver=None, **kwargs):
98 99 """
99 100 Helper to generate a static asset file path for rhodecode assets
100 101
101 102 eg. h.asset('images/image.png', ver='3923')
102 103
103 104 :param path: path of asset
104 105 :param ver: optional version query param to append as ?ver=
105 106 """
106 107 request = get_current_request()
107 108 query = {}
108 109 query.update(kwargs)
109 110 if ver:
110 111 query = {'ver': ver}
111 112 return request.static_path(
112 113 'rhodecode:public/{}'.format(path), _query=query)
113 114
114 115
115 116 default_html_escape_table = {
116 117 ord('&'): u'&amp;',
117 118 ord('<'): u'&lt;',
118 119 ord('>'): u'&gt;',
119 120 ord('"'): u'&quot;',
120 121 ord("'"): u'&#39;',
121 122 }
122 123
123 124
124 125 def html_escape(text, html_escape_table=default_html_escape_table):
125 126 """Produce entities within text."""
126 127 return text.translate(html_escape_table)
127 128
128 129
129 130 def chop_at_smart(s, sub, inclusive=False, suffix_if_chopped=None):
130 131 """
131 132 Truncate string ``s`` at the first occurrence of ``sub``.
132 133
133 134 If ``inclusive`` is true, truncate just after ``sub`` rather than at it.
134 135 """
135 136 suffix_if_chopped = suffix_if_chopped or ''
136 137 pos = s.find(sub)
137 138 if pos == -1:
138 139 return s
139 140
140 141 if inclusive:
141 142 pos += len(sub)
142 143
143 144 chopped = s[:pos]
144 145 left = s[pos:].strip()
145 146
146 147 if left and suffix_if_chopped:
147 148 chopped += suffix_if_chopped
148 149
149 150 return chopped
150 151
151 152
152 153 def shorter(text, size=20):
153 154 postfix = '...'
154 155 if len(text) > size:
155 156 return text[:size - len(postfix)] + postfix
156 157 return text
157 158
158 159
159 160 def _reset(name, value=None, id=NotGiven, type="reset", **attrs):
160 161 """
161 162 Reset button
162 163 """
163 164 _set_input_attrs(attrs, type, name, value)
164 165 _set_id_attr(attrs, id, name)
165 166 convert_boolean_attrs(attrs, ["disabled"])
166 167 return HTML.input(**attrs)
167 168
168 169 reset = _reset
169 170 safeid = _make_safe_id_component
170 171
171 172
172 173 def branding(name, length=40):
173 174 return truncate(name, length, indicator="")
174 175
175 176
176 177 def FID(raw_id, path):
177 178 """
178 179 Creates a unique ID for filenode based on it's hash of path and commit
179 180 it's safe to use in urls
180 181
181 182 :param raw_id:
182 183 :param path:
183 184 """
184 185
185 186 return 'c-%s-%s' % (short_id(raw_id), md5_safe(path)[:12])
186 187
187 188
188 189 class _GetError(object):
189 190 """Get error from form_errors, and represent it as span wrapped error
190 191 message
191 192
192 193 :param field_name: field to fetch errors for
193 194 :param form_errors: form errors dict
194 195 """
195 196
196 197 def __call__(self, field_name, form_errors):
197 198 tmpl = """<span class="error_msg">%s</span>"""
198 199 if form_errors and field_name in form_errors:
199 200 return literal(tmpl % form_errors.get(field_name))
200 201
201 202 get_error = _GetError()
202 203
203 204
204 205 class _ToolTip(object):
205 206
206 207 def __call__(self, tooltip_title, trim_at=50):
207 208 """
208 209 Special function just to wrap our text into nice formatted
209 210 autowrapped text
210 211
211 212 :param tooltip_title:
212 213 """
213 214 tooltip_title = escape(tooltip_title)
214 215 tooltip_title = tooltip_title.replace('<', '&lt;').replace('>', '&gt;')
215 216 return tooltip_title
216 217 tooltip = _ToolTip()
217 218
218 219
219 220 def files_breadcrumbs(repo_name, commit_id, file_path):
220 221 if isinstance(file_path, str):
221 222 file_path = safe_unicode(file_path)
222 223
223 224 # TODO: johbo: Is this always a url like path, or is this operating
224 225 # system dependent?
225 226 path_segments = file_path.split('/')
226 227
227 228 repo_name_html = escape(repo_name)
228 229 if len(path_segments) == 1 and path_segments[0] == '':
229 230 url_segments = [repo_name_html]
230 231 else:
231 232 url_segments = [
232 233 link_to(
233 234 repo_name_html,
234 235 route_path(
235 236 'repo_files',
236 237 repo_name=repo_name,
237 238 commit_id=commit_id,
238 239 f_path=''),
239 240 class_='pjax-link')]
240 241
241 242 last_cnt = len(path_segments) - 1
242 243 for cnt, segment in enumerate(path_segments):
243 244 if not segment:
244 245 continue
245 246 segment_html = escape(segment)
246 247
247 248 if cnt != last_cnt:
248 249 url_segments.append(
249 250 link_to(
250 251 segment_html,
251 252 route_path(
252 253 'repo_files',
253 254 repo_name=repo_name,
254 255 commit_id=commit_id,
255 256 f_path='/'.join(path_segments[:cnt + 1])),
256 257 class_='pjax-link'))
257 258 else:
258 259 url_segments.append(segment_html)
259 260
260 261 return literal('/'.join(url_segments))
261 262
262 263
264 def code_highlight(code, lexer, formatter, use_hl_filter=False):
265 """
266 Lex ``code`` with ``lexer`` and format it with the formatter ``formatter``.
267
268 If ``outfile`` is given and a valid file object (an object
269 with a ``write`` method), the result will be written to it, otherwise
270 it is returned as a string.
271 """
272 if use_hl_filter:
273 # add HL filter
274 from rhodecode.lib.index import search_utils
275 lexer.add_filter(search_utils.ElasticSearchHLFilter())
276 return pygments.format(pygments.lex(code, lexer), formatter)
277
278
263 279 class CodeHtmlFormatter(HtmlFormatter):
264 280 """
265 281 My code Html Formatter for source codes
266 282 """
267 283
268 284 def wrap(self, source, outfile):
269 285 return self._wrap_div(self._wrap_pre(self._wrap_code(source)))
270 286
271 287 def _wrap_code(self, source):
272 288 for cnt, it in enumerate(source):
273 289 i, t = it
274 290 t = '<div id="L%s">%s</div>' % (cnt + 1, t)
275 291 yield i, t
276 292
277 293 def _wrap_tablelinenos(self, inner):
278 294 dummyoutfile = StringIO.StringIO()
279 295 lncount = 0
280 296 for t, line in inner:
281 297 if t:
282 298 lncount += 1
283 299 dummyoutfile.write(line)
284 300
285 301 fl = self.linenostart
286 302 mw = len(str(lncount + fl - 1))
287 303 sp = self.linenospecial
288 304 st = self.linenostep
289 305 la = self.lineanchors
290 306 aln = self.anchorlinenos
291 307 nocls = self.noclasses
292 308 if sp:
293 309 lines = []
294 310
295 311 for i in range(fl, fl + lncount):
296 312 if i % st == 0:
297 313 if i % sp == 0:
298 314 if aln:
299 315 lines.append('<a href="#%s%d" class="special">%*d</a>' %
300 316 (la, i, mw, i))
301 317 else:
302 318 lines.append('<span class="special">%*d</span>' % (mw, i))
303 319 else:
304 320 if aln:
305 321 lines.append('<a href="#%s%d">%*d</a>' % (la, i, mw, i))
306 322 else:
307 323 lines.append('%*d' % (mw, i))
308 324 else:
309 325 lines.append('')
310 326 ls = '\n'.join(lines)
311 327 else:
312 328 lines = []
313 329 for i in range(fl, fl + lncount):
314 330 if i % st == 0:
315 331 if aln:
316 332 lines.append('<a href="#%s%d">%*d</a>' % (la, i, mw, i))
317 333 else:
318 334 lines.append('%*d' % (mw, i))
319 335 else:
320 336 lines.append('')
321 337 ls = '\n'.join(lines)
322 338
323 339 # in case you wonder about the seemingly redundant <div> here: since the
324 340 # content in the other cell also is wrapped in a div, some browsers in
325 341 # some configurations seem to mess up the formatting...
326 342 if nocls:
327 343 yield 0, ('<table class="%stable">' % self.cssclass +
328 344 '<tr><td><div class="linenodiv" '
329 345 'style="background-color: #f0f0f0; padding-right: 10px">'
330 346 '<pre style="line-height: 125%">' +
331 347 ls + '</pre></div></td><td id="hlcode" class="code">')
332 348 else:
333 349 yield 0, ('<table class="%stable">' % self.cssclass +
334 350 '<tr><td class="linenos"><div class="linenodiv"><pre>' +
335 351 ls + '</pre></div></td><td id="hlcode" class="code">')
336 352 yield 0, dummyoutfile.getvalue()
337 353 yield 0, '</td></tr></table>'
338 354
339 355
340 356 class SearchContentCodeHtmlFormatter(CodeHtmlFormatter):
341 357 def __init__(self, **kw):
342 358 # only show these line numbers if set
343 359 self.only_lines = kw.pop('only_line_numbers', [])
344 360 self.query_terms = kw.pop('query_terms', [])
345 361 self.max_lines = kw.pop('max_lines', 5)
346 362 self.line_context = kw.pop('line_context', 3)
347 363 self.url = kw.pop('url', None)
348 364
349 365 super(CodeHtmlFormatter, self).__init__(**kw)
350 366
351 367 def _wrap_code(self, source):
352 368 for cnt, it in enumerate(source):
353 369 i, t = it
354 370 t = '<pre>%s</pre>' % t
355 371 yield i, t
356 372
357 373 def _wrap_tablelinenos(self, inner):
358 374 yield 0, '<table class="code-highlight %stable">' % self.cssclass
359 375
360 376 last_shown_line_number = 0
361 377 current_line_number = 1
362 378
363 379 for t, line in inner:
364 380 if not t:
365 381 yield t, line
366 382 continue
367 383
368 384 if current_line_number in self.only_lines:
369 385 if last_shown_line_number + 1 != current_line_number:
370 386 yield 0, '<tr>'
371 387 yield 0, '<td class="line">...</td>'
372 388 yield 0, '<td id="hlcode" class="code"></td>'
373 389 yield 0, '</tr>'
374 390
375 391 yield 0, '<tr>'
376 392 if self.url:
377 393 yield 0, '<td class="line"><a href="%s#L%i">%i</a></td>' % (
378 394 self.url, current_line_number, current_line_number)
379 395 else:
380 396 yield 0, '<td class="line"><a href="">%i</a></td>' % (
381 397 current_line_number)
382 398 yield 0, '<td id="hlcode" class="code">' + line + '</td>'
383 399 yield 0, '</tr>'
384 400
385 401 last_shown_line_number = current_line_number
386 402
387 403 current_line_number += 1
388 404
389
390 405 yield 0, '</table>'
391 406
392 407
393 def extract_phrases(text_query):
394 """
395 Extracts phrases from search term string making sure phrases
396 contained in double quotes are kept together - and discarding empty values
397 or fully whitespace values eg.
398
399 'some text "a phrase" more' => ['some', 'text', 'a phrase', 'more']
400
401 """
402
403 in_phrase = False
404 buf = ''
405 phrases = []
406 for char in text_query:
407 if in_phrase:
408 if char == '"': # end phrase
409 phrases.append(buf)
410 buf = ''
411 in_phrase = False
412 continue
413 else:
414 buf += char
415 continue
416 else:
417 if char == '"': # start phrase
418 in_phrase = True
419 phrases.append(buf)
420 buf = ''
421 continue
422 elif char == ' ':
423 phrases.append(buf)
424 buf = ''
425 continue
426 else:
427 buf += char
428
429 phrases.append(buf)
430 phrases = [phrase.strip() for phrase in phrases if phrase.strip()]
431 return phrases
432
433
434 def get_matching_offsets(text, phrases):
435 """
436 Returns a list of string offsets in `text` that the list of `terms` match
437
438 >>> get_matching_offsets('some text here', ['some', 'here'])
439 [(0, 4), (10, 14)]
440
441 """
442 offsets = []
443 for phrase in phrases:
444 for match in re.finditer(phrase, text):
445 offsets.append((match.start(), match.end()))
446
447 return offsets
448
449
450 def normalize_text_for_matching(x):
451 """
452 Replaces all non alnum characters to spaces and lower cases the string,
453 useful for comparing two text strings without punctuation
454 """
455 return re.sub(r'[^\w]', ' ', x.lower())
456
457
458 def get_matching_line_offsets(lines, terms):
459 """ Return a set of `lines` indices (starting from 1) matching a
460 text search query, along with `context` lines above/below matching lines
461
462 :param lines: list of strings representing lines
463 :param terms: search term string to match in lines eg. 'some text'
464 :param context: number of lines above/below a matching line to add to result
465 :param max_lines: cut off for lines of interest
466 eg.
467
468 text = '''
469 words words words
470 words words words
471 some text some
472 words words words
473 words words words
474 text here what
475 '''
476 get_matching_line_offsets(text, 'text', context=1)
477 {3: [(5, 9)], 6: [(0, 4)]]
478
479 """
480 matching_lines = {}
481 phrases = [normalize_text_for_matching(phrase)
482 for phrase in extract_phrases(terms)]
483
484 for line_index, line in enumerate(lines, start=1):
485 match_offsets = get_matching_offsets(
486 normalize_text_for_matching(line), phrases)
487 if match_offsets:
488 matching_lines[line_index] = match_offsets
489
490 return matching_lines
491
492
493 408 def hsv_to_rgb(h, s, v):
494 409 """ Convert hsv color values to rgb """
495 410
496 411 if s == 0.0:
497 412 return v, v, v
498 413 i = int(h * 6.0) # XXX assume int() truncates!
499 414 f = (h * 6.0) - i
500 415 p = v * (1.0 - s)
501 416 q = v * (1.0 - s * f)
502 417 t = v * (1.0 - s * (1.0 - f))
503 418 i = i % 6
504 419 if i == 0:
505 420 return v, t, p
506 421 if i == 1:
507 422 return q, v, p
508 423 if i == 2:
509 424 return p, v, t
510 425 if i == 3:
511 426 return p, q, v
512 427 if i == 4:
513 428 return t, p, v
514 429 if i == 5:
515 430 return v, p, q
516 431
517 432
518 433 def unique_color_generator(n=10000, saturation=0.10, lightness=0.95):
519 434 """
520 435 Generator for getting n of evenly distributed colors using
521 436 hsv color and golden ratio. It always return same order of colors
522 437
523 438 :param n: number of colors to generate
524 439 :param saturation: saturation of returned colors
525 440 :param lightness: lightness of returned colors
526 441 :returns: RGB tuple
527 442 """
528 443
529 444 golden_ratio = 0.618033988749895
530 445 h = 0.22717784590367374
531 446
532 447 for _ in xrange(n):
533 448 h += golden_ratio
534 449 h %= 1
535 450 HSV_tuple = [h, saturation, lightness]
536 451 RGB_tuple = hsv_to_rgb(*HSV_tuple)
537 452 yield map(lambda x: str(int(x * 256)), RGB_tuple)
538 453
539 454
540 455 def color_hasher(n=10000, saturation=0.10, lightness=0.95):
541 456 """
542 457 Returns a function which when called with an argument returns a unique
543 458 color for that argument, eg.
544 459
545 460 :param n: number of colors to generate
546 461 :param saturation: saturation of returned colors
547 462 :param lightness: lightness of returned colors
548 463 :returns: css RGB string
549 464
550 465 >>> color_hash = color_hasher()
551 466 >>> color_hash('hello')
552 467 'rgb(34, 12, 59)'
553 468 >>> color_hash('hello')
554 469 'rgb(34, 12, 59)'
555 470 >>> color_hash('other')
556 471 'rgb(90, 224, 159)'
557 472 """
558 473
559 474 color_dict = {}
560 475 cgenerator = unique_color_generator(
561 476 saturation=saturation, lightness=lightness)
562 477
563 478 def get_color_string(thing):
564 479 if thing in color_dict:
565 480 col = color_dict[thing]
566 481 else:
567 482 col = color_dict[thing] = cgenerator.next()
568 483 return "rgb(%s)" % (', '.join(col))
569 484
570 485 return get_color_string
571 486
572 487
573 488 def get_lexer_safe(mimetype=None, filepath=None):
574 489 """
575 490 Tries to return a relevant pygments lexer using mimetype/filepath name,
576 491 defaulting to plain text if none could be found
577 492 """
578 493 lexer = None
579 494 try:
580 495 if mimetype:
581 496 lexer = get_lexer_for_mimetype(mimetype)
582 497 if not lexer:
583 498 lexer = get_lexer_for_filename(filepath)
584 499 except pygments.util.ClassNotFound:
585 500 pass
586 501
587 502 if not lexer:
588 503 lexer = get_lexer_by_name('text')
589 504
590 505 return lexer
591 506
592 507
593 508 def get_lexer_for_filenode(filenode):
594 509 lexer = get_custom_lexer(filenode.extension) or filenode.lexer
595 510 return lexer
596 511
597 512
598 513 def pygmentize(filenode, **kwargs):
599 514 """
600 515 pygmentize function using pygments
601 516
602 517 :param filenode:
603 518 """
604 519 lexer = get_lexer_for_filenode(filenode)
605 520 return literal(code_highlight(filenode.content, lexer,
606 521 CodeHtmlFormatter(**kwargs)))
607 522
608 523
609 524 def is_following_repo(repo_name, user_id):
610 525 from rhodecode.model.scm import ScmModel
611 526 return ScmModel().is_following_repo(repo_name, user_id)
612 527
613 528
614 529 class _Message(object):
615 530 """A message returned by ``Flash.pop_messages()``.
616 531
617 532 Converting the message to a string returns the message text. Instances
618 533 also have the following attributes:
619 534
620 535 * ``message``: the message text.
621 536 * ``category``: the category specified when the message was created.
622 537 """
623 538
624 539 def __init__(self, category, message):
625 540 self.category = category
626 541 self.message = message
627 542
628 543 def __str__(self):
629 544 return self.message
630 545
631 546 __unicode__ = __str__
632 547
633 548 def __html__(self):
634 549 return escape(safe_unicode(self.message))
635 550
636 551
637 552 class Flash(object):
638 553 # List of allowed categories. If None, allow any category.
639 554 categories = ["warning", "notice", "error", "success"]
640 555
641 556 # Default category if none is specified.
642 557 default_category = "notice"
643 558
644 559 def __init__(self, session_key="flash", categories=None,
645 560 default_category=None):
646 561 """
647 562 Instantiate a ``Flash`` object.
648 563
649 564 ``session_key`` is the key to save the messages under in the user's
650 565 session.
651 566
652 567 ``categories`` is an optional list which overrides the default list
653 568 of categories.
654 569
655 570 ``default_category`` overrides the default category used for messages
656 571 when none is specified.
657 572 """
658 573 self.session_key = session_key
659 574 if categories is not None:
660 575 self.categories = categories
661 576 if default_category is not None:
662 577 self.default_category = default_category
663 578 if self.categories and self.default_category not in self.categories:
664 579 raise ValueError(
665 580 "unrecognized default category %r" % (self.default_category,))
666 581
667 582 def pop_messages(self, session=None, request=None):
668 583 """
669 584 Return all accumulated messages and delete them from the session.
670 585
671 586 The return value is a list of ``Message`` objects.
672 587 """
673 588 messages = []
674 589
675 590 if not session:
676 591 if not request:
677 592 request = get_current_request()
678 593 session = request.session
679 594
680 595 # Pop the 'old' pylons flash messages. They are tuples of the form
681 596 # (category, message)
682 597 for cat, msg in session.pop(self.session_key, []):
683 598 messages.append(_Message(cat, msg))
684 599
685 600 # Pop the 'new' pyramid flash messages for each category as list
686 601 # of strings.
687 602 for cat in self.categories:
688 603 for msg in session.pop_flash(queue=cat):
689 604 messages.append(_Message(cat, msg))
690 605 # Map messages from the default queue to the 'notice' category.
691 606 for msg in session.pop_flash():
692 607 messages.append(_Message('notice', msg))
693 608
694 609 session.save()
695 610 return messages
696 611
697 612 def json_alerts(self, session=None, request=None):
698 613 payloads = []
699 614 messages = flash.pop_messages(session=session, request=request)
700 615 if messages:
701 616 for message in messages:
702 617 subdata = {}
703 618 if hasattr(message.message, 'rsplit'):
704 619 flash_data = message.message.rsplit('|DELIM|', 1)
705 620 org_message = flash_data[0]
706 621 if len(flash_data) > 1:
707 622 subdata = json.loads(flash_data[1])
708 623 else:
709 624 org_message = message.message
710 625 payloads.append({
711 626 'message': {
712 627 'message': u'{}'.format(org_message),
713 628 'level': message.category,
714 629 'force': True,
715 630 'subdata': subdata
716 631 }
717 632 })
718 633 return json.dumps(payloads)
719 634
720 635 def __call__(self, message, category=None, ignore_duplicate=False,
721 636 session=None, request=None):
722 637
723 638 if not session:
724 639 if not request:
725 640 request = get_current_request()
726 641 session = request.session
727 642
728 643 session.flash(
729 644 message, queue=category, allow_duplicate=not ignore_duplicate)
730 645
731 646
732 647 flash = Flash()
733 648
734 649 #==============================================================================
735 650 # SCM FILTERS available via h.
736 651 #==============================================================================
737 652 from rhodecode.lib.vcs.utils import author_name, author_email
738 653 from rhodecode.lib.utils2 import credentials_filter, age as _age
739 654 from rhodecode.model.db import User, ChangesetStatus
740 655
741 656 age = _age
742 657 capitalize = lambda x: x.capitalize()
743 658 email = author_email
744 659 short_id = lambda x: x[:12]
745 660 hide_credentials = lambda x: ''.join(credentials_filter(x))
746 661
747 662
748 663 import pytz
749 664 import tzlocal
750 665 local_timezone = tzlocal.get_localzone()
751 666
752 667
753 668 def age_component(datetime_iso, value=None, time_is_local=False):
754 669 title = value or format_date(datetime_iso)
755 670 tzinfo = '+00:00'
756 671
757 672 # detect if we have a timezone info, otherwise, add it
758 673 if time_is_local and isinstance(datetime_iso, datetime) and not datetime_iso.tzinfo:
759 674 force_timezone = os.environ.get('RC_TIMEZONE', '')
760 675 if force_timezone:
761 676 force_timezone = pytz.timezone(force_timezone)
762 677 timezone = force_timezone or local_timezone
763 678 offset = timezone.localize(datetime_iso).strftime('%z')
764 679 tzinfo = '{}:{}'.format(offset[:-2], offset[-2:])
765 680
766 681 return literal(
767 682 '<time class="timeago tooltip" '
768 683 'title="{1}{2}" datetime="{0}{2}">{1}</time>'.format(
769 684 datetime_iso, title, tzinfo))
770 685
771 686
772 687 def _shorten_commit_id(commit_id):
773 688 from rhodecode import CONFIG
774 689 def_len = safe_int(CONFIG.get('rhodecode_show_sha_length', 12))
775 690 return commit_id[:def_len]
776 691
777 692
778 693 def show_id(commit):
779 694 """
780 695 Configurable function that shows ID
781 696 by default it's r123:fffeeefffeee
782 697
783 698 :param commit: commit instance
784 699 """
785 700 from rhodecode import CONFIG
786 701 show_idx = str2bool(CONFIG.get('rhodecode_show_revision_number', True))
787 702
788 703 raw_id = _shorten_commit_id(commit.raw_id)
789 704 if show_idx:
790 705 return 'r%s:%s' % (commit.idx, raw_id)
791 706 else:
792 707 return '%s' % (raw_id, )
793 708
794 709
795 710 def format_date(date):
796 711 """
797 712 use a standardized formatting for dates used in RhodeCode
798 713
799 714 :param date: date/datetime object
800 715 :return: formatted date
801 716 """
802 717
803 718 if date:
804 719 _fmt = "%a, %d %b %Y %H:%M:%S"
805 720 return safe_unicode(date.strftime(_fmt))
806 721
807 722 return u""
808 723
809 724
810 725 class _RepoChecker(object):
811 726
812 727 def __init__(self, backend_alias):
813 728 self._backend_alias = backend_alias
814 729
815 730 def __call__(self, repository):
816 731 if hasattr(repository, 'alias'):
817 732 _type = repository.alias
818 733 elif hasattr(repository, 'repo_type'):
819 734 _type = repository.repo_type
820 735 else:
821 736 _type = repository
822 737 return _type == self._backend_alias
823 738
824 739 is_git = _RepoChecker('git')
825 740 is_hg = _RepoChecker('hg')
826 741 is_svn = _RepoChecker('svn')
827 742
828 743
829 744 def get_repo_type_by_name(repo_name):
830 745 repo = Repository.get_by_repo_name(repo_name)
831 746 return repo.repo_type
832 747
833 748
834 749 def is_svn_without_proxy(repository):
835 750 if is_svn(repository):
836 751 from rhodecode.model.settings import VcsSettingsModel
837 752 conf = VcsSettingsModel().get_ui_settings_as_config_obj()
838 753 return not str2bool(conf.get('vcs_svn_proxy', 'http_requests_enabled'))
839 754 return False
840 755
841 756
842 757 def discover_user(author):
843 758 """
844 759 Tries to discover RhodeCode User based on the autho string. Author string
845 760 is typically `FirstName LastName <email@address.com>`
846 761 """
847 762
848 763 # if author is already an instance use it for extraction
849 764 if isinstance(author, User):
850 765 return author
851 766
852 767 # Valid email in the attribute passed, see if they're in the system
853 768 _email = author_email(author)
854 769 if _email != '':
855 770 user = User.get_by_email(_email, case_insensitive=True, cache=True)
856 771 if user is not None:
857 772 return user
858 773
859 774 # Maybe it's a username, we try to extract it and fetch by username ?
860 775 _author = author_name(author)
861 776 user = User.get_by_username(_author, case_insensitive=True, cache=True)
862 777 if user is not None:
863 778 return user
864 779
865 780 return None
866 781
867 782
868 783 def email_or_none(author):
869 784 # extract email from the commit string
870 785 _email = author_email(author)
871 786
872 787 # If we have an email, use it, otherwise
873 788 # see if it contains a username we can get an email from
874 789 if _email != '':
875 790 return _email
876 791 else:
877 792 user = User.get_by_username(
878 793 author_name(author), case_insensitive=True, cache=True)
879 794
880 795 if user is not None:
881 796 return user.email
882 797
883 798 # No valid email, not a valid user in the system, none!
884 799 return None
885 800
886 801
887 802 def link_to_user(author, length=0, **kwargs):
888 803 user = discover_user(author)
889 804 # user can be None, but if we have it already it means we can re-use it
890 805 # in the person() function, so we save 1 intensive-query
891 806 if user:
892 807 author = user
893 808
894 809 display_person = person(author, 'username_or_name_or_email')
895 810 if length:
896 811 display_person = shorter(display_person, length)
897 812
898 813 if user:
899 814 return link_to(
900 815 escape(display_person),
901 816 route_path('user_profile', username=user.username),
902 817 **kwargs)
903 818 else:
904 819 return escape(display_person)
905 820
906 821
907 822 def link_to_group(users_group_name, **kwargs):
908 823 return link_to(
909 824 escape(users_group_name),
910 825 route_path('user_group_profile', user_group_name=users_group_name),
911 826 **kwargs)
912 827
913 828
914 829 def person(author, show_attr="username_and_name"):
915 830 user = discover_user(author)
916 831 if user:
917 832 return getattr(user, show_attr)
918 833 else:
919 834 _author = author_name(author)
920 835 _email = email(author)
921 836 return _author or _email
922 837
923 838
924 839 def author_string(email):
925 840 if email:
926 841 user = User.get_by_email(email, case_insensitive=True, cache=True)
927 842 if user:
928 843 if user.first_name or user.last_name:
929 844 return '%s %s &lt;%s&gt;' % (
930 845 user.first_name, user.last_name, email)
931 846 else:
932 847 return email
933 848 else:
934 849 return email
935 850 else:
936 851 return None
937 852
938 853
939 854 def person_by_id(id_, show_attr="username_and_name"):
940 855 # attr to return from fetched user
941 856 person_getter = lambda usr: getattr(usr, show_attr)
942 857
943 858 #maybe it's an ID ?
944 859 if str(id_).isdigit() or isinstance(id_, int):
945 860 id_ = int(id_)
946 861 user = User.get(id_)
947 862 if user is not None:
948 863 return person_getter(user)
949 864 return id_
950 865
951 866
952 867 def gravatar_with_user(request, author, show_disabled=False):
953 868 _render = request.get_partial_renderer(
954 869 'rhodecode:templates/base/base.mako')
955 870 return _render('gravatar_with_user', author, show_disabled=show_disabled)
956 871
957 872
958 873 tags_paterns = OrderedDict((
959 874 ('lang', (re.compile(r'\[(lang|language)\ \=\&gt;\ *([a-zA-Z\-\/\#\+\.]*)\]'),
960 875 '<div class="metatag" tag="lang">\\2</div>')),
961 876
962 877 ('see', (re.compile(r'\[see\ \=\&gt;\ *([a-zA-Z0-9\/\=\?\&amp;\ \:\/\.\-]*)\]'),
963 878 '<div class="metatag" tag="see">see: \\1 </div>')),
964 879
965 880 ('url', (re.compile(r'\[url\ \=\&gt;\ \[([a-zA-Z0-9\ \.\-\_]+)\]\((http://|https://|/)(.*?)\)\]'),
966 881 '<div class="metatag" tag="url"> <a href="\\2\\3">\\1</a> </div>')),
967 882
968 883 ('license', (re.compile(r'\[license\ \=\&gt;\ *([a-zA-Z0-9\/\=\?\&amp;\ \:\/\.\-]*)\]'),
969 884 '<div class="metatag" tag="license"><a href="http:\/\/www.opensource.org/licenses/\\1">\\1</a></div>')),
970 885
971 886 ('ref', (re.compile(r'\[(requires|recommends|conflicts|base)\ \=\&gt;\ *([a-zA-Z0-9\-\/]*)\]'),
972 887 '<div class="metatag" tag="ref \\1">\\1: <a href="/\\2">\\2</a></div>')),
973 888
974 889 ('state', (re.compile(r'\[(stable|featured|stale|dead|dev|deprecated)\]'),
975 890 '<div class="metatag" tag="state \\1">\\1</div>')),
976 891
977 892 # label in grey
978 893 ('label', (re.compile(r'\[([a-z]+)\]'),
979 894 '<div class="metatag" tag="label">\\1</div>')),
980 895
981 896 # generic catch all in grey
982 897 ('generic', (re.compile(r'\[([a-zA-Z0-9\.\-\_]+)\]'),
983 898 '<div class="metatag" tag="generic">\\1</div>')),
984 899 ))
985 900
986 901
987 902 def extract_metatags(value):
988 903 """
989 904 Extract supported meta-tags from given text value
990 905 """
991 906 tags = []
992 907 if not value:
993 908 return tags, ''
994 909
995 910 for key, val in tags_paterns.items():
996 911 pat, replace_html = val
997 912 tags.extend([(key, x.group()) for x in pat.finditer(value)])
998 913 value = pat.sub('', value)
999 914
1000 915 return tags, value
1001 916
1002 917
1003 918 def style_metatag(tag_type, value):
1004 919 """
1005 920 converts tags from value into html equivalent
1006 921 """
1007 922 if not value:
1008 923 return ''
1009 924
1010 925 html_value = value
1011 926 tag_data = tags_paterns.get(tag_type)
1012 927 if tag_data:
1013 928 pat, replace_html = tag_data
1014 929 # convert to plain `unicode` instead of a markup tag to be used in
1015 930 # regex expressions. safe_unicode doesn't work here
1016 931 html_value = pat.sub(replace_html, unicode(value))
1017 932
1018 933 return html_value
1019 934
1020 935
1021 936 def bool2icon(value, show_at_false=True):
1022 937 """
1023 938 Returns boolean value of a given value, represented as html element with
1024 939 classes that will represent icons
1025 940
1026 941 :param value: given value to convert to html node
1027 942 """
1028 943
1029 944 if value: # does bool conversion
1030 945 return HTML.tag('i', class_="icon-true")
1031 946 else: # not true as bool
1032 947 if show_at_false:
1033 948 return HTML.tag('i', class_="icon-false")
1034 949 return HTML.tag('i')
1035 950
1036 951 #==============================================================================
1037 952 # PERMS
1038 953 #==============================================================================
1039 954 from rhodecode.lib.auth import HasPermissionAny, HasPermissionAll, \
1040 955 HasRepoPermissionAny, HasRepoPermissionAll, HasRepoGroupPermissionAll, \
1041 956 HasRepoGroupPermissionAny, HasRepoPermissionAnyApi, get_csrf_token, \
1042 957 csrf_token_key
1043 958
1044 959
1045 960 #==============================================================================
1046 961 # GRAVATAR URL
1047 962 #==============================================================================
1048 963 class InitialsGravatar(object):
1049 964 def __init__(self, email_address, first_name, last_name, size=30,
1050 965 background=None, text_color='#fff'):
1051 966 self.size = size
1052 967 self.first_name = first_name
1053 968 self.last_name = last_name
1054 969 self.email_address = email_address
1055 970 self.background = background or self.str2color(email_address)
1056 971 self.text_color = text_color
1057 972
1058 973 def get_color_bank(self):
1059 974 """
1060 975 returns a predefined list of colors that gravatars can use.
1061 976 Those are randomized distinct colors that guarantee readability and
1062 977 uniqueness.
1063 978
1064 979 generated with: http://phrogz.net/css/distinct-colors.html
1065 980 """
1066 981 return [
1067 982 '#bf3030', '#a67f53', '#00ff00', '#5989b3', '#392040', '#d90000',
1068 983 '#402910', '#204020', '#79baf2', '#a700b3', '#bf6060', '#7f5320',
1069 984 '#008000', '#003059', '#ee00ff', '#ff0000', '#8c4b00', '#007300',
1070 985 '#005fb3', '#de73e6', '#ff4040', '#ffaa00', '#3df255', '#203140',
1071 986 '#47004d', '#591616', '#664400', '#59b365', '#0d2133', '#83008c',
1072 987 '#592d2d', '#bf9f60', '#73e682', '#1d3f73', '#73006b', '#402020',
1073 988 '#b2862d', '#397341', '#597db3', '#e600d6', '#a60000', '#736039',
1074 989 '#00b318', '#79aaf2', '#330d30', '#ff8080', '#403010', '#16591f',
1075 990 '#002459', '#8c4688', '#e50000', '#ffbf40', '#00732e', '#102340',
1076 991 '#bf60ac', '#8c4646', '#cc8800', '#00a642', '#1d3473', '#b32d98',
1077 992 '#660e00', '#ffd580', '#80ffb2', '#7391e6', '#733967', '#d97b6c',
1078 993 '#8c5e00', '#59b389', '#3967e6', '#590047', '#73281d', '#665200',
1079 994 '#00e67a', '#2d50b3', '#8c2377', '#734139', '#b2982d', '#16593a',
1080 995 '#001859', '#ff00aa', '#a65e53', '#ffcc00', '#0d3321', '#2d3959',
1081 996 '#731d56', '#401610', '#4c3d00', '#468c6c', '#002ca6', '#d936a3',
1082 997 '#d94c36', '#403920', '#36d9a3', '#0d1733', '#592d4a', '#993626',
1083 998 '#cca300', '#00734d', '#46598c', '#8c005e', '#7f1100', '#8c7000',
1084 999 '#00a66f', '#7382e6', '#b32d74', '#d9896c', '#ffe680', '#1d7362',
1085 1000 '#364cd9', '#73003d', '#d93a00', '#998a4d', '#59b3a1', '#5965b3',
1086 1001 '#e5007a', '#73341d', '#665f00', '#00b38f', '#0018b3', '#59163a',
1087 1002 '#b2502d', '#bfb960', '#00ffcc', '#23318c', '#a6537f', '#734939',
1088 1003 '#b2a700', '#104036', '#3d3df2', '#402031', '#e56739', '#736f39',
1089 1004 '#79f2ea', '#000059', '#401029', '#4c1400', '#ffee00', '#005953',
1090 1005 '#101040', '#990052', '#402820', '#403d10', '#00ffee', '#0000d9',
1091 1006 '#ff80c4', '#a66953', '#eeff00', '#00ccbe', '#8080ff', '#e673a1',
1092 1007 '#a62c00', '#474d00', '#1a3331', '#46468c', '#733950', '#662900',
1093 1008 '#858c23', '#238c85', '#0f0073', '#b20047', '#d9986c', '#becc00',
1094 1009 '#396f73', '#281d73', '#ff0066', '#ff6600', '#dee673', '#59adb3',
1095 1010 '#6559b3', '#590024', '#b2622d', '#98b32d', '#36ced9', '#332d59',
1096 1011 '#40001a', '#733f1d', '#526600', '#005359', '#242040', '#bf6079',
1097 1012 '#735039', '#cef23d', '#007780', '#5630bf', '#66001b', '#b24700',
1098 1013 '#acbf60', '#1d6273', '#25008c', '#731d34', '#a67453', '#50592d',
1099 1014 '#00ccff', '#6600ff', '#ff0044', '#4c1f00', '#8a994d', '#79daf2',
1100 1015 '#a173e6', '#d93662', '#402310', '#aaff00', '#2d98b3', '#8c40ff',
1101 1016 '#592d39', '#ff8c40', '#354020', '#103640', '#1a0040', '#331a20',
1102 1017 '#331400', '#334d00', '#1d5673', '#583973', '#7f0022', '#4c3626',
1103 1018 '#88cc00', '#36a3d9', '#3d0073', '#d9364c', '#33241a', '#698c23',
1104 1019 '#5995b3', '#300059', '#e57382', '#7f3300', '#366600', '#00aaff',
1105 1020 '#3a1659', '#733941', '#663600', '#74b32d', '#003c59', '#7f53a6',
1106 1021 '#73000f', '#ff8800', '#baf279', '#79caf2', '#291040', '#a6293a',
1107 1022 '#b2742d', '#587339', '#0077b3', '#632699', '#400009', '#d9a66c',
1108 1023 '#294010', '#2d4a59', '#aa00ff', '#4c131b', '#b25f00', '#5ce600',
1109 1024 '#267399', '#a336d9', '#990014', '#664e33', '#86bf60', '#0088ff',
1110 1025 '#7700b3', '#593a16', '#073300', '#1d4b73', '#ac60bf', '#e59539',
1111 1026 '#4f8c46', '#368dd9', '#5c0073'
1112 1027 ]
1113 1028
1114 1029 def rgb_to_hex_color(self, rgb_tuple):
1115 1030 """
1116 1031 Converts an rgb_tuple passed to an hex color.
1117 1032
1118 1033 :param rgb_tuple: tuple with 3 ints represents rgb color space
1119 1034 """
1120 1035 return '#' + ("".join(map(chr, rgb_tuple)).encode('hex'))
1121 1036
1122 1037 def email_to_int_list(self, email_str):
1123 1038 """
1124 1039 Get every byte of the hex digest value of email and turn it to integer.
1125 1040 It's going to be always between 0-255
1126 1041 """
1127 1042 digest = md5_safe(email_str.lower())
1128 1043 return [int(digest[i * 2:i * 2 + 2], 16) for i in range(16)]
1129 1044
1130 1045 def pick_color_bank_index(self, email_str, color_bank):
1131 1046 return self.email_to_int_list(email_str)[0] % len(color_bank)
1132 1047
1133 1048 def str2color(self, email_str):
1134 1049 """
1135 1050 Tries to map in a stable algorithm an email to color
1136 1051
1137 1052 :param email_str:
1138 1053 """
1139 1054 color_bank = self.get_color_bank()
1140 1055 # pick position (module it's length so we always find it in the
1141 1056 # bank even if it's smaller than 256 values
1142 1057 pos = self.pick_color_bank_index(email_str, color_bank)
1143 1058 return color_bank[pos]
1144 1059
1145 1060 def normalize_email(self, email_address):
1146 1061 import unicodedata
1147 1062 # default host used to fill in the fake/missing email
1148 1063 default_host = u'localhost'
1149 1064
1150 1065 if not email_address:
1151 1066 email_address = u'%s@%s' % (User.DEFAULT_USER, default_host)
1152 1067
1153 1068 email_address = safe_unicode(email_address)
1154 1069
1155 1070 if u'@' not in email_address:
1156 1071 email_address = u'%s@%s' % (email_address, default_host)
1157 1072
1158 1073 if email_address.endswith(u'@'):
1159 1074 email_address = u'%s%s' % (email_address, default_host)
1160 1075
1161 1076 email_address = unicodedata.normalize('NFKD', email_address)\
1162 1077 .encode('ascii', 'ignore')
1163 1078 return email_address
1164 1079
1165 1080 def get_initials(self):
1166 1081 """
1167 1082 Returns 2 letter initials calculated based on the input.
1168 1083 The algorithm picks first given email address, and takes first letter
1169 1084 of part before @, and then the first letter of server name. In case
1170 1085 the part before @ is in a format of `somestring.somestring2` it replaces
1171 1086 the server letter with first letter of somestring2
1172 1087
1173 1088 In case function was initialized with both first and lastname, this
1174 1089 overrides the extraction from email by first letter of the first and
1175 1090 last name. We add special logic to that functionality, In case Full name
1176 1091 is compound, like Guido Von Rossum, we use last part of the last name
1177 1092 (Von Rossum) picking `R`.
1178 1093
1179 1094 Function also normalizes the non-ascii characters to they ascii
1180 1095 representation, eg Δ„ => A
1181 1096 """
1182 1097 import unicodedata
1183 1098 # replace non-ascii to ascii
1184 1099 first_name = unicodedata.normalize(
1185 1100 'NFKD', safe_unicode(self.first_name)).encode('ascii', 'ignore')
1186 1101 last_name = unicodedata.normalize(
1187 1102 'NFKD', safe_unicode(self.last_name)).encode('ascii', 'ignore')
1188 1103
1189 1104 # do NFKD encoding, and also make sure email has proper format
1190 1105 email_address = self.normalize_email(self.email_address)
1191 1106
1192 1107 # first push the email initials
1193 1108 prefix, server = email_address.split('@', 1)
1194 1109
1195 1110 # check if prefix is maybe a 'first_name.last_name' syntax
1196 1111 _dot_split = prefix.rsplit('.', 1)
1197 1112 if len(_dot_split) == 2 and _dot_split[1]:
1198 1113 initials = [_dot_split[0][0], _dot_split[1][0]]
1199 1114 else:
1200 1115 initials = [prefix[0], server[0]]
1201 1116
1202 1117 # then try to replace either first_name or last_name
1203 1118 fn_letter = (first_name or " ")[0].strip()
1204 1119 ln_letter = (last_name.split(' ', 1)[-1] or " ")[0].strip()
1205 1120
1206 1121 if fn_letter:
1207 1122 initials[0] = fn_letter
1208 1123
1209 1124 if ln_letter:
1210 1125 initials[1] = ln_letter
1211 1126
1212 1127 return ''.join(initials).upper()
1213 1128
1214 1129 def get_img_data_by_type(self, font_family, img_type):
1215 1130 default_user = """
1216 1131 <svg xmlns="http://www.w3.org/2000/svg"
1217 1132 version="1.1" x="0px" y="0px" width="{size}" height="{size}"
1218 1133 viewBox="-15 -10 439.165 429.164"
1219 1134
1220 1135 xml:space="preserve"
1221 1136 style="background:{background};" >
1222 1137
1223 1138 <path d="M204.583,216.671c50.664,0,91.74-48.075,
1224 1139 91.74-107.378c0-82.237-41.074-107.377-91.74-107.377
1225 1140 c-50.668,0-91.74,25.14-91.74,107.377C112.844,
1226 1141 168.596,153.916,216.671,
1227 1142 204.583,216.671z" fill="{text_color}"/>
1228 1143 <path d="M407.164,374.717L360.88,
1229 1144 270.454c-2.117-4.771-5.836-8.728-10.465-11.138l-71.83-37.392
1230 1145 c-1.584-0.823-3.502-0.663-4.926,0.415c-20.316,
1231 1146 15.366-44.203,23.488-69.076,23.488c-24.877,
1232 1147 0-48.762-8.122-69.078-23.488
1233 1148 c-1.428-1.078-3.346-1.238-4.93-0.415L58.75,
1234 1149 259.316c-4.631,2.41-8.346,6.365-10.465,11.138L2.001,374.717
1235 1150 c-3.191,7.188-2.537,15.412,1.75,22.005c4.285,
1236 1151 6.592,11.537,10.526,19.4,10.526h362.861c7.863,0,15.117-3.936,
1237 1152 19.402-10.527 C409.699,390.129,
1238 1153 410.355,381.902,407.164,374.717z" fill="{text_color}"/>
1239 1154 </svg>""".format(
1240 1155 size=self.size,
1241 1156 background='#979797', # @grey4
1242 1157 text_color=self.text_color,
1243 1158 font_family=font_family)
1244 1159
1245 1160 return {
1246 1161 "default_user": default_user
1247 1162 }[img_type]
1248 1163
1249 1164 def get_img_data(self, svg_type=None):
1250 1165 """
1251 1166 generates the svg metadata for image
1252 1167 """
1253 1168 fonts = [
1254 1169 '-apple-system',
1255 1170 'BlinkMacSystemFont',
1256 1171 'Segoe UI',
1257 1172 'Roboto',
1258 1173 'Oxygen-Sans',
1259 1174 'Ubuntu',
1260 1175 'Cantarell',
1261 1176 'Helvetica Neue',
1262 1177 'sans-serif'
1263 1178 ]
1264 1179 font_family = ','.join(fonts)
1265 1180 if svg_type:
1266 1181 return self.get_img_data_by_type(font_family, svg_type)
1267 1182
1268 1183 initials = self.get_initials()
1269 1184 img_data = """
1270 1185 <svg xmlns="http://www.w3.org/2000/svg" pointer-events="none"
1271 1186 width="{size}" height="{size}"
1272 1187 style="width: 100%; height: 100%; background-color: {background}"
1273 1188 viewBox="0 0 {size} {size}">
1274 1189 <text text-anchor="middle" y="50%" x="50%" dy="0.35em"
1275 1190 pointer-events="auto" fill="{text_color}"
1276 1191 font-family="{font_family}"
1277 1192 style="font-weight: 400; font-size: {f_size}px;">{text}
1278 1193 </text>
1279 1194 </svg>""".format(
1280 1195 size=self.size,
1281 1196 f_size=self.size/1.85, # scale the text inside the box nicely
1282 1197 background=self.background,
1283 1198 text_color=self.text_color,
1284 1199 text=initials.upper(),
1285 1200 font_family=font_family)
1286 1201
1287 1202 return img_data
1288 1203
1289 1204 def generate_svg(self, svg_type=None):
1290 1205 img_data = self.get_img_data(svg_type)
1291 1206 return "data:image/svg+xml;base64,%s" % img_data.encode('base64')
1292 1207
1293 1208
1294 1209 def initials_gravatar(email_address, first_name, last_name, size=30):
1295 1210 svg_type = None
1296 1211 if email_address == User.DEFAULT_USER_EMAIL:
1297 1212 svg_type = 'default_user'
1298 1213 klass = InitialsGravatar(email_address, first_name, last_name, size)
1299 1214 return klass.generate_svg(svg_type=svg_type)
1300 1215
1301 1216
1302 1217 def gravatar_url(email_address, size=30, request=None):
1303 1218 request = get_current_request()
1304 1219 _use_gravatar = request.call_context.visual.use_gravatar
1305 1220 _gravatar_url = request.call_context.visual.gravatar_url
1306 1221
1307 1222 _gravatar_url = _gravatar_url or User.DEFAULT_GRAVATAR_URL
1308 1223
1309 1224 email_address = email_address or User.DEFAULT_USER_EMAIL
1310 1225 if isinstance(email_address, unicode):
1311 1226 # hashlib crashes on unicode items
1312 1227 email_address = safe_str(email_address)
1313 1228
1314 1229 # empty email or default user
1315 1230 if not email_address or email_address == User.DEFAULT_USER_EMAIL:
1316 1231 return initials_gravatar(User.DEFAULT_USER_EMAIL, '', '', size=size)
1317 1232
1318 1233 if _use_gravatar:
1319 1234 # TODO: Disuse pyramid thread locals. Think about another solution to
1320 1235 # get the host and schema here.
1321 1236 request = get_current_request()
1322 1237 tmpl = safe_str(_gravatar_url)
1323 1238 tmpl = tmpl.replace('{email}', email_address)\
1324 1239 .replace('{md5email}', md5_safe(email_address.lower())) \
1325 1240 .replace('{netloc}', request.host)\
1326 1241 .replace('{scheme}', request.scheme)\
1327 1242 .replace('{size}', safe_str(size))
1328 1243 return tmpl
1329 1244 else:
1330 1245 return initials_gravatar(email_address, '', '', size=size)
1331 1246
1332 1247
1333 1248 class Page(_Page):
1334 1249 """
1335 1250 Custom pager to match rendering style with paginator
1336 1251 """
1337 1252
1338 1253 def _get_pos(self, cur_page, max_page, items):
1339 1254 edge = (items / 2) + 1
1340 1255 if (cur_page <= edge):
1341 1256 radius = max(items / 2, items - cur_page)
1342 1257 elif (max_page - cur_page) < edge:
1343 1258 radius = (items - 1) - (max_page - cur_page)
1344 1259 else:
1345 1260 radius = items / 2
1346 1261
1347 1262 left = max(1, (cur_page - (radius)))
1348 1263 right = min(max_page, cur_page + (radius))
1349 1264 return left, cur_page, right
1350 1265
1351 1266 def _range(self, regexp_match):
1352 1267 """
1353 1268 Return range of linked pages (e.g. '1 2 [3] 4 5 6 7 8').
1354 1269
1355 1270 Arguments:
1356 1271
1357 1272 regexp_match
1358 1273 A "re" (regular expressions) match object containing the
1359 1274 radius of linked pages around the current page in
1360 1275 regexp_match.group(1) as a string
1361 1276
1362 1277 This function is supposed to be called as a callable in
1363 1278 re.sub.
1364 1279
1365 1280 """
1366 1281 radius = int(regexp_match.group(1))
1367 1282
1368 1283 # Compute the first and last page number within the radius
1369 1284 # e.g. '1 .. 5 6 [7] 8 9 .. 12'
1370 1285 # -> leftmost_page = 5
1371 1286 # -> rightmost_page = 9
1372 1287 leftmost_page, _cur, rightmost_page = self._get_pos(self.page,
1373 1288 self.last_page,
1374 1289 (radius * 2) + 1)
1375 1290 nav_items = []
1376 1291
1377 1292 # Create a link to the first page (unless we are on the first page
1378 1293 # or there would be no need to insert '..' spacers)
1379 1294 if self.page != self.first_page and self.first_page < leftmost_page:
1380 1295 nav_items.append(self._pagerlink(self.first_page, self.first_page))
1381 1296
1382 1297 # Insert dots if there are pages between the first page
1383 1298 # and the currently displayed page range
1384 1299 if leftmost_page - self.first_page > 1:
1385 1300 # Wrap in a SPAN tag if nolink_attr is set
1386 1301 text = '..'
1387 1302 if self.dotdot_attr:
1388 1303 text = HTML.span(c=text, **self.dotdot_attr)
1389 1304 nav_items.append(text)
1390 1305
1391 1306 for thispage in xrange(leftmost_page, rightmost_page + 1):
1392 1307 # Hilight the current page number and do not use a link
1393 1308 if thispage == self.page:
1394 1309 text = '%s' % (thispage,)
1395 1310 # Wrap in a SPAN tag if nolink_attr is set
1396 1311 if self.curpage_attr:
1397 1312 text = HTML.span(c=text, **self.curpage_attr)
1398 1313 nav_items.append(text)
1399 1314 # Otherwise create just a link to that page
1400 1315 else:
1401 1316 text = '%s' % (thispage,)
1402 1317 nav_items.append(self._pagerlink(thispage, text))
1403 1318
1404 1319 # Insert dots if there are pages between the displayed
1405 1320 # page numbers and the end of the page range
1406 1321 if self.last_page - rightmost_page > 1:
1407 1322 text = '..'
1408 1323 # Wrap in a SPAN tag if nolink_attr is set
1409 1324 if self.dotdot_attr:
1410 1325 text = HTML.span(c=text, **self.dotdot_attr)
1411 1326 nav_items.append(text)
1412 1327
1413 1328 # Create a link to the very last page (unless we are on the last
1414 1329 # page or there would be no need to insert '..' spacers)
1415 1330 if self.page != self.last_page and rightmost_page < self.last_page:
1416 1331 nav_items.append(self._pagerlink(self.last_page, self.last_page))
1417 1332
1418 1333 ## prerender links
1419 1334 #_page_link = url.current()
1420 1335 #nav_items.append(literal('<link rel="prerender" href="%s?page=%s">' % (_page_link, str(int(self.page)+1))))
1421 1336 #nav_items.append(literal('<link rel="prefetch" href="%s?page=%s">' % (_page_link, str(int(self.page)+1))))
1422 1337 return self.separator.join(nav_items)
1423 1338
1424 1339 def pager(self, format='~2~', page_param='page', partial_param='partial',
1425 1340 show_if_single_page=False, separator=' ', onclick=None,
1426 1341 symbol_first='<<', symbol_last='>>',
1427 1342 symbol_previous='<', symbol_next='>',
1428 1343 link_attr={'class': 'pager_link', 'rel': 'prerender'},
1429 1344 curpage_attr={'class': 'pager_curpage'},
1430 1345 dotdot_attr={'class': 'pager_dotdot'}, **kwargs):
1431 1346
1432 1347 self.curpage_attr = curpage_attr
1433 1348 self.separator = separator
1434 1349 self.pager_kwargs = kwargs
1435 1350 self.page_param = page_param
1436 1351 self.partial_param = partial_param
1437 1352 self.onclick = onclick
1438 1353 self.link_attr = link_attr
1439 1354 self.dotdot_attr = dotdot_attr
1440 1355
1441 1356 # Don't show navigator if there is no more than one page
1442 1357 if self.page_count == 0 or (self.page_count == 1 and not show_if_single_page):
1443 1358 return ''
1444 1359
1445 1360 from string import Template
1446 1361 # Replace ~...~ in token format by range of pages
1447 1362 result = re.sub(r'~(\d+)~', self._range, format)
1448 1363
1449 1364 # Interpolate '%' variables
1450 1365 result = Template(result).safe_substitute({
1451 1366 'first_page': self.first_page,
1452 1367 'last_page': self.last_page,
1453 1368 'page': self.page,
1454 1369 'page_count': self.page_count,
1455 1370 'items_per_page': self.items_per_page,
1456 1371 'first_item': self.first_item,
1457 1372 'last_item': self.last_item,
1458 1373 'item_count': self.item_count,
1459 1374 'link_first': self.page > self.first_page and \
1460 1375 self._pagerlink(self.first_page, symbol_first) or '',
1461 1376 'link_last': self.page < self.last_page and \
1462 1377 self._pagerlink(self.last_page, symbol_last) or '',
1463 1378 'link_previous': self.previous_page and \
1464 1379 self._pagerlink(self.previous_page, symbol_previous) \
1465 1380 or HTML.span(symbol_previous, class_="pg-previous disabled"),
1466 1381 'link_next': self.next_page and \
1467 1382 self._pagerlink(self.next_page, symbol_next) \
1468 1383 or HTML.span(symbol_next, class_="pg-next disabled")
1469 1384 })
1470 1385
1471 1386 return literal(result)
1472 1387
1473 1388
1474 1389 #==============================================================================
1475 1390 # REPO PAGER, PAGER FOR REPOSITORY
1476 1391 #==============================================================================
1477 1392 class RepoPage(Page):
1478 1393
1479 1394 def __init__(self, collection, page=1, items_per_page=20,
1480 1395 item_count=None, url=None, **kwargs):
1481 1396
1482 1397 """Create a "RepoPage" instance. special pager for paging
1483 1398 repository
1484 1399 """
1485 1400 self._url_generator = url
1486 1401
1487 1402 # Safe the kwargs class-wide so they can be used in the pager() method
1488 1403 self.kwargs = kwargs
1489 1404
1490 1405 # Save a reference to the collection
1491 1406 self.original_collection = collection
1492 1407
1493 1408 self.collection = collection
1494 1409
1495 1410 # The self.page is the number of the current page.
1496 1411 # The first page has the number 1!
1497 1412 try:
1498 1413 self.page = int(page) # make it int() if we get it as a string
1499 1414 except (ValueError, TypeError):
1500 1415 self.page = 1
1501 1416
1502 1417 self.items_per_page = items_per_page
1503 1418
1504 1419 # Unless the user tells us how many items the collections has
1505 1420 # we calculate that ourselves.
1506 1421 if item_count is not None:
1507 1422 self.item_count = item_count
1508 1423 else:
1509 1424 self.item_count = len(self.collection)
1510 1425
1511 1426 # Compute the number of the first and last available page
1512 1427 if self.item_count > 0:
1513 1428 self.first_page = 1
1514 1429 self.page_count = int(math.ceil(float(self.item_count) /
1515 1430 self.items_per_page))
1516 1431 self.last_page = self.first_page + self.page_count - 1
1517 1432
1518 1433 # Make sure that the requested page number is the range of
1519 1434 # valid pages
1520 1435 if self.page > self.last_page:
1521 1436 self.page = self.last_page
1522 1437 elif self.page < self.first_page:
1523 1438 self.page = self.first_page
1524 1439
1525 1440 # Note: the number of items on this page can be less than
1526 1441 # items_per_page if the last page is not full
1527 1442 self.first_item = max(0, (self.item_count) - (self.page *
1528 1443 items_per_page))
1529 1444 self.last_item = ((self.item_count - 1) - items_per_page *
1530 1445 (self.page - 1))
1531 1446
1532 1447 self.items = list(self.collection[self.first_item:self.last_item + 1])
1533 1448
1534 1449 # Links to previous and next page
1535 1450 if self.page > self.first_page:
1536 1451 self.previous_page = self.page - 1
1537 1452 else:
1538 1453 self.previous_page = None
1539 1454
1540 1455 if self.page < self.last_page:
1541 1456 self.next_page = self.page + 1
1542 1457 else:
1543 1458 self.next_page = None
1544 1459
1545 1460 # No items available
1546 1461 else:
1547 1462 self.first_page = None
1548 1463 self.page_count = 0
1549 1464 self.last_page = None
1550 1465 self.first_item = None
1551 1466 self.last_item = None
1552 1467 self.previous_page = None
1553 1468 self.next_page = None
1554 1469 self.items = []
1555 1470
1556 1471 # This is a subclass of the 'list' type. Initialise the list now.
1557 1472 list.__init__(self, reversed(self.items))
1558 1473
1559 1474
1560 1475 def breadcrumb_repo_link(repo):
1561 1476 """
1562 1477 Makes a breadcrumbs path link to repo
1563 1478
1564 1479 ex::
1565 1480 group >> subgroup >> repo
1566 1481
1567 1482 :param repo: a Repository instance
1568 1483 """
1569 1484
1570 1485 path = [
1571 1486 link_to(group.name, route_path('repo_group_home', repo_group_name=group.group_name))
1572 1487 for group in repo.groups_with_parents
1573 1488 ] + [
1574 1489 link_to(repo.just_name, route_path('repo_summary', repo_name=repo.repo_name))
1575 1490 ]
1576 1491
1577 1492 return literal(' &raquo; '.join(path))
1578 1493
1579 1494
1580 1495 def format_byte_size_binary(file_size):
1581 1496 """
1582 1497 Formats file/folder sizes to standard.
1583 1498 """
1584 1499 if file_size is None:
1585 1500 file_size = 0
1586 1501
1587 1502 formatted_size = format_byte_size(file_size, binary=True)
1588 1503 return formatted_size
1589 1504
1590 1505
1591 1506 def urlify_text(text_, safe=True):
1592 1507 """
1593 1508 Extrac urls from text and make html links out of them
1594 1509
1595 1510 :param text_:
1596 1511 """
1597 1512
1598 1513 url_pat = re.compile(r'''(http[s]?://(?:[a-zA-Z]|[0-9]|[$-_@#.&+]'''
1599 1514 '''|[!*\(\),]|(?:%[0-9a-fA-F][0-9a-fA-F]))+)''')
1600 1515
1601 1516 def url_func(match_obj):
1602 1517 url_full = match_obj.groups()[0]
1603 1518 return '<a href="%(url)s">%(url)s</a>' % ({'url': url_full})
1604 1519 _newtext = url_pat.sub(url_func, text_)
1605 1520 if safe:
1606 1521 return literal(_newtext)
1607 1522 return _newtext
1608 1523
1609 1524
1610 1525 def urlify_commits(text_, repository):
1611 1526 """
1612 1527 Extract commit ids from text and make link from them
1613 1528
1614 1529 :param text_:
1615 1530 :param repository: repo name to build the URL with
1616 1531 """
1617 1532
1618 1533 URL_PAT = re.compile(r'(^|\s)([0-9a-fA-F]{12,40})($|\s)')
1619 1534
1620 1535 def url_func(match_obj):
1621 1536 commit_id = match_obj.groups()[1]
1622 1537 pref = match_obj.groups()[0]
1623 1538 suf = match_obj.groups()[2]
1624 1539
1625 1540 tmpl = (
1626 1541 '%(pref)s<a class="%(cls)s" href="%(url)s">'
1627 1542 '%(commit_id)s</a>%(suf)s'
1628 1543 )
1629 1544 return tmpl % {
1630 1545 'pref': pref,
1631 1546 'cls': 'revision-link',
1632 1547 'url': route_url('repo_commit', repo_name=repository,
1633 1548 commit_id=commit_id),
1634 1549 'commit_id': commit_id,
1635 1550 'suf': suf
1636 1551 }
1637 1552
1638 1553 newtext = URL_PAT.sub(url_func, text_)
1639 1554
1640 1555 return newtext
1641 1556
1642 1557
1643 1558 def _process_url_func(match_obj, repo_name, uid, entry,
1644 1559 return_raw_data=False, link_format='html'):
1645 1560 pref = ''
1646 1561 if match_obj.group().startswith(' '):
1647 1562 pref = ' '
1648 1563
1649 1564 issue_id = ''.join(match_obj.groups())
1650 1565
1651 1566 if link_format == 'html':
1652 1567 tmpl = (
1653 1568 '%(pref)s<a class="%(cls)s" href="%(url)s">'
1654 1569 '%(issue-prefix)s%(id-repr)s'
1655 1570 '</a>')
1656 1571 elif link_format == 'rst':
1657 1572 tmpl = '`%(issue-prefix)s%(id-repr)s <%(url)s>`_'
1658 1573 elif link_format == 'markdown':
1659 1574 tmpl = '[%(issue-prefix)s%(id-repr)s](%(url)s)'
1660 1575 else:
1661 1576 raise ValueError('Bad link_format:{}'.format(link_format))
1662 1577
1663 1578 (repo_name_cleaned,
1664 1579 parent_group_name) = RepoGroupModel().\
1665 1580 _get_group_name_and_parent(repo_name)
1666 1581
1667 1582 # variables replacement
1668 1583 named_vars = {
1669 1584 'id': issue_id,
1670 1585 'repo': repo_name,
1671 1586 'repo_name': repo_name_cleaned,
1672 1587 'group_name': parent_group_name
1673 1588 }
1674 1589 # named regex variables
1675 1590 named_vars.update(match_obj.groupdict())
1676 1591 _url = string.Template(entry['url']).safe_substitute(**named_vars)
1677 1592
1678 1593 data = {
1679 1594 'pref': pref,
1680 1595 'cls': 'issue-tracker-link',
1681 1596 'url': _url,
1682 1597 'id-repr': issue_id,
1683 1598 'issue-prefix': entry['pref'],
1684 1599 'serv': entry['url'],
1685 1600 }
1686 1601 if return_raw_data:
1687 1602 return {
1688 1603 'id': issue_id,
1689 1604 'url': _url
1690 1605 }
1691 1606 return tmpl % data
1692 1607
1693 1608
1694 1609 def get_active_pattern_entries(repo_name):
1695 1610 repo = None
1696 1611 if repo_name:
1697 1612 # Retrieving repo_name to avoid invalid repo_name to explode on
1698 1613 # IssueTrackerSettingsModel but still passing invalid name further down
1699 1614 repo = Repository.get_by_repo_name(repo_name, cache=True)
1700 1615
1701 1616 settings_model = IssueTrackerSettingsModel(repo=repo)
1702 1617 active_entries = settings_model.get_settings(cache=True)
1703 1618 return active_entries
1704 1619
1705 1620
1706 1621 def process_patterns(text_string, repo_name, link_format='html',
1707 1622 active_entries=None):
1708 1623
1709 1624 allowed_formats = ['html', 'rst', 'markdown']
1710 1625 if link_format not in allowed_formats:
1711 1626 raise ValueError('Link format can be only one of:{} got {}'.format(
1712 1627 allowed_formats, link_format))
1713 1628
1714 1629 active_entries = active_entries or get_active_pattern_entries(repo_name)
1715 1630 issues_data = []
1716 1631 newtext = text_string
1717 1632
1718 1633 for uid, entry in active_entries.items():
1719 1634 log.debug('found issue tracker entry with uid %s', uid)
1720 1635
1721 1636 if not (entry['pat'] and entry['url']):
1722 1637 log.debug('skipping due to missing data')
1723 1638 continue
1724 1639
1725 1640 log.debug('issue tracker entry: uid: `%s` PAT:%s URL:%s PREFIX:%s',
1726 1641 uid, entry['pat'], entry['url'], entry['pref'])
1727 1642
1728 1643 try:
1729 1644 pattern = re.compile(r'%s' % entry['pat'])
1730 1645 except re.error:
1731 1646 log.exception(
1732 1647 'issue tracker pattern: `%s` failed to compile',
1733 1648 entry['pat'])
1734 1649 continue
1735 1650
1736 1651 data_func = partial(
1737 1652 _process_url_func, repo_name=repo_name, entry=entry, uid=uid,
1738 1653 return_raw_data=True)
1739 1654
1740 1655 for match_obj in pattern.finditer(text_string):
1741 1656 issues_data.append(data_func(match_obj))
1742 1657
1743 1658 url_func = partial(
1744 1659 _process_url_func, repo_name=repo_name, entry=entry, uid=uid,
1745 1660 link_format=link_format)
1746 1661
1747 1662 newtext = pattern.sub(url_func, newtext)
1748 1663 log.debug('processed prefix:uid `%s`', uid)
1749 1664
1750 1665 return newtext, issues_data
1751 1666
1752 1667
1753 1668 def urlify_commit_message(commit_text, repository=None,
1754 1669 active_pattern_entries=None):
1755 1670 """
1756 1671 Parses given text message and makes proper links.
1757 1672 issues are linked to given issue-server, and rest is a commit link
1758 1673
1759 1674 :param commit_text:
1760 1675 :param repository:
1761 1676 """
1762 1677 def escaper(string):
1763 1678 return string.replace('<', '&lt;').replace('>', '&gt;')
1764 1679
1765 1680 newtext = escaper(commit_text)
1766 1681
1767 1682 # extract http/https links and make them real urls
1768 1683 newtext = urlify_text(newtext, safe=False)
1769 1684
1770 1685 # urlify commits - extract commit ids and make link out of them, if we have
1771 1686 # the scope of repository present.
1772 1687 if repository:
1773 1688 newtext = urlify_commits(newtext, repository)
1774 1689
1775 1690 # process issue tracker patterns
1776 1691 newtext, issues = process_patterns(newtext, repository or '',
1777 1692 active_entries=active_pattern_entries)
1778 1693
1779 1694 return literal(newtext)
1780 1695
1781 1696
1782 1697 def render_binary(repo_name, file_obj):
1783 1698 """
1784 1699 Choose how to render a binary file
1785 1700 """
1786 1701
1787 1702 filename = file_obj.name
1788 1703
1789 1704 # images
1790 1705 for ext in ['*.png', '*.jpg', '*.ico', '*.gif']:
1791 1706 if fnmatch.fnmatch(filename, pat=ext):
1792 1707 alt = escape(filename)
1793 1708 src = route_path(
1794 1709 'repo_file_raw', repo_name=repo_name,
1795 1710 commit_id=file_obj.commit.raw_id,
1796 1711 f_path=file_obj.path)
1797 1712 return literal(
1798 1713 '<img class="rendered-binary" alt="{}" src="{}">'.format(alt, src))
1799 1714
1800 1715
1801 1716 def renderer_from_filename(filename, exclude=None):
1802 1717 """
1803 1718 choose a renderer based on filename, this works only for text based files
1804 1719 """
1805 1720
1806 1721 # ipython
1807 1722 for ext in ['*.ipynb']:
1808 1723 if fnmatch.fnmatch(filename, pat=ext):
1809 1724 return 'jupyter'
1810 1725
1811 1726 is_markup = MarkupRenderer.renderer_from_filename(filename, exclude=exclude)
1812 1727 if is_markup:
1813 1728 return is_markup
1814 1729 return None
1815 1730
1816 1731
1817 1732 def render(source, renderer='rst', mentions=False, relative_urls=None,
1818 1733 repo_name=None):
1819 1734
1820 1735 def maybe_convert_relative_links(html_source):
1821 1736 if relative_urls:
1822 1737 return relative_links(html_source, relative_urls)
1823 1738 return html_source
1824 1739
1825 1740 if renderer == 'plain':
1826 1741 return literal(
1827 1742 MarkupRenderer.plain(source, leading_newline=False))
1828 1743
1829 1744 elif renderer == 'rst':
1830 1745 if repo_name:
1831 1746 # process patterns on comments if we pass in repo name
1832 1747 source, issues = process_patterns(
1833 1748 source, repo_name, link_format='rst')
1834 1749
1835 1750 return literal(
1836 1751 '<div class="rst-block">%s</div>' %
1837 1752 maybe_convert_relative_links(
1838 1753 MarkupRenderer.rst(source, mentions=mentions)))
1839 1754
1840 1755 elif renderer == 'markdown':
1841 1756 if repo_name:
1842 1757 # process patterns on comments if we pass in repo name
1843 1758 source, issues = process_patterns(
1844 1759 source, repo_name, link_format='markdown')
1845 1760
1846 1761 return literal(
1847 1762 '<div class="markdown-block">%s</div>' %
1848 1763 maybe_convert_relative_links(
1849 1764 MarkupRenderer.markdown(source, flavored=True,
1850 1765 mentions=mentions)))
1851 1766
1852 1767 elif renderer == 'jupyter':
1853 1768 return literal(
1854 1769 '<div class="ipynb">%s</div>' %
1855 1770 maybe_convert_relative_links(
1856 1771 MarkupRenderer.jupyter(source)))
1857 1772
1858 1773 # None means just show the file-source
1859 1774 return None
1860 1775
1861 1776
1862 1777 def commit_status(repo, commit_id):
1863 1778 return ChangesetStatusModel().get_status(repo, commit_id)
1864 1779
1865 1780
1866 1781 def commit_status_lbl(commit_status):
1867 1782 return dict(ChangesetStatus.STATUSES).get(commit_status)
1868 1783
1869 1784
1870 1785 def commit_time(repo_name, commit_id):
1871 1786 repo = Repository.get_by_repo_name(repo_name)
1872 1787 commit = repo.get_commit(commit_id=commit_id)
1873 1788 return commit.date
1874 1789
1875 1790
1876 1791 def get_permission_name(key):
1877 1792 return dict(Permission.PERMS).get(key)
1878 1793
1879 1794
1880 1795 def journal_filter_help(request):
1881 1796 _ = request.translate
1882 1797 from rhodecode.lib.audit_logger import ACTIONS
1883 1798 actions = '\n'.join(textwrap.wrap(', '.join(sorted(ACTIONS.keys())), 80))
1884 1799
1885 1800 return _(
1886 1801 'Example filter terms:\n' +
1887 1802 ' repository:vcs\n' +
1888 1803 ' username:marcin\n' +
1889 1804 ' username:(NOT marcin)\n' +
1890 1805 ' action:*push*\n' +
1891 1806 ' ip:127.0.0.1\n' +
1892 1807 ' date:20120101\n' +
1893 1808 ' date:[20120101100000 TO 20120102]\n' +
1894 1809 '\n' +
1895 1810 'Actions: {actions}\n' +
1896 1811 '\n' +
1897 1812 'Generate wildcards using \'*\' character:\n' +
1898 1813 ' "repository:vcs*" - search everything starting with \'vcs\'\n' +
1899 1814 ' "repository:*vcs*" - search for repository containing \'vcs\'\n' +
1900 1815 '\n' +
1901 1816 'Optional AND / OR operators in queries\n' +
1902 1817 ' "repository:vcs OR repository:test"\n' +
1903 1818 ' "username:test AND repository:test*"\n'
1904 1819 ).format(actions=actions)
1905 1820
1906 1821
1907 def search_filter_help(searcher, request):
1908 _ = request.translate
1909
1910 terms = ''
1911 return _(
1912 'Example filter terms for `{searcher}` search:\n' +
1913 '{terms}\n' +
1914 'Generate wildcards using \'*\' character:\n' +
1915 ' "repo_name:vcs*" - search everything starting with \'vcs\'\n' +
1916 ' "repo_name:*vcs*" - search for repository containing \'vcs\'\n' +
1917 '\n' +
1918 'Optional AND / OR operators in queries\n' +
1919 ' "repo_name:vcs OR repo_name:test"\n' +
1920 ' "owner:test AND repo_name:test*"\n' +
1921 'More: {search_doc}'
1922 ).format(searcher=searcher.name,
1923 terms=terms, search_doc=searcher.query_lang_doc)
1924
1925
1926 1822 def not_mapped_error(repo_name):
1927 1823 from rhodecode.translation import _
1928 1824 flash(_('%s repository is not mapped to db perhaps'
1929 1825 ' it was created or renamed from the filesystem'
1930 1826 ' please run the application again'
1931 1827 ' in order to rescan repositories') % repo_name, category='error')
1932 1828
1933 1829
1934 1830 def ip_range(ip_addr):
1935 1831 from rhodecode.model.db import UserIpMap
1936 1832 s, e = UserIpMap._get_ip_range(ip_addr)
1937 1833 return '%s - %s' % (s, e)
1938 1834
1939 1835
1940 1836 def form(url, method='post', needs_csrf_token=True, **attrs):
1941 1837 """Wrapper around webhelpers.tags.form to prevent CSRF attacks."""
1942 1838 if method.lower() != 'get' and needs_csrf_token:
1943 1839 raise Exception(
1944 1840 'Forms to POST/PUT/DELETE endpoints should have (in general) a ' +
1945 1841 'CSRF token. If the endpoint does not require such token you can ' +
1946 1842 'explicitly set the parameter needs_csrf_token to false.')
1947 1843
1948 1844 return wh_form(url, method=method, **attrs)
1949 1845
1950 1846
1951 1847 def secure_form(form_url, method="POST", multipart=False, **attrs):
1952 1848 """Start a form tag that points the action to an url. This
1953 1849 form tag will also include the hidden field containing
1954 1850 the auth token.
1955 1851
1956 1852 The url options should be given either as a string, or as a
1957 1853 ``url()`` function. The method for the form defaults to POST.
1958 1854
1959 1855 Options:
1960 1856
1961 1857 ``multipart``
1962 1858 If set to True, the enctype is set to "multipart/form-data".
1963 1859 ``method``
1964 1860 The method to use when submitting the form, usually either
1965 1861 "GET" or "POST". If "PUT", "DELETE", or another verb is used, a
1966 1862 hidden input with name _method is added to simulate the verb
1967 1863 over POST.
1968 1864
1969 1865 """
1970 1866 from webhelpers.pylonslib.secure_form import insecure_form
1971 1867
1972 1868 if 'request' in attrs:
1973 1869 session = attrs['request'].session
1974 1870 del attrs['request']
1975 1871 else:
1976 1872 raise ValueError(
1977 1873 'Calling this form requires request= to be passed as argument')
1978 1874
1979 1875 form = insecure_form(form_url, method, multipart, **attrs)
1980 1876 token = literal(
1981 1877 '<input type="hidden" id="{}" name="{}" value="{}">'.format(
1982 1878 csrf_token_key, csrf_token_key, get_csrf_token(session)))
1983 1879
1984 1880 return literal("%s\n%s" % (form, token))
1985 1881
1986 1882
1987 1883 def dropdownmenu(name, selected, options, enable_filter=False, **attrs):
1988 1884 select_html = select(name, selected, options, **attrs)
1989 1885 select2 = """
1990 1886 <script>
1991 1887 $(document).ready(function() {
1992 1888 $('#%s').select2({
1993 1889 containerCssClass: 'drop-menu',
1994 1890 dropdownCssClass: 'drop-menu-dropdown',
1995 1891 dropdownAutoWidth: true%s
1996 1892 });
1997 1893 });
1998 1894 </script>
1999 1895 """
2000 1896 filter_option = """,
2001 1897 minimumResultsForSearch: -1
2002 1898 """
2003 1899 input_id = attrs.get('id') or name
2004 1900 filter_enabled = "" if enable_filter else filter_option
2005 1901 select_script = literal(select2 % (input_id, filter_enabled))
2006 1902
2007 1903 return literal(select_html+select_script)
2008 1904
2009 1905
2010 1906 def get_visual_attr(tmpl_context_var, attr_name):
2011 1907 """
2012 1908 A safe way to get a variable from visual variable of template context
2013 1909
2014 1910 :param tmpl_context_var: instance of tmpl_context, usually present as `c`
2015 1911 :param attr_name: name of the attribute we fetch from the c.visual
2016 1912 """
2017 1913 visual = getattr(tmpl_context_var, 'visual', None)
2018 1914 if not visual:
2019 1915 return
2020 1916 else:
2021 1917 return getattr(visual, attr_name, None)
2022 1918
2023 1919
2024 1920 def get_last_path_part(file_node):
2025 1921 if not file_node.path:
2026 1922 return u''
2027 1923
2028 1924 path = safe_unicode(file_node.path.split('/')[-1])
2029 1925 return u'../' + path
2030 1926
2031 1927
2032 1928 def route_url(*args, **kwargs):
2033 1929 """
2034 1930 Wrapper around pyramids `route_url` (fully qualified url) function.
2035 1931 """
2036 1932 req = get_current_request()
2037 1933 return req.route_url(*args, **kwargs)
2038 1934
2039 1935
2040 1936 def route_path(*args, **kwargs):
2041 1937 """
2042 1938 Wrapper around pyramids `route_path` function.
2043 1939 """
2044 1940 req = get_current_request()
2045 1941 return req.route_path(*args, **kwargs)
2046 1942
2047 1943
2048 1944 def route_path_or_none(*args, **kwargs):
2049 1945 try:
2050 1946 return route_path(*args, **kwargs)
2051 1947 except KeyError:
2052 1948 return None
2053 1949
2054 1950
2055 1951 def current_route_path(request, **kw):
2056 1952 new_args = request.GET.mixed()
2057 1953 new_args.update(kw)
2058 1954 return request.current_route_path(_query=new_args)
2059 1955
2060 1956
2061 1957 def api_call_example(method, args):
2062 1958 """
2063 1959 Generates an API call example via CURL
2064 1960 """
2065 1961 args_json = json.dumps(OrderedDict([
2066 1962 ('id', 1),
2067 1963 ('auth_token', 'SECRET'),
2068 1964 ('method', method),
2069 1965 ('args', args)
2070 1966 ]))
2071 1967 return literal(
2072 1968 "curl {api_url} -X POST -H 'content-type:text/plain' --data-binary '{data}'"
2073 1969 "<br/><br/>SECRET can be found in <a href=\"{token_url}\">auth-tokens</a> page, "
2074 1970 "and needs to be of `api calls` role."
2075 1971 .format(
2076 1972 api_url=route_url('apiv2'),
2077 1973 token_url=route_url('my_account_auth_tokens'),
2078 1974 data=args_json))
2079 1975
2080 1976
2081 1977 def notification_description(notification, request):
2082 1978 """
2083 1979 Generate notification human readable description based on notification type
2084 1980 """
2085 1981 from rhodecode.model.notification import NotificationModel
2086 1982 return NotificationModel().make_description(
2087 1983 notification, translate=request.translate)
2088 1984
2089 1985
2090 1986 def go_import_header(request, db_repo=None):
2091 1987 """
2092 1988 Creates a header for go-import functionality in Go Lang
2093 1989 """
2094 1990
2095 1991 if not db_repo:
2096 1992 return
2097 1993 if 'go-get' not in request.GET:
2098 1994 return
2099 1995
2100 1996 clone_url = db_repo.clone_url()
2101 1997 prefix = re.split(r'^https?:\/\/', clone_url)[-1]
2102 1998 # we have a repo and go-get flag,
2103 1999 return literal('<meta name="go-import" content="{} {} {}">'.format(
2104 2000 prefix, db_repo.repo_type, clone_url))
2105 2001
2106 2002
2107 2003 def reviewer_as_json(*args, **kwargs):
2108 2004 from rhodecode.apps.repository.utils import reviewer_as_json as _reviewer_as_json
2109 2005 return _reviewer_as_json(*args, **kwargs)
2006
2007
2008 def get_repo_view_type(request):
2009 route_name = request.matched_route.name
2010 route_to_view_type = {
2011 'repo_changelog': 'changelog',
2012 'repo_files': 'files',
2013 'repo_summary': 'summary',
2014 'repo_commit': 'commit'
2015
2016 }
2017 return route_to_view_type.get(route_name)
@@ -1,59 +1,98 b''
1 1 # -*- coding: utf-8 -*-
2 2
3 3 # Copyright (C) 2012-2018 RhodeCode GmbH
4 4 #
5 5 # This program is free software: you can redistribute it and/or modify
6 6 # it under the terms of the GNU Affero General Public License, version 3
7 7 # (only), as published by the Free Software Foundation.
8 8 #
9 9 # This program is distributed in the hope that it will be useful,
10 10 # but WITHOUT ANY WARRANTY; without even the implied warranty of
11 11 # MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
12 12 # GNU General Public License for more details.
13 13 #
14 14 # You should have received a copy of the GNU Affero General Public License
15 15 # along with this program. If not, see <http://www.gnu.org/licenses/>.
16 16 #
17 17 # This program is dual-licensed. If you wish to learn more about the
18 18 # RhodeCode Enterprise Edition, including its added features, Support services,
19 19 # and proprietary license terms, please see https://rhodecode.com/licenses/
20 20
21 21 """
22 22 Index schema for RhodeCode
23 23 """
24 24
25 25 import importlib
26 26 import logging
27 27
28 from rhodecode.lib.index.search_utils import normalize_text_for_matching
29
28 30 log = logging.getLogger(__name__)
29 31
30 32 # leave defaults for backward compat
31 33 default_searcher = 'rhodecode.lib.index.whoosh'
32 34 default_location = '%(here)s/data/index'
33 35
36 ES_VERSION_2 = '2'
37 ES_VERSION_6 = '6'
38 # for legacy reasons we keep 2 compat as default
39 DEFAULT_ES_VERSION = ES_VERSION_2
34 40
35 class BaseSearch(object):
41 from rhodecode_tools.lib.fts_index.elasticsearch_engine_6 import \
42 ES_CONFIG # pragma: no cover
43
44
45 class BaseSearcher(object):
36 46 query_lang_doc = ''
47 es_version = None
48 name = None
37 49
38 50 def __init__(self):
39 51 pass
40 52
41 53 def cleanup(self):
42 54 pass
43 55
44 56 def search(self, query, document_type, search_user, repo_name=None,
45 57 raise_on_exc=True):
46 58 raise Exception('NotImplemented')
47 59
60 @staticmethod
61 def query_to_mark(query, default_field=None):
62 """
63 Formats the query to mark token for jquery.mark.js highlighting. ES could
64 have a different format optionally.
48 65
49 def searcher_from_config(config, prefix='search.'):
66 :param default_field:
67 :param query:
68 """
69 return ' '.join(normalize_text_for_matching(query).split())
70
71 @property
72 def is_es_6(self):
73 return self.es_version == ES_VERSION_6
74
75 def get_handlers(self):
76 return {}
77
78
79 def search_config(config, prefix='search.'):
50 80 _config = {}
51 81 for key in config.keys():
52 82 if key.startswith(prefix):
53 83 _config[key[len(prefix):]] = config[key]
84 return _config
85
86
87 def searcher_from_config(config, prefix='search.'):
88 _config = search_config(config, prefix)
54 89
55 90 if 'location' not in _config:
56 91 _config['location'] = default_location
92 if 'es_version' not in _config:
93 # use old legacy ES version set to 2
94 _config['es_version'] = '2'
95
57 96 imported = importlib.import_module(_config.get('module', default_searcher))
58 searcher = imported.Search(config=_config)
97 searcher = imported.Searcher(config=_config)
59 98 return searcher
@@ -1,280 +1,286 b''
1 1 # -*- coding: utf-8 -*-
2 2
3 3 # Copyright (C) 2012-2018 RhodeCode GmbH
4 4 #
5 5 # This program is free software: you can redistribute it and/or modify
6 6 # it under the terms of the GNU Affero General Public License, version 3
7 7 # (only), as published by the Free Software Foundation.
8 8 #
9 9 # This program is distributed in the hope that it will be useful,
10 10 # but WITHOUT ANY WARRANTY; without even the implied warranty of
11 11 # MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
12 12 # GNU General Public License for more details.
13 13 #
14 14 # You should have received a copy of the GNU Affero General Public License
15 15 # along with this program. If not, see <http://www.gnu.org/licenses/>.
16 16 #
17 17 # This program is dual-licensed. If you wish to learn more about the
18 18 # RhodeCode Enterprise Edition, including its added features, Support services,
19 19 # and proprietary license terms, please see https://rhodecode.com/licenses/
20 20
21 21 """
22 22 Index schema for RhodeCode
23 23 """
24 24
25 25 from __future__ import absolute_import
26 26 import os
27 27 import re
28 28 import logging
29 29
30 30 from whoosh import query as query_lib
31 31 from whoosh.highlight import HtmlFormatter, ContextFragmenter
32 32 from whoosh.index import create_in, open_dir, exists_in, EmptyIndexError
33 33 from whoosh.qparser import QueryParser, QueryParserError
34 34
35 35 import rhodecode.lib.helpers as h
36 from rhodecode.lib.index import BaseSearch
36 from rhodecode.lib.index import BaseSearcher
37 37 from rhodecode.lib.utils2 import safe_unicode
38 38
39 39 log = logging.getLogger(__name__)
40 40
41 41
42 42 try:
43 43 # we first try to import from rhodecode tools, fallback to copies if
44 44 # we're unable to
45 45 from rhodecode_tools.lib.fts_index.whoosh_schema import (
46 46 ANALYZER, FILE_INDEX_NAME, FILE_SCHEMA, COMMIT_INDEX_NAME,
47 47 COMMIT_SCHEMA)
48 48 except ImportError:
49 49 log.warning('rhodecode_tools schema not available, doing a fallback '
50 50 'import from `rhodecode.lib.index.whoosh_fallback_schema`')
51 51 from rhodecode.lib.index.whoosh_fallback_schema import (
52 52 ANALYZER, FILE_INDEX_NAME, FILE_SCHEMA, COMMIT_INDEX_NAME,
53 53 COMMIT_SCHEMA)
54 54
55 55
56 56 FORMATTER = HtmlFormatter('span', between='\n<span class="break">...</span>\n')
57 57 FRAGMENTER = ContextFragmenter(200)
58 58
59 59 log = logging.getLogger(__name__)
60 60
61 61
62 class Search(BaseSearch):
62 class WhooshSearcher(BaseSearcher):
63 63 # this also shows in UI
64 64 query_lang_doc = 'http://whoosh.readthedocs.io/en/latest/querylang.html'
65 65 name = 'whoosh'
66 66
67 67 def __init__(self, config):
68 super(Search, self).__init__()
68 super(Searcher, self).__init__()
69 69 self.config = config
70 70 if not os.path.isdir(self.config['location']):
71 71 os.makedirs(self.config['location'])
72 72
73 73 opener = create_in
74 74 if exists_in(self.config['location'], indexname=FILE_INDEX_NAME):
75 75 opener = open_dir
76 76 file_index = opener(self.config['location'], schema=FILE_SCHEMA,
77 77 indexname=FILE_INDEX_NAME)
78 78
79 79 opener = create_in
80 80 if exists_in(self.config['location'], indexname=COMMIT_INDEX_NAME):
81 81 opener = open_dir
82 82 changeset_index = opener(self.config['location'], schema=COMMIT_SCHEMA,
83 83 indexname=COMMIT_INDEX_NAME)
84 84
85 85 self.commit_schema = COMMIT_SCHEMA
86 86 self.commit_index = changeset_index
87 87 self.file_schema = FILE_SCHEMA
88 88 self.file_index = file_index
89 89 self.searcher = None
90 90
91 91 def cleanup(self):
92 92 if self.searcher:
93 93 self.searcher.close()
94 94
95 95 def _extend_query(self, query):
96 96 hashes = re.compile('([0-9a-f]{5,40})').findall(query)
97 97 if hashes:
98 98 hashes_or_query = ' OR '.join('commit_id:%s*' % h for h in hashes)
99 99 query = u'(%s) OR %s' % (query, hashes_or_query)
100 100 return query
101 101
102 102 def search(self, query, document_type, search_user,
103 103 repo_name=None, requested_page=1, page_limit=10, sort=None,
104 104 raise_on_exc=True):
105 105
106 106 original_query = query
107 107 query = self._extend_query(query)
108 108
109 109 log.debug(u'QUERY: %s on %s', query, document_type)
110 110 result = {
111 111 'results': [],
112 112 'count': 0,
113 113 'error': None,
114 114 'runtime': 0
115 115 }
116 116 search_type, index_name, schema_defn = self._prepare_for_search(
117 117 document_type)
118 118 self._init_searcher(index_name)
119 119 try:
120 120 qp = QueryParser(search_type, schema=schema_defn)
121 121 allowed_repos_filter = self._get_repo_filter(
122 122 search_user, repo_name)
123 123 try:
124 124 query = qp.parse(safe_unicode(query))
125 125 log.debug('query: %s (%s)', query, repr(query))
126 126
127 127 reverse, sortedby = False, None
128 128 if search_type == 'message':
129 129 if sort == 'oldfirst':
130 130 sortedby = 'date'
131 131 reverse = False
132 132 elif sort == 'newfirst':
133 133 sortedby = 'date'
134 134 reverse = True
135 135
136 136 whoosh_results = self.searcher.search(
137 137 query, filter=allowed_repos_filter, limit=None,
138 138 sortedby=sortedby, reverse=reverse)
139 139
140 140 # fixes for 32k limit that whoosh uses for highlight
141 141 whoosh_results.fragmenter.charlimit = None
142 142 res_ln = whoosh_results.scored_length()
143 143 result['runtime'] = whoosh_results.runtime
144 144 result['count'] = res_ln
145 145 result['results'] = WhooshResultWrapper(
146 146 search_type, res_ln, whoosh_results)
147 147
148 148 except QueryParserError:
149 149 result['error'] = 'Invalid search query. Try quoting it.'
150 150 except (EmptyIndexError, IOError, OSError):
151 151 msg = 'There is no index to search in. Please run whoosh indexer'
152 152 log.exception(msg)
153 153 result['error'] = msg
154 154 except Exception:
155 155 msg = 'An error occurred during this search operation'
156 156 log.exception(msg)
157 157 result['error'] = msg
158 158
159 159 return result
160 160
161 161 def statistics(self, translator):
162 162 _ = translator
163 163 stats = [
164 164 {'key': _('Index Type'), 'value': 'Whoosh'},
165 {'sep': True},
166
165 167 {'key': _('File Index'), 'value': str(self.file_index)},
166 {'key': _('Indexed documents'),
167 'value': self.file_index.doc_count()},
168 {'key': _('Last update'),
169 'value': h.time_to_datetime(self.file_index.last_modified())},
168 {'key': _('Indexed documents'), 'value': self.file_index.doc_count()},
169 {'key': _('Last update'), 'value': h.time_to_datetime(self.file_index.last_modified())},
170
171 {'sep': True},
172
170 173 {'key': _('Commit index'), 'value': str(self.commit_index)},
171 {'key': _('Indexed documents'),
172 'value': str(self.commit_index.doc_count())},
173 {'key': _('Last update'),
174 'value': h.time_to_datetime(self.commit_index.last_modified())}
174 {'key': _('Indexed documents'), 'value': str(self.commit_index.doc_count())},
175 {'key': _('Last update'), 'value': h.time_to_datetime(self.commit_index.last_modified())}
175 176 ]
176 177 return stats
177 178
178 179 def _get_repo_filter(self, auth_user, repo_name):
179 180
180 181 allowed_to_search = [
181 182 repo for repo, perm in
182 183 auth_user.permissions['repositories'].items()
183 184 if perm != 'repository.none']
184 185
185 186 if repo_name:
186 187 repo_filter = [query_lib.Term('repository', repo_name)]
187 188
188 189 elif 'hg.admin' in auth_user.permissions.get('global', []):
189 190 return None
190 191
191 192 else:
192 193 repo_filter = [query_lib.Term('repository', _rn)
193 194 for _rn in allowed_to_search]
194 195 # in case we're not allowed to search anywhere, it's a trick
195 196 # to tell whoosh we're filtering, on ALL results
196 197 repo_filter = repo_filter or [query_lib.Term('repository', '')]
197 198
198 199 return query_lib.Or(repo_filter)
199 200
200 201 def _prepare_for_search(self, cur_type):
201 202 search_type = {
202 203 'content': 'content',
203 204 'commit': 'message',
204 205 'path': 'path',
205 206 'repository': 'repository'
206 207 }.get(cur_type, 'content')
207 208
208 209 index_name = {
209 210 'content': FILE_INDEX_NAME,
210 211 'commit': COMMIT_INDEX_NAME,
211 212 'path': FILE_INDEX_NAME
212 213 }.get(cur_type, FILE_INDEX_NAME)
213 214
214 215 schema_defn = {
215 216 'content': self.file_schema,
216 217 'commit': self.commit_schema,
217 218 'path': self.file_schema
218 219 }.get(cur_type, self.file_schema)
219 220
220 221 log.debug('IDX: %s', index_name)
221 222 log.debug('SCHEMA: %s', schema_defn)
222 223 return search_type, index_name, schema_defn
223 224
224 225 def _init_searcher(self, index_name):
225 226 idx = open_dir(self.config['location'], indexname=index_name)
226 227 self.searcher = idx.searcher()
227 228 return self.searcher
228 229
229 230
231 Searcher = WhooshSearcher
232
233
230 234 class WhooshResultWrapper(object):
231 235 def __init__(self, search_type, total_hits, results):
232 236 self.search_type = search_type
233 237 self.results = results
234 238 self.total_hits = total_hits
235 239
236 240 def __str__(self):
237 241 return '<%s at %s>' % (self.__class__.__name__, len(self))
238 242
239 243 def __repr__(self):
240 244 return self.__str__()
241 245
242 246 def __len__(self):
243 247 return self.total_hits
244 248
245 249 def __iter__(self):
246 250 """
247 251 Allows Iteration over results,and lazy generate content
248 252
249 253 *Requires* implementation of ``__getitem__`` method.
250 254 """
251 255 for hit in self.results:
252 256 yield self.get_full_content(hit)
253 257
254 258 def __getitem__(self, key):
255 259 """
256 260 Slicing of resultWrapper
257 261 """
258 262 i, j = key.start, key.stop
259 263 for hit in self.results[i:j]:
260 264 yield self.get_full_content(hit)
261 265
262 266 def get_full_content(self, hit):
263 267 # TODO: marcink: this feels like an overkill, there's a lot of data
264 268 # inside hit object, and we don't need all
265 269 res = dict(hit)
270 # elastic search uses that, we set it empty so it fallbacks to regular HL logic
271 res['content_highlight'] = ''
266 272
267 273 f_path = '' # pragma: no cover
268 274 if self.search_type in ['content', 'path']:
269 275 f_path = res['path'][len(res['repository']):]
270 276 f_path = f_path.lstrip(os.sep)
271 277
272 278 if self.search_type == 'content':
273 279 res.update({'content_short_hl': hit.highlights('content'),
274 280 'f_path': f_path})
275 281 elif self.search_type == 'path':
276 282 res.update({'f_path': f_path})
277 283 elif self.search_type == 'message':
278 284 res.update({'message_hl': hit.highlights('message')})
279 285
280 286 return res
@@ -1,1011 +1,1022 b''
1 1 # -*- coding: utf-8 -*-
2 2
3 3 # Copyright (C) 2011-2018 RhodeCode GmbH
4 4 #
5 5 # This program is free software: you can redistribute it and/or modify
6 6 # it under the terms of the GNU Affero General Public License, version 3
7 7 # (only), as published by the Free Software Foundation.
8 8 #
9 9 # This program is distributed in the hope that it will be useful,
10 10 # but WITHOUT ANY WARRANTY; without even the implied warranty of
11 11 # MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
12 12 # GNU General Public License for more details.
13 13 #
14 14 # You should have received a copy of the GNU Affero General Public License
15 15 # along with this program. If not, see <http://www.gnu.org/licenses/>.
16 16 #
17 17 # This program is dual-licensed. If you wish to learn more about the
18 18 # RhodeCode Enterprise Edition, including its added features, Support services,
19 19 # and proprietary license terms, please see https://rhodecode.com/licenses/
20 20
21 21
22 22 """
23 23 Some simple helper functions
24 24 """
25 25
26 26 import collections
27 27 import datetime
28 28 import dateutil.relativedelta
29 29 import hashlib
30 30 import logging
31 31 import re
32 32 import sys
33 33 import time
34 34 import urllib
35 35 import urlobject
36 36 import uuid
37 37 import getpass
38 38
39 39 import pygments.lexers
40 40 import sqlalchemy
41 41 import sqlalchemy.engine.url
42 42 import sqlalchemy.exc
43 43 import sqlalchemy.sql
44 44 import webob
45 45 import pyramid.threadlocal
46 46
47 47 import rhodecode
48 48 from rhodecode.translation import _, _pluralize
49 49
50 50
51 51 def md5(s):
52 52 return hashlib.md5(s).hexdigest()
53 53
54 54
55 55 def md5_safe(s):
56 56 return md5(safe_str(s))
57 57
58 58
59 59 def sha1(s):
60 60 return hashlib.sha1(s).hexdigest()
61 61
62 62
63 63 def sha1_safe(s):
64 64 return sha1(safe_str(s))
65 65
66 66
67 67 def __get_lem(extra_mapping=None):
68 68 """
69 69 Get language extension map based on what's inside pygments lexers
70 70 """
71 71 d = collections.defaultdict(lambda: [])
72 72
73 73 def __clean(s):
74 74 s = s.lstrip('*')
75 75 s = s.lstrip('.')
76 76
77 77 if s.find('[') != -1:
78 78 exts = []
79 79 start, stop = s.find('['), s.find(']')
80 80
81 81 for suffix in s[start + 1:stop]:
82 82 exts.append(s[:s.find('[')] + suffix)
83 83 return [e.lower() for e in exts]
84 84 else:
85 85 return [s.lower()]
86 86
87 87 for lx, t in sorted(pygments.lexers.LEXERS.items()):
88 88 m = map(__clean, t[-2])
89 89 if m:
90 90 m = reduce(lambda x, y: x + y, m)
91 91 for ext in m:
92 92 desc = lx.replace('Lexer', '')
93 93 d[ext].append(desc)
94 94
95 95 data = dict(d)
96 96
97 97 extra_mapping = extra_mapping or {}
98 98 if extra_mapping:
99 99 for k, v in extra_mapping.items():
100 100 if k not in data:
101 101 # register new mapping2lexer
102 102 data[k] = [v]
103 103
104 104 return data
105 105
106 106
107 107 def str2bool(_str):
108 108 """
109 109 returns True/False value from given string, it tries to translate the
110 110 string into boolean
111 111
112 112 :param _str: string value to translate into boolean
113 113 :rtype: boolean
114 114 :returns: boolean from given string
115 115 """
116 116 if _str is None:
117 117 return False
118 118 if _str in (True, False):
119 119 return _str
120 120 _str = str(_str).strip().lower()
121 121 return _str in ('t', 'true', 'y', 'yes', 'on', '1')
122 122
123 123
124 124 def aslist(obj, sep=None, strip=True):
125 125 """
126 126 Returns given string separated by sep as list
127 127
128 128 :param obj:
129 129 :param sep:
130 130 :param strip:
131 131 """
132 132 if isinstance(obj, (basestring,)):
133 133 lst = obj.split(sep)
134 134 if strip:
135 135 lst = [v.strip() for v in lst]
136 136 return lst
137 137 elif isinstance(obj, (list, tuple)):
138 138 return obj
139 139 elif obj is None:
140 140 return []
141 141 else:
142 142 return [obj]
143 143
144 144
145 145 def convert_line_endings(line, mode):
146 146 """
147 147 Converts a given line "line end" accordingly to given mode
148 148
149 149 Available modes are::
150 150 0 - Unix
151 151 1 - Mac
152 152 2 - DOS
153 153
154 154 :param line: given line to convert
155 155 :param mode: mode to convert to
156 156 :rtype: str
157 157 :return: converted line according to mode
158 158 """
159 159 if mode == 0:
160 160 line = line.replace('\r\n', '\n')
161 161 line = line.replace('\r', '\n')
162 162 elif mode == 1:
163 163 line = line.replace('\r\n', '\r')
164 164 line = line.replace('\n', '\r')
165 165 elif mode == 2:
166 166 line = re.sub('\r(?!\n)|(?<!\r)\n', '\r\n', line)
167 167 return line
168 168
169 169
170 170 def detect_mode(line, default):
171 171 """
172 172 Detects line break for given line, if line break couldn't be found
173 173 given default value is returned
174 174
175 175 :param line: str line
176 176 :param default: default
177 177 :rtype: int
178 178 :return: value of line end on of 0 - Unix, 1 - Mac, 2 - DOS
179 179 """
180 180 if line.endswith('\r\n'):
181 181 return 2
182 182 elif line.endswith('\n'):
183 183 return 0
184 184 elif line.endswith('\r'):
185 185 return 1
186 186 else:
187 187 return default
188 188
189 189
190 190 def safe_int(val, default=None):
191 191 """
192 192 Returns int() of val if val is not convertable to int use default
193 193 instead
194 194
195 195 :param val:
196 196 :param default:
197 197 """
198 198
199 199 try:
200 200 val = int(val)
201 201 except (ValueError, TypeError):
202 202 val = default
203 203
204 204 return val
205 205
206 206
207 207 def safe_unicode(str_, from_encoding=None):
208 208 """
209 209 safe unicode function. Does few trick to turn str_ into unicode
210 210
211 211 In case of UnicodeDecode error, we try to return it with encoding detected
212 212 by chardet library if it fails fallback to unicode with errors replaced
213 213
214 214 :param str_: string to decode
215 215 :rtype: unicode
216 216 :returns: unicode object
217 217 """
218 218 if isinstance(str_, unicode):
219 219 return str_
220 220
221 221 if not from_encoding:
222 222 DEFAULT_ENCODINGS = aslist(rhodecode.CONFIG.get('default_encoding',
223 223 'utf8'), sep=',')
224 224 from_encoding = DEFAULT_ENCODINGS
225 225
226 226 if not isinstance(from_encoding, (list, tuple)):
227 227 from_encoding = [from_encoding]
228 228
229 229 try:
230 230 return unicode(str_)
231 231 except UnicodeDecodeError:
232 232 pass
233 233
234 234 for enc in from_encoding:
235 235 try:
236 236 return unicode(str_, enc)
237 237 except UnicodeDecodeError:
238 238 pass
239 239
240 240 try:
241 241 import chardet
242 242 encoding = chardet.detect(str_)['encoding']
243 243 if encoding is None:
244 244 raise Exception()
245 245 return str_.decode(encoding)
246 246 except (ImportError, UnicodeDecodeError, Exception):
247 247 return unicode(str_, from_encoding[0], 'replace')
248 248
249 249
250 250 def safe_str(unicode_, to_encoding=None):
251 251 """
252 252 safe str function. Does few trick to turn unicode_ into string
253 253
254 254 In case of UnicodeEncodeError, we try to return it with encoding detected
255 255 by chardet library if it fails fallback to string with errors replaced
256 256
257 257 :param unicode_: unicode to encode
258 258 :rtype: str
259 259 :returns: str object
260 260 """
261 261
262 262 # if it's not basestr cast to str
263 263 if not isinstance(unicode_, basestring):
264 264 return str(unicode_)
265 265
266 266 if isinstance(unicode_, str):
267 267 return unicode_
268 268
269 269 if not to_encoding:
270 270 DEFAULT_ENCODINGS = aslist(rhodecode.CONFIG.get('default_encoding',
271 271 'utf8'), sep=',')
272 272 to_encoding = DEFAULT_ENCODINGS
273 273
274 274 if not isinstance(to_encoding, (list, tuple)):
275 275 to_encoding = [to_encoding]
276 276
277 277 for enc in to_encoding:
278 278 try:
279 279 return unicode_.encode(enc)
280 280 except UnicodeEncodeError:
281 281 pass
282 282
283 283 try:
284 284 import chardet
285 285 encoding = chardet.detect(unicode_)['encoding']
286 286 if encoding is None:
287 287 raise UnicodeEncodeError()
288 288
289 289 return unicode_.encode(encoding)
290 290 except (ImportError, UnicodeEncodeError):
291 291 return unicode_.encode(to_encoding[0], 'replace')
292 292
293 293
294 294 def remove_suffix(s, suffix):
295 295 if s.endswith(suffix):
296 296 s = s[:-1 * len(suffix)]
297 297 return s
298 298
299 299
300 300 def remove_prefix(s, prefix):
301 301 if s.startswith(prefix):
302 302 s = s[len(prefix):]
303 303 return s
304 304
305 305
306 306 def find_calling_context(ignore_modules=None):
307 307 """
308 308 Look through the calling stack and return the frame which called
309 309 this function and is part of core module ( ie. rhodecode.* )
310 310
311 311 :param ignore_modules: list of modules to ignore eg. ['rhodecode.lib']
312 312 """
313 313
314 314 ignore_modules = ignore_modules or []
315 315
316 316 f = sys._getframe(2)
317 317 while f.f_back is not None:
318 318 name = f.f_globals.get('__name__')
319 319 if name and name.startswith(__name__.split('.')[0]):
320 320 if name not in ignore_modules:
321 321 return f
322 322 f = f.f_back
323 323 return None
324 324
325 325
326 326 def ping_connection(connection, branch):
327 327 if branch:
328 328 # "branch" refers to a sub-connection of a connection,
329 329 # we don't want to bother pinging on these.
330 330 return
331 331
332 332 # turn off "close with result". This flag is only used with
333 333 # "connectionless" execution, otherwise will be False in any case
334 334 save_should_close_with_result = connection.should_close_with_result
335 335 connection.should_close_with_result = False
336 336
337 337 try:
338 338 # run a SELECT 1. use a core select() so that
339 339 # the SELECT of a scalar value without a table is
340 340 # appropriately formatted for the backend
341 341 connection.scalar(sqlalchemy.sql.select([1]))
342 342 except sqlalchemy.exc.DBAPIError as err:
343 343 # catch SQLAlchemy's DBAPIError, which is a wrapper
344 344 # for the DBAPI's exception. It includes a .connection_invalidated
345 345 # attribute which specifies if this connection is a "disconnect"
346 346 # condition, which is based on inspection of the original exception
347 347 # by the dialect in use.
348 348 if err.connection_invalidated:
349 349 # run the same SELECT again - the connection will re-validate
350 350 # itself and establish a new connection. The disconnect detection
351 351 # here also causes the whole connection pool to be invalidated
352 352 # so that all stale connections are discarded.
353 353 connection.scalar(sqlalchemy.sql.select([1]))
354 354 else:
355 355 raise
356 356 finally:
357 357 # restore "close with result"
358 358 connection.should_close_with_result = save_should_close_with_result
359 359
360 360
361 361 def engine_from_config(configuration, prefix='sqlalchemy.', **kwargs):
362 362 """Custom engine_from_config functions."""
363 363 log = logging.getLogger('sqlalchemy.engine')
364 364 _ping_connection = configuration.pop('sqlalchemy.db1.ping_connection', None)
365 365
366 366 engine = sqlalchemy.engine_from_config(configuration, prefix, **kwargs)
367 367
368 368 def color_sql(sql):
369 369 color_seq = '\033[1;33m' # This is yellow: code 33
370 370 normal = '\x1b[0m'
371 371 return ''.join([color_seq, sql, normal])
372 372
373 373 if configuration['debug'] or _ping_connection:
374 374 sqlalchemy.event.listen(engine, "engine_connect", ping_connection)
375 375
376 376 if configuration['debug']:
377 377 # attach events only for debug configuration
378 378
379 379 def before_cursor_execute(conn, cursor, statement,
380 380 parameters, context, executemany):
381 381 setattr(conn, 'query_start_time', time.time())
382 382 log.info(color_sql(">>>>> STARTING QUERY >>>>>"))
383 383 calling_context = find_calling_context(ignore_modules=[
384 384 'rhodecode.lib.caching_query',
385 385 'rhodecode.model.settings',
386 386 ])
387 387 if calling_context:
388 388 log.info(color_sql('call context %s:%s' % (
389 389 calling_context.f_code.co_filename,
390 390 calling_context.f_lineno,
391 391 )))
392 392
393 393 def after_cursor_execute(conn, cursor, statement,
394 394 parameters, context, executemany):
395 395 delattr(conn, 'query_start_time')
396 396
397 397 sqlalchemy.event.listen(engine, "before_cursor_execute",
398 398 before_cursor_execute)
399 399 sqlalchemy.event.listen(engine, "after_cursor_execute",
400 400 after_cursor_execute)
401 401
402 402 return engine
403 403
404 404
405 405 def get_encryption_key(config):
406 406 secret = config.get('rhodecode.encrypted_values.secret')
407 407 default = config['beaker.session.secret']
408 408 return secret or default
409 409
410 410
411 411 def age(prevdate, now=None, show_short_version=False, show_suffix=True,
412 412 short_format=False):
413 413 """
414 414 Turns a datetime into an age string.
415 415 If show_short_version is True, this generates a shorter string with
416 416 an approximate age; ex. '1 day ago', rather than '1 day and 23 hours ago'.
417 417
418 418 * IMPORTANT*
419 419 Code of this function is written in special way so it's easier to
420 420 backport it to javascript. If you mean to update it, please also update
421 421 `jquery.timeago-extension.js` file
422 422
423 423 :param prevdate: datetime object
424 424 :param now: get current time, if not define we use
425 425 `datetime.datetime.now()`
426 426 :param show_short_version: if it should approximate the date and
427 427 return a shorter string
428 428 :param show_suffix:
429 429 :param short_format: show short format, eg 2D instead of 2 days
430 430 :rtype: unicode
431 431 :returns: unicode words describing age
432 432 """
433 433
434 434 def _get_relative_delta(now, prevdate):
435 435 base = dateutil.relativedelta.relativedelta(now, prevdate)
436 436 return {
437 437 'year': base.years,
438 438 'month': base.months,
439 439 'day': base.days,
440 440 'hour': base.hours,
441 441 'minute': base.minutes,
442 442 'second': base.seconds,
443 443 }
444 444
445 445 def _is_leap_year(year):
446 446 return year % 4 == 0 and (year % 100 != 0 or year % 400 == 0)
447 447
448 448 def get_month(prevdate):
449 449 return prevdate.month
450 450
451 451 def get_year(prevdate):
452 452 return prevdate.year
453 453
454 454 now = now or datetime.datetime.now()
455 455 order = ['year', 'month', 'day', 'hour', 'minute', 'second']
456 456 deltas = {}
457 457 future = False
458 458
459 459 if prevdate > now:
460 460 now_old = now
461 461 now = prevdate
462 462 prevdate = now_old
463 463 future = True
464 464 if future:
465 465 prevdate = prevdate.replace(microsecond=0)
466 466 # Get date parts deltas
467 467 for part in order:
468 468 rel_delta = _get_relative_delta(now, prevdate)
469 469 deltas[part] = rel_delta[part]
470 470
471 471 # Fix negative offsets (there is 1 second between 10:59:59 and 11:00:00,
472 472 # not 1 hour, -59 minutes and -59 seconds)
473 473 offsets = [[5, 60], [4, 60], [3, 24]]
474 474 for element in offsets: # seconds, minutes, hours
475 475 num = element[0]
476 476 length = element[1]
477 477
478 478 part = order[num]
479 479 carry_part = order[num - 1]
480 480
481 481 if deltas[part] < 0:
482 482 deltas[part] += length
483 483 deltas[carry_part] -= 1
484 484
485 485 # Same thing for days except that the increment depends on the (variable)
486 486 # number of days in the month
487 487 month_lengths = [31, 28, 31, 30, 31, 30, 31, 31, 30, 31, 30, 31]
488 488 if deltas['day'] < 0:
489 489 if get_month(prevdate) == 2 and _is_leap_year(get_year(prevdate)):
490 490 deltas['day'] += 29
491 491 else:
492 492 deltas['day'] += month_lengths[get_month(prevdate) - 1]
493 493
494 494 deltas['month'] -= 1
495 495
496 496 if deltas['month'] < 0:
497 497 deltas['month'] += 12
498 498 deltas['year'] -= 1
499 499
500 500 # Format the result
501 501 if short_format:
502 502 fmt_funcs = {
503 503 'year': lambda d: u'%dy' % d,
504 504 'month': lambda d: u'%dm' % d,
505 505 'day': lambda d: u'%dd' % d,
506 506 'hour': lambda d: u'%dh' % d,
507 507 'minute': lambda d: u'%dmin' % d,
508 508 'second': lambda d: u'%dsec' % d,
509 509 }
510 510 else:
511 511 fmt_funcs = {
512 512 'year': lambda d: _pluralize(u'${num} year', u'${num} years', d, mapping={'num': d}).interpolate(),
513 513 'month': lambda d: _pluralize(u'${num} month', u'${num} months', d, mapping={'num': d}).interpolate(),
514 514 'day': lambda d: _pluralize(u'${num} day', u'${num} days', d, mapping={'num': d}).interpolate(),
515 515 'hour': lambda d: _pluralize(u'${num} hour', u'${num} hours', d, mapping={'num': d}).interpolate(),
516 516 'minute': lambda d: _pluralize(u'${num} minute', u'${num} minutes', d, mapping={'num': d}).interpolate(),
517 517 'second': lambda d: _pluralize(u'${num} second', u'${num} seconds', d, mapping={'num': d}).interpolate(),
518 518 }
519 519
520 520 i = 0
521 521 for part in order:
522 522 value = deltas[part]
523 523 if value != 0:
524 524
525 525 if i < 5:
526 526 sub_part = order[i + 1]
527 527 sub_value = deltas[sub_part]
528 528 else:
529 529 sub_value = 0
530 530
531 531 if sub_value == 0 or show_short_version:
532 532 _val = fmt_funcs[part](value)
533 533 if future:
534 534 if show_suffix:
535 535 return _(u'in ${ago}', mapping={'ago': _val})
536 536 else:
537 537 return _(_val)
538 538
539 539 else:
540 540 if show_suffix:
541 541 return _(u'${ago} ago', mapping={'ago': _val})
542 542 else:
543 543 return _(_val)
544 544
545 545 val = fmt_funcs[part](value)
546 546 val_detail = fmt_funcs[sub_part](sub_value)
547 547 mapping = {'val': val, 'detail': val_detail}
548 548
549 549 if short_format:
550 550 datetime_tmpl = _(u'${val}, ${detail}', mapping=mapping)
551 551 if show_suffix:
552 552 datetime_tmpl = _(u'${val}, ${detail} ago', mapping=mapping)
553 553 if future:
554 554 datetime_tmpl = _(u'in ${val}, ${detail}', mapping=mapping)
555 555 else:
556 556 datetime_tmpl = _(u'${val} and ${detail}', mapping=mapping)
557 557 if show_suffix:
558 558 datetime_tmpl = _(u'${val} and ${detail} ago', mapping=mapping)
559 559 if future:
560 560 datetime_tmpl = _(u'in ${val} and ${detail}', mapping=mapping)
561 561
562 562 return datetime_tmpl
563 563 i += 1
564 564 return _(u'just now')
565 565
566 566
567 567 def cleaned_uri(uri):
568 568 """
569 569 Quotes '[' and ']' from uri if there is only one of them.
570 570 according to RFC3986 we cannot use such chars in uri
571 571 :param uri:
572 572 :return: uri without this chars
573 573 """
574 574 return urllib.quote(uri, safe='@$:/')
575 575
576 576
577 577 def uri_filter(uri):
578 578 """
579 579 Removes user:password from given url string
580 580
581 581 :param uri:
582 582 :rtype: unicode
583 583 :returns: filtered list of strings
584 584 """
585 585 if not uri:
586 586 return ''
587 587
588 588 proto = ''
589 589
590 590 for pat in ('https://', 'http://'):
591 591 if uri.startswith(pat):
592 592 uri = uri[len(pat):]
593 593 proto = pat
594 594 break
595 595
596 596 # remove passwords and username
597 597 uri = uri[uri.find('@') + 1:]
598 598
599 599 # get the port
600 600 cred_pos = uri.find(':')
601 601 if cred_pos == -1:
602 602 host, port = uri, None
603 603 else:
604 604 host, port = uri[:cred_pos], uri[cred_pos + 1:]
605 605
606 606 return filter(None, [proto, host, port])
607 607
608 608
609 609 def credentials_filter(uri):
610 610 """
611 611 Returns a url with removed credentials
612 612
613 613 :param uri:
614 614 """
615 615
616 616 uri = uri_filter(uri)
617 617 # check if we have port
618 618 if len(uri) > 2 and uri[2]:
619 619 uri[2] = ':' + uri[2]
620 620
621 621 return ''.join(uri)
622 622
623 623
624 624 def get_clone_url(request, uri_tmpl, repo_name, repo_id, **override):
625 625 qualifed_home_url = request.route_url('home')
626 626 parsed_url = urlobject.URLObject(qualifed_home_url)
627 627 decoded_path = safe_unicode(urllib.unquote(parsed_url.path.rstrip('/')))
628 628
629 629 args = {
630 630 'scheme': parsed_url.scheme,
631 631 'user': '',
632 632 'sys_user': getpass.getuser(),
633 633 # path if we use proxy-prefix
634 634 'netloc': parsed_url.netloc+decoded_path,
635 635 'hostname': parsed_url.hostname,
636 636 'prefix': decoded_path,
637 637 'repo': repo_name,
638 638 'repoid': str(repo_id)
639 639 }
640 640 args.update(override)
641 641 args['user'] = urllib.quote(safe_str(args['user']))
642 642
643 643 for k, v in args.items():
644 644 uri_tmpl = uri_tmpl.replace('{%s}' % k, v)
645 645
646 646 # remove leading @ sign if it's present. Case of empty user
647 647 url_obj = urlobject.URLObject(uri_tmpl)
648 648 url = url_obj.with_netloc(url_obj.netloc.lstrip('@'))
649 649
650 650 return safe_unicode(url)
651 651
652 652
653 653 def get_commit_safe(repo, commit_id=None, commit_idx=None, pre_load=None):
654 654 """
655 655 Safe version of get_commit if this commit doesn't exists for a
656 656 repository it returns a Dummy one instead
657 657
658 658 :param repo: repository instance
659 659 :param commit_id: commit id as str
660 660 :param pre_load: optional list of commit attributes to load
661 661 """
662 662 # TODO(skreft): remove these circular imports
663 663 from rhodecode.lib.vcs.backends.base import BaseRepository, EmptyCommit
664 664 from rhodecode.lib.vcs.exceptions import RepositoryError
665 665 if not isinstance(repo, BaseRepository):
666 666 raise Exception('You must pass an Repository '
667 667 'object as first argument got %s', type(repo))
668 668
669 669 try:
670 670 commit = repo.get_commit(
671 671 commit_id=commit_id, commit_idx=commit_idx, pre_load=pre_load)
672 672 except (RepositoryError, LookupError):
673 673 commit = EmptyCommit()
674 674 return commit
675 675
676 676
677 677 def datetime_to_time(dt):
678 678 if dt:
679 679 return time.mktime(dt.timetuple())
680 680
681 681
682 682 def time_to_datetime(tm):
683 683 if tm:
684 684 if isinstance(tm, basestring):
685 685 try:
686 686 tm = float(tm)
687 687 except ValueError:
688 688 return
689 689 return datetime.datetime.fromtimestamp(tm)
690 690
691 691
692 692 def time_to_utcdatetime(tm):
693 693 if tm:
694 694 if isinstance(tm, basestring):
695 695 try:
696 696 tm = float(tm)
697 697 except ValueError:
698 698 return
699 699 return datetime.datetime.utcfromtimestamp(tm)
700 700
701 701
702 702 MENTIONS_REGEX = re.compile(
703 703 # ^@ or @ without any special chars in front
704 704 r'(?:^@|[^a-zA-Z0-9\-\_\.]@)'
705 705 # main body starts with letter, then can be . - _
706 706 r'([a-zA-Z0-9]{1}[a-zA-Z0-9\-\_\.]+)',
707 707 re.VERBOSE | re.MULTILINE)
708 708
709 709
710 710 def extract_mentioned_users(s):
711 711 """
712 712 Returns unique usernames from given string s that have @mention
713 713
714 714 :param s: string to get mentions
715 715 """
716 716 usrs = set()
717 717 for username in MENTIONS_REGEX.findall(s):
718 718 usrs.add(username)
719 719
720 720 return sorted(list(usrs), key=lambda k: k.lower())
721 721
722 722
723 723 class AttributeDictBase(dict):
724 724 def __getstate__(self):
725 725 odict = self.__dict__ # get attribute dictionary
726 726 return odict
727 727
728 728 def __setstate__(self, dict):
729 729 self.__dict__ = dict
730 730
731 731 __setattr__ = dict.__setitem__
732 732 __delattr__ = dict.__delitem__
733 733
734 734
735 735 class StrictAttributeDict(AttributeDictBase):
736 736 """
737 737 Strict Version of Attribute dict which raises an Attribute error when
738 738 requested attribute is not set
739 739 """
740 740 def __getattr__(self, attr):
741 741 try:
742 742 return self[attr]
743 743 except KeyError:
744 744 raise AttributeError('%s object has no attribute %s' % (
745 745 self.__class__, attr))
746 746
747 747
748 748 class AttributeDict(AttributeDictBase):
749 749 def __getattr__(self, attr):
750 750 return self.get(attr, None)
751 751
752 752
753 753
754 754 class OrderedDefaultDict(collections.OrderedDict, collections.defaultdict):
755 755 def __init__(self, default_factory=None, *args, **kwargs):
756 756 # in python3 you can omit the args to super
757 757 super(OrderedDefaultDict, self).__init__(*args, **kwargs)
758 758 self.default_factory = default_factory
759 759
760 760
761 761 def fix_PATH(os_=None):
762 762 """
763 763 Get current active python path, and append it to PATH variable to fix
764 764 issues of subprocess calls and different python versions
765 765 """
766 766 if os_ is None:
767 767 import os
768 768 else:
769 769 os = os_
770 770
771 771 cur_path = os.path.split(sys.executable)[0]
772 772 if not os.environ['PATH'].startswith(cur_path):
773 773 os.environ['PATH'] = '%s:%s' % (cur_path, os.environ['PATH'])
774 774
775 775
776 776 def obfuscate_url_pw(engine):
777 777 _url = engine or ''
778 778 try:
779 779 _url = sqlalchemy.engine.url.make_url(engine)
780 780 if _url.password:
781 781 _url.password = 'XXXXX'
782 782 except Exception:
783 783 pass
784 784 return unicode(_url)
785 785
786 786
787 787 def get_server_url(environ):
788 788 req = webob.Request(environ)
789 789 return req.host_url + req.script_name
790 790
791 791
792 792 def unique_id(hexlen=32):
793 793 alphabet = "23456789ABCDEFGHJKLMNPQRSTUVWXYZabcdefghjklmnpqrstuvwxyz"
794 794 return suuid(truncate_to=hexlen, alphabet=alphabet)
795 795
796 796
797 797 def suuid(url=None, truncate_to=22, alphabet=None):
798 798 """
799 799 Generate and return a short URL safe UUID.
800 800
801 801 If the url parameter is provided, set the namespace to the provided
802 802 URL and generate a UUID.
803 803
804 804 :param url to get the uuid for
805 805 :truncate_to: truncate the basic 22 UUID to shorter version
806 806
807 807 The IDs won't be universally unique any longer, but the probability of
808 808 a collision will still be very low.
809 809 """
810 810 # Define our alphabet.
811 811 _ALPHABET = alphabet or "23456789ABCDEFGHJKLMNPQRSTUVWXYZ"
812 812
813 813 # If no URL is given, generate a random UUID.
814 814 if url is None:
815 815 unique_id = uuid.uuid4().int
816 816 else:
817 817 unique_id = uuid.uuid3(uuid.NAMESPACE_URL, url).int
818 818
819 819 alphabet_length = len(_ALPHABET)
820 820 output = []
821 821 while unique_id > 0:
822 822 digit = unique_id % alphabet_length
823 823 output.append(_ALPHABET[digit])
824 824 unique_id = int(unique_id / alphabet_length)
825 825 return "".join(output)[:truncate_to]
826 826
827 827
828 828 def get_current_rhodecode_user(request=None):
829 829 """
830 830 Gets rhodecode user from request
831 831 """
832 832 pyramid_request = request or pyramid.threadlocal.get_current_request()
833 833
834 834 # web case
835 835 if pyramid_request and hasattr(pyramid_request, 'user'):
836 836 return pyramid_request.user
837 837
838 838 # api case
839 839 if pyramid_request and hasattr(pyramid_request, 'rpc_user'):
840 840 return pyramid_request.rpc_user
841 841
842 842 return None
843 843
844 844
845 845 def action_logger_generic(action, namespace=''):
846 846 """
847 847 A generic logger for actions useful to the system overview, tries to find
848 848 an acting user for the context of the call otherwise reports unknown user
849 849
850 850 :param action: logging message eg 'comment 5 deleted'
851 851 :param type: string
852 852
853 853 :param namespace: namespace of the logging message eg. 'repo.comments'
854 854 :param type: string
855 855
856 856 """
857 857
858 858 logger_name = 'rhodecode.actions'
859 859
860 860 if namespace:
861 861 logger_name += '.' + namespace
862 862
863 863 log = logging.getLogger(logger_name)
864 864
865 865 # get a user if we can
866 866 user = get_current_rhodecode_user()
867 867
868 868 logfunc = log.info
869 869
870 870 if not user:
871 871 user = '<unknown user>'
872 872 logfunc = log.warning
873 873
874 874 logfunc('Logging action by {}: {}'.format(user, action))
875 875
876 876
877 877 def escape_split(text, sep=',', maxsplit=-1):
878 878 r"""
879 879 Allows for escaping of the separator: e.g. arg='foo\, bar'
880 880
881 881 It should be noted that the way bash et. al. do command line parsing, those
882 882 single quotes are required.
883 883 """
884 884 escaped_sep = r'\%s' % sep
885 885
886 886 if escaped_sep not in text:
887 887 return text.split(sep, maxsplit)
888 888
889 889 before, _mid, after = text.partition(escaped_sep)
890 890 startlist = before.split(sep, maxsplit) # a regular split is fine here
891 891 unfinished = startlist[-1]
892 892 startlist = startlist[:-1]
893 893
894 894 # recurse because there may be more escaped separators
895 895 endlist = escape_split(after, sep, maxsplit)
896 896
897 897 # finish building the escaped value. we use endlist[0] becaue the first
898 898 # part of the string sent in recursion is the rest of the escaped value.
899 899 unfinished += sep + endlist[0]
900 900
901 901 return startlist + [unfinished] + endlist[1:] # put together all the parts
902 902
903 903
904 904 class OptionalAttr(object):
905 905 """
906 906 Special Optional Option that defines other attribute. Example::
907 907
908 908 def test(apiuser, userid=Optional(OAttr('apiuser')):
909 909 user = Optional.extract(userid)
910 910 # calls
911 911
912 912 """
913 913
914 914 def __init__(self, attr_name):
915 915 self.attr_name = attr_name
916 916
917 917 def __repr__(self):
918 918 return '<OptionalAttr:%s>' % self.attr_name
919 919
920 920 def __call__(self):
921 921 return self
922 922
923 923
924 924 # alias
925 925 OAttr = OptionalAttr
926 926
927 927
928 928 class Optional(object):
929 929 """
930 930 Defines an optional parameter::
931 931
932 932 param = param.getval() if isinstance(param, Optional) else param
933 933 param = param() if isinstance(param, Optional) else param
934 934
935 935 is equivalent of::
936 936
937 937 param = Optional.extract(param)
938 938
939 939 """
940 940
941 941 def __init__(self, type_):
942 942 self.type_ = type_
943 943
944 944 def __repr__(self):
945 945 return '<Optional:%s>' % self.type_.__repr__()
946 946
947 947 def __call__(self):
948 948 return self.getval()
949 949
950 950 def getval(self):
951 951 """
952 952 returns value from this Optional instance
953 953 """
954 954 if isinstance(self.type_, OAttr):
955 955 # use params name
956 956 return self.type_.attr_name
957 957 return self.type_
958 958
959 959 @classmethod
960 960 def extract(cls, val):
961 961 """
962 962 Extracts value from Optional() instance
963 963
964 964 :param val:
965 965 :return: original value if it's not Optional instance else
966 966 value of instance
967 967 """
968 968 if isinstance(val, cls):
969 969 return val.getval()
970 970 return val
971 971
972 972
973 973 def glob2re(pat):
974 974 """
975 975 Translate a shell PATTERN to a regular expression.
976 976
977 977 There is no way to quote meta-characters.
978 978 """
979 979
980 980 i, n = 0, len(pat)
981 981 res = ''
982 982 while i < n:
983 983 c = pat[i]
984 984 i = i+1
985 985 if c == '*':
986 986 #res = res + '.*'
987 987 res = res + '[^/]*'
988 988 elif c == '?':
989 989 #res = res + '.'
990 990 res = res + '[^/]'
991 991 elif c == '[':
992 992 j = i
993 993 if j < n and pat[j] == '!':
994 994 j = j+1
995 995 if j < n and pat[j] == ']':
996 996 j = j+1
997 997 while j < n and pat[j] != ']':
998 998 j = j+1
999 999 if j >= n:
1000 1000 res = res + '\\['
1001 1001 else:
1002 1002 stuff = pat[i:j].replace('\\','\\\\')
1003 1003 i = j+1
1004 1004 if stuff[0] == '!':
1005 1005 stuff = '^' + stuff[1:]
1006 1006 elif stuff[0] == '^':
1007 1007 stuff = '\\' + stuff
1008 1008 res = '%s[%s]' % (res, stuff)
1009 1009 else:
1010 1010 res = res + re.escape(c)
1011 1011 return res + '\Z(?ms)'
1012
1013
1014 def parse_byte_string(size_str):
1015 match = re.match(r'(\d+)(MB|KB)', size_str, re.IGNORECASE)
1016 if not match:
1017 raise ValueError('Given size:%s is invalid, please make sure '
1018 'to use format of <num>(MB|KB)' % size_str)
1019
1020 _parts = match.groups()
1021 num, type_ = _parts
1022 return long(num) * {'mb': 1024*1024, 'kb': 1024}[type_.lower()]
@@ -1,65 +1,65 b''
1 1 # -*- coding: utf-8 -*-
2 2
3 3 # Copyright (C) 2010-2018 RhodeCode GmbH
4 4 #
5 5 # This program is free software: you can redistribute it and/or modify
6 6 # it under the terms of the GNU Affero General Public License, version 3
7 7 # (only), as published by the Free Software Foundation.
8 8 #
9 9 # This program is distributed in the hope that it will be useful,
10 10 # but WITHOUT ANY WARRANTY; without even the implied warranty of
11 11 # MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
12 12 # GNU General Public License for more details.
13 13 #
14 14 # You should have received a copy of the GNU Affero General Public License
15 15 # along with this program. If not, see <http://www.gnu.org/licenses/>.
16 16 #
17 17 # This program is dual-licensed. If you wish to learn more about the
18 18 # RhodeCode Enterprise Edition, including its added features, Support services,
19 19 # and proprietary license terms, please see https://rhodecode.com/licenses/
20 20
21 21 """
22 22 This module provides some useful tools for ``vcs`` like annotate/diff html
23 23 output. It also includes some internal helpers.
24 24 """
25 25
26 26
27 27 def author_email(author):
28 28 """
29 29 returns email address of given author.
30 30 If any of <,> sign are found, it fallbacks to regex findall()
31 31 and returns first found result or empty string
32 32
33 33 Regex taken from http://www.regular-expressions.info/email.html
34 34 """
35 35 import re
36 36 if not author:
37 37 return ''
38 38
39 39 r = author.find('>')
40 40 l = author.find('<')
41 41
42 42 if l == -1 or r == -1:
43 43 # fallback to regex match of email out of a string
44 44 email_re = re.compile(r"""[a-z0-9!#$%&'*+/=?^_`{|}~-]+(?:\.[a-z0-9!"""
45 45 r"""#$%&'*+/=?^_`{|}~-]+)*@(?:[a-z0-9](?:[a-z"""
46 46 r"""0-9-]*[a-z0-9])?\.)+[a-z0-9](?:[a-z0-9-]"""
47 47 r"""*[a-z0-9])?""", re.IGNORECASE)
48 48 m = re.findall(email_re, author)
49 49 return m[0] if m else ''
50 50
51 51 return author[l + 1:r].strip()
52 52
53 53
54 54 def author_name(author):
55 55 """
56 56 get name of author, or else username.
57 57 It'll try to find an email in the author string and just cut it off
58 58 to get the username
59 59 """
60 60
61 if not author or not '@' in author:
61 if not author or '@' not in author:
62 62 return author
63 63 else:
64 64 return author.replace(author_email(author), '').replace('<', '')\
65 65 .replace('>', '').strip()
@@ -1,43 +1,46 b''
1 1 # -*- coding: utf-8 -*-
2 2
3 3 # Copyright (C) 2016-2018 RhodeCode GmbH
4 4 #
5 5 # This program is free software: you can redistribute it and/or modify
6 6 # it under the terms of the GNU Affero General Public License, version 3
7 7 # (only), as published by the Free Software Foundation.
8 8 #
9 9 # This program is distributed in the hope that it will be useful,
10 10 # but WITHOUT ANY WARRANTY; without even the implied warranty of
11 11 # MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
12 12 # GNU General Public License for more details.
13 13 #
14 14 # You should have received a copy of the GNU Affero General Public License
15 15 # along with this program. If not, see <http://www.gnu.org/licenses/>.
16 16 #
17 17 # This program is dual-licensed. If you wish to learn more about the
18 18 # RhodeCode Enterprise Edition, including its added features, Support services,
19 19 # and proprietary license terms, please see https://rhodecode.com/licenses/
20 20
21 21
22 22 import colander
23 23
24 24
25 25 class SearchParamsSchema(colander.MappingSchema):
26 26 search_query = colander.SchemaNode(
27 27 colander.String(),
28 28 missing='')
29 29 search_type = colander.SchemaNode(
30 30 colander.String(),
31 31 missing='content',
32 32 validator=colander.OneOf(['content', 'path', 'commit', 'repository']))
33 33 search_sort = colander.SchemaNode(
34 34 colander.String(),
35 35 missing='newfirst',
36 36 validator=colander.OneOf(['oldfirst', 'newfirst']))
37 search_max_lines = colander.SchemaNode(
38 colander.Integer(),
39 missing=10)
37 40 page_limit = colander.SchemaNode(
38 41 colander.Integer(),
39 42 missing=10,
40 43 validator=colander.Range(1, 500))
41 44 requested_page = colander.SchemaNode(
42 45 colander.Integer(),
43 46 missing=1)
@@ -1,1257 +1,1259 b''
1 1 // Default styles
2 2
3 3 .diff-collapse {
4 4 margin: @padding 0;
5 5 text-align: right;
6 6 }
7 7
8 8 .diff-container {
9 9 margin-bottom: @space;
10 10
11 11 .diffblock {
12 12 margin-bottom: @space;
13 13 }
14 14
15 15 &.hidden {
16 16 display: none;
17 17 overflow: hidden;
18 18 }
19 19 }
20 20
21 21
22 22 div.diffblock .sidebyside {
23 23 background: #ffffff;
24 24 }
25 25
26 26 div.diffblock {
27 27 overflow-x: auto;
28 28 overflow-y: hidden;
29 29 clear: both;
30 30 padding: 0px;
31 31 background: @grey6;
32 32 border: @border-thickness solid @grey5;
33 33 -webkit-border-radius: @border-radius @border-radius 0px 0px;
34 34 border-radius: @border-radius @border-radius 0px 0px;
35 35
36 36
37 37 .comments-number {
38 38 float: right;
39 39 }
40 40
41 41 // BEGIN CODE-HEADER STYLES
42 42
43 43 .code-header {
44 44 background: @grey6;
45 45 padding: 10px 0 10px 0;
46 46 height: auto;
47 47 width: 100%;
48 48
49 49 .hash {
50 50 float: left;
51 51 padding: 2px 0 0 2px;
52 52 }
53 53
54 54 .date {
55 55 float: left;
56 56 text-transform: uppercase;
57 57 padding: 4px 0px 0px 2px;
58 58 }
59 59
60 60 div {
61 61 margin-left: 4px;
62 62 }
63 63
64 64 div.compare_header {
65 65 min-height: 40px;
66 66 margin: 0;
67 67 padding: 0 @padding;
68 68
69 69 .drop-menu {
70 70 float:left;
71 71 display: block;
72 72 margin:0 0 @padding 0;
73 73 }
74 74
75 75 .compare-label {
76 76 float: left;
77 77 clear: both;
78 78 display: inline-block;
79 79 min-width: 5em;
80 80 margin: 0;
81 81 padding: @button-padding @button-padding @button-padding 0;
82 82 font-weight: @text-semibold-weight;
83 83 font-family: @text-semibold;
84 84 }
85 85
86 86 .compare-buttons {
87 87 float: left;
88 88 margin: 0;
89 89 padding: 0 0 @padding;
90 90
91 91 .btn {
92 92 margin: 0 @padding 0 0;
93 93 }
94 94 }
95 95 }
96 96
97 97 }
98 98
99 99 .parents {
100 100 float: left;
101 101 width: 100px;
102 102 font-weight: 400;
103 103 vertical-align: middle;
104 104 padding: 0px 2px 0px 2px;
105 105 background-color: @grey6;
106 106
107 107 #parent_link {
108 108 margin: 00px 2px;
109 109
110 110 &.double {
111 111 margin: 0px 2px;
112 112 }
113 113
114 114 &.disabled{
115 115 margin-right: @padding;
116 116 }
117 117 }
118 118 }
119 119
120 120 .children {
121 121 float: right;
122 122 width: 100px;
123 123 font-weight: 400;
124 124 vertical-align: middle;
125 125 text-align: right;
126 126 padding: 0px 2px 0px 2px;
127 127 background-color: @grey6;
128 128
129 129 #child_link {
130 130 margin: 0px 2px;
131 131
132 132 &.double {
133 133 margin: 0px 2px;
134 134 }
135 135
136 136 &.disabled{
137 137 margin-right: @padding;
138 138 }
139 139 }
140 140 }
141 141
142 142 .changeset_header {
143 143 height: 16px;
144 144
145 145 & > div{
146 146 margin-right: @padding;
147 147 }
148 148 }
149 149
150 150 .changeset_file {
151 151 text-align: left;
152 152 float: left;
153 153 padding: 0;
154 154
155 155 a{
156 156 display: inline-block;
157 157 margin-right: 0.5em;
158 158 }
159 159
160 160 #selected_mode{
161 161 margin-left: 0;
162 162 }
163 163 }
164 164
165 165 .diff-menu-wrapper {
166 166 float: left;
167 167 }
168 168
169 169 .diff-menu {
170 170 position: absolute;
171 171 background: none repeat scroll 0 0 #FFFFFF;
172 172 border-color: #003367 @grey3 @grey3;
173 173 border-right: 1px solid @grey3;
174 174 border-style: solid solid solid;
175 175 border-width: @border-thickness;
176 176 box-shadow: 2px 8px 4px rgba(0, 0, 0, 0.2);
177 177 margin-top: 5px;
178 178 margin-left: 1px;
179 179 }
180 180
181 181 .diff-actions, .editor-actions {
182 182 float: left;
183 183
184 184 input{
185 185 margin: 0 0.5em 0 0;
186 186 }
187 187 }
188 188
189 189 // END CODE-HEADER STYLES
190 190
191 191 // BEGIN CODE-BODY STYLES
192 192
193 193 .code-body {
194 194 padding: 0;
195 195 background-color: #ffffff;
196 196 position: relative;
197 197 max-width: none;
198 198 box-sizing: border-box;
199 199 // TODO: johbo: Parent has overflow: auto, this forces the child here
200 200 // to have the intended size and to scroll. Should be simplified.
201 201 width: 100%;
202 202 overflow-x: auto;
203 203 }
204 204
205 205 pre.raw {
206 206 background: white;
207 207 color: @grey1;
208 208 }
209 209 // END CODE-BODY STYLES
210 210
211 211 }
212 212
213 213
214 214 table.code-difftable {
215 215 border-collapse: collapse;
216 216 width: 99%;
217 217 border-radius: 0px !important;
218 218
219 219 td {
220 220 padding: 0 !important;
221 221 background: none !important;
222 222 border: 0 !important;
223 223 }
224 224
225 225 .context {
226 226 background: none repeat scroll 0 0 #DDE7EF;
227 227 }
228 228
229 229 .add {
230 230 background: none repeat scroll 0 0 #DDFFDD;
231 231
232 232 ins {
233 233 background: none repeat scroll 0 0 #AAFFAA;
234 234 text-decoration: none;
235 235 }
236 236 }
237 237
238 238 .del {
239 239 background: none repeat scroll 0 0 #FFDDDD;
240 240
241 241 del {
242 242 background: none repeat scroll 0 0 #FFAAAA;
243 243 text-decoration: none;
244 244 }
245 245 }
246 246
247 247 /** LINE NUMBERS **/
248 248 .lineno {
249 249 padding-left: 2px !important;
250 250 padding-right: 2px;
251 251 text-align: right;
252 252 width: 32px;
253 253 -moz-user-select: none;
254 254 -webkit-user-select: none;
255 255 border-right: @border-thickness solid @grey5 !important;
256 256 border-left: 0px solid #CCC !important;
257 257 border-top: 0px solid #CCC !important;
258 258 border-bottom: none !important;
259 259
260 260 a {
261 261 &:extend(pre);
262 262 text-align: right;
263 263 padding-right: 2px;
264 264 cursor: pointer;
265 265 display: block;
266 266 width: 32px;
267 267 }
268 268 }
269 269
270 270 .context {
271 271 cursor: auto;
272 272 &:extend(pre);
273 273 }
274 274
275 275 .lineno-inline {
276 276 background: none repeat scroll 0 0 #FFF !important;
277 277 padding-left: 2px;
278 278 padding-right: 2px;
279 279 text-align: right;
280 280 width: 30px;
281 281 -moz-user-select: none;
282 282 -webkit-user-select: none;
283 283 }
284 284
285 285 /** CODE **/
286 286 .code {
287 287 display: block;
288 288 width: 100%;
289 289
290 290 td {
291 291 margin: 0;
292 292 padding: 0;
293 293 }
294 294
295 295 pre {
296 296 margin: 0;
297 297 padding: 0;
298 298 margin-left: .5em;
299 299 }
300 300 }
301 301 }
302 302
303 303
304 304 // Comments
305 305
306 306 div.comment:target {
307 307 border-left: 6px solid @comment-highlight-color !important;
308 308 padding-left: 3px;
309 309 margin-left: -9px;
310 310 }
311 311
312 312 //TODO: anderson: can't get an absolute number out of anything, so had to put the
313 313 //current values that might change. But to make it clear I put as a calculation
314 314 @comment-max-width: 1065px;
315 315 @pr-extra-margin: 34px;
316 316 @pr-border-spacing: 4px;
317 317 @pr-comment-width: @comment-max-width - @pr-extra-margin - @pr-border-spacing;
318 318
319 319 // Pull Request
320 320 .cs_files .code-difftable {
321 321 border: @border-thickness solid @grey5; //borders only on PRs
322 322
323 323 .comment-inline-form,
324 324 div.comment {
325 325 width: @pr-comment-width;
326 326 }
327 327 }
328 328
329 329 // Changeset
330 330 .code-difftable {
331 331 .comment-inline-form,
332 332 div.comment {
333 333 width: @comment-max-width;
334 334 }
335 335 }
336 336
337 337 //Style page
338 338 @style-extra-margin: @sidebar-width + (@sidebarpadding * 3) + @padding;
339 339 #style-page .code-difftable{
340 340 .comment-inline-form,
341 341 div.comment {
342 342 width: @comment-max-width - @style-extra-margin;
343 343 }
344 344 }
345 345
346 346 #context-bar > h2 {
347 347 font-size: 20px;
348 348 }
349 349
350 350 #context-bar > h2> a {
351 351 font-size: 20px;
352 352 }
353 353 // end of defaults
354 354
355 355 .file_diff_buttons {
356 356 padding: 0 0 @padding;
357 357
358 358 .drop-menu {
359 359 float: left;
360 360 margin: 0 @padding 0 0;
361 361 }
362 362 .btn {
363 363 margin: 0 @padding 0 0;
364 364 }
365 365 }
366 366
367 367 .code-body.textarea.editor {
368 368 max-width: none;
369 369 padding: 15px;
370 370 }
371 371
372 372 td.injected_diff{
373 373 max-width: 1178px;
374 374 overflow-x: auto;
375 375 overflow-y: hidden;
376 376
377 377 div.diff-container,
378 378 div.diffblock{
379 379 max-width: 100%;
380 380 }
381 381
382 382 div.code-body {
383 383 max-width: 1124px;
384 384 overflow-x: auto;
385 385 overflow-y: hidden;
386 386 padding: 0;
387 387 }
388 388 div.diffblock {
389 389 border: none;
390 390 }
391 391
392 392 &.inline-form {
393 393 width: 99%
394 394 }
395 395 }
396 396
397 397
398 398 table.code-difftable {
399 399 width: 100%;
400 400 }
401 401
402 402 /** PYGMENTS COLORING **/
403 403 div.codeblock {
404 404
405 405 // TODO: johbo: Added interim to get rid of the margin around
406 406 // Select2 widgets. This needs further cleanup.
407 407 margin-top: @padding;
408 408
409 409 overflow: auto;
410 410 padding: 0px;
411 411 border: @border-thickness solid @grey5;
412 412 background: @grey6;
413 413 .border-radius(@border-radius);
414 414
415 415 #remove_gist {
416 416 float: right;
417 417 }
418 418
419 419 .gist_url {
420 420 padding: 0px 0px 10px 0px;
421 421 }
422 422
423 423 .author {
424 424 clear: both;
425 425 vertical-align: middle;
426 426 font-weight: @text-bold-weight;
427 427 font-family: @text-bold;
428 428 }
429 429
430 430 .btn-mini {
431 431 float: left;
432 432 margin: 0 5px 0 0;
433 433 }
434 434
435 435 .code-header {
436 436 padding: @padding;
437 437 border-bottom: @border-thickness solid @grey5;
438 438
439 439 .rc-user {
440 440 min-width: 0;
441 441 margin-right: .5em;
442 442 }
443 443
444 444 .stats {
445 445 clear: both;
446 446 margin: 0 0 @padding 0;
447 447 padding: 0;
448 448 .left {
449 449 float: left;
450 450 clear: left;
451 451 max-width: 75%;
452 452 margin: 0 0 @padding 0;
453 453
454 454 &.item {
455 455 margin-right: @padding;
456 456 &.last { border-right: none; }
457 457 }
458 458 }
459 459 .buttons { float: right; }
460 460 .author {
461 461 height: 25px; margin-left: 15px; font-weight: bold;
462 462 }
463 463 }
464 464
465 465 .commit {
466 466 margin: 5px 0 0 26px;
467 467 font-weight: normal;
468 468 white-space: pre-wrap;
469 469 }
470 470 }
471 471
472 472 .message {
473 473 position: relative;
474 474 margin: @padding;
475 475
476 476 .codeblock-label {
477 477 margin: 0 0 1em 0;
478 478 }
479 479 }
480 480
481 481 .code-body {
482 482 padding: @padding;
483 483 background-color: #ffffff;
484 484 min-width: 100%;
485 485 box-sizing: border-box;
486 486 // TODO: johbo: Parent has overflow: auto, this forces the child here
487 487 // to have the intended size and to scroll. Should be simplified.
488 488 width: 100%;
489 489 overflow-x: auto;
490 490
491 491 img.rendered-binary {
492 492 height: auto;
493 493 width: 100%;
494 494 }
495 495 }
496 496 }
497 497
498 498 .code-highlighttable,
499 499 div.codeblock {
500 500
501 501 &.readme {
502 502 background-color: white;
503 503 }
504 504
505 505 .markdown-block table {
506 506 border-collapse: collapse;
507 507
508 508 th,
509 509 td {
510 510 padding: .5em;
511 511 border: @border-thickness solid @border-default-color;
512 512 }
513 513 }
514 514
515 515 table {
516 516 border: 0px;
517 517 margin: 0;
518 518 letter-spacing: normal;
519 519
520 520
521 521 td {
522 522 border: 0px;
523 523 vertical-align: top;
524 524 }
525 525 }
526 526 }
527 527
528 528 div.codeblock .code-header .search-path { padding: 0 0 0 10px; }
529 529 div.search-code-body {
530 530 background-color: #ffffff; padding: 5px 0 5px 10px;
531 531 pre {
532 532 .match { background-color: #faffa6;}
533 533 .break { display: block; width: 100%; background-color: #DDE7EF; color: #747474; }
534 534 }
535 535 .code-highlighttable {
536 536 border-collapse: collapse;
537 537
538 538 tr:hover {
539 539 background: #fafafa;
540 540 }
541 541 td.code {
542 542 padding-left: 10px;
543 543 }
544 544 td.line {
545 545 border-right: 1px solid #ccc !important;
546 546 padding-right: 10px;
547 547 text-align: right;
548 548 font-family: @text-monospace;
549 549 span {
550 550 white-space: pre-wrap;
551 551 color: #666666;
552 552 }
553 553 }
554 554 }
555 555 }
556 556
557 557 div.annotatediv { margin-left: 2px; margin-right: 4px; }
558 558 .code-highlight {
559 559 margin: 0; padding: 0; border-left: @border-thickness solid @grey5;
560 560 pre, .linenodiv pre { padding: 0 5px; margin: 0; }
561 561 pre div:target {background-color: @comment-highlight-color !important;}
562 562 }
563 563
564 564 .linenos a { text-decoration: none; }
565 565
566 566 .CodeMirror-selected { background: @rchighlightblue; }
567 567 .CodeMirror-focused .CodeMirror-selected { background: @rchighlightblue; }
568 568 .CodeMirror ::selection { background: @rchighlightblue; }
569 569 .CodeMirror ::-moz-selection { background: @rchighlightblue; }
570 570
571 571 .code { display: block; border:0px !important; }
572 572 .code-highlight, /* TODO: dan: merge codehilite into code-highlight */
573 573 /* This can be generated with `pygmentize -S default -f html` */
574 574 .codehilite {
575 .c-ElasticMatch { background-color: #faffa6; padding: 0.2em;}
575 576 .hll { background-color: #ffffcc }
576 577 .c { color: #408080; font-style: italic } /* Comment */
577 578 .err, .codehilite .err { border: none } /* Error */
578 579 .k { color: #008000; font-weight: bold } /* Keyword */
579 580 .o { color: #666666 } /* Operator */
580 581 .ch { color: #408080; font-style: italic } /* Comment.Hashbang */
581 582 .cm { color: #408080; font-style: italic } /* Comment.Multiline */
582 583 .cp { color: #BC7A00 } /* Comment.Preproc */
583 584 .cpf { color: #408080; font-style: italic } /* Comment.PreprocFile */
584 585 .c1 { color: #408080; font-style: italic } /* Comment.Single */
585 586 .cs { color: #408080; font-style: italic } /* Comment.Special */
586 587 .gd { color: #A00000 } /* Generic.Deleted */
587 588 .ge { font-style: italic } /* Generic.Emph */
588 589 .gr { color: #FF0000 } /* Generic.Error */
589 590 .gh { color: #000080; font-weight: bold } /* Generic.Heading */
590 591 .gi { color: #00A000 } /* Generic.Inserted */
591 592 .go { color: #888888 } /* Generic.Output */
592 593 .gp { color: #000080; font-weight: bold } /* Generic.Prompt */
593 594 .gs { font-weight: bold } /* Generic.Strong */
594 595 .gu { color: #800080; font-weight: bold } /* Generic.Subheading */
595 596 .gt { color: #0044DD } /* Generic.Traceback */
596 597 .kc { color: #008000; font-weight: bold } /* Keyword.Constant */
597 598 .kd { color: #008000; font-weight: bold } /* Keyword.Declaration */
598 599 .kn { color: #008000; font-weight: bold } /* Keyword.Namespace */
599 600 .kp { color: #008000 } /* Keyword.Pseudo */
600 601 .kr { color: #008000; font-weight: bold } /* Keyword.Reserved */
601 602 .kt { color: #B00040 } /* Keyword.Type */
602 603 .m { color: #666666 } /* Literal.Number */
603 604 .s { color: #BA2121 } /* Literal.String */
604 605 .na { color: #7D9029 } /* Name.Attribute */
605 606 .nb { color: #008000 } /* Name.Builtin */
606 607 .nc { color: #0000FF; font-weight: bold } /* Name.Class */
607 608 .no { color: #880000 } /* Name.Constant */
608 609 .nd { color: #AA22FF } /* Name.Decorator */
609 610 .ni { color: #999999; font-weight: bold } /* Name.Entity */
610 611 .ne { color: #D2413A; font-weight: bold } /* Name.Exception */
611 612 .nf { color: #0000FF } /* Name.Function */
612 613 .nl { color: #A0A000 } /* Name.Label */
613 614 .nn { color: #0000FF; font-weight: bold } /* Name.Namespace */
614 615 .nt { color: #008000; font-weight: bold } /* Name.Tag */
615 616 .nv { color: #19177C } /* Name.Variable */
616 617 .ow { color: #AA22FF; font-weight: bold } /* Operator.Word */
617 618 .w { color: #bbbbbb } /* Text.Whitespace */
618 619 .mb { color: #666666 } /* Literal.Number.Bin */
619 620 .mf { color: #666666 } /* Literal.Number.Float */
620 621 .mh { color: #666666 } /* Literal.Number.Hex */
621 622 .mi { color: #666666 } /* Literal.Number.Integer */
622 623 .mo { color: #666666 } /* Literal.Number.Oct */
623 624 .sa { color: #BA2121 } /* Literal.String.Affix */
624 625 .sb { color: #BA2121 } /* Literal.String.Backtick */
625 626 .sc { color: #BA2121 } /* Literal.String.Char */
626 627 .dl { color: #BA2121 } /* Literal.String.Delimiter */
627 628 .sd { color: #BA2121; font-style: italic } /* Literal.String.Doc */
628 629 .s2 { color: #BA2121 } /* Literal.String.Double */
629 630 .se { color: #BB6622; font-weight: bold } /* Literal.String.Escape */
630 631 .sh { color: #BA2121 } /* Literal.String.Heredoc */
631 632 .si { color: #BB6688; font-weight: bold } /* Literal.String.Interpol */
632 633 .sx { color: #008000 } /* Literal.String.Other */
633 634 .sr { color: #BB6688 } /* Literal.String.Regex */
634 635 .s1 { color: #BA2121 } /* Literal.String.Single */
635 636 .ss { color: #19177C } /* Literal.String.Symbol */
636 637 .bp { color: #008000 } /* Name.Builtin.Pseudo */
637 638 .fm { color: #0000FF } /* Name.Function.Magic */
638 639 .vc { color: #19177C } /* Name.Variable.Class */
639 640 .vg { color: #19177C } /* Name.Variable.Global */
640 641 .vi { color: #19177C } /* Name.Variable.Instance */
641 642 .vm { color: #19177C } /* Name.Variable.Magic */
642 643 .il { color: #666666 } /* Literal.Number.Integer.Long */
644
643 645 }
644 646
645 647 /* customized pre blocks for markdown/rst */
646 648 pre.literal-block, .codehilite pre{
647 649 padding: @padding;
648 650 border: 1px solid @grey6;
649 651 .border-radius(@border-radius);
650 652 background-color: @grey7;
651 653 }
652 654
653 655
654 656 /* START NEW CODE BLOCK CSS */
655 657
656 658 @cb-line-height: 18px;
657 659 @cb-line-code-padding: 10px;
658 660 @cb-text-padding: 5px;
659 661
660 662 @pill-padding: 2px 7px;
661 663 @pill-padding-small: 2px 2px 1px 2px;
662 664
663 665 input.filediff-collapse-state {
664 666 display: none;
665 667
666 668 &:checked + .filediff { /* file diff is collapsed */
667 669 .cb {
668 670 display: none
669 671 }
670 672 .filediff-collapse-indicator {
671 673 width: 0;
672 674 height: 0;
673 675 border-style: solid;
674 676 border-width: 4.5px 0 4.5px 9.3px;
675 677 border-color: transparent transparent transparent #aaa;
676 678 margin: 6px 0px;
677 679 }
678 680 .filediff-menu {
679 681 display: none;
680 682 }
681 683
682 684 }
683 685
684 686 &+ .filediff { /* file diff is expanded */
685 687 .filediff-collapse-indicator {
686 688 width: 0;
687 689 height: 0;
688 690 border-style: solid;
689 691 border-width: 9.3px 4.5px 0 4.5px;
690 692 border-color: #aaa transparent transparent transparent;
691 693 margin: 6px 0px;
692 694
693 695 }
694 696 .filediff-menu {
695 697 display: block;
696 698 }
697 699 margin: 10px 0;
698 700 &:nth-child(2) {
699 701 margin: 0;
700 702 }
701 703 }
702 704 }
703 705
704 706 .filediffs .anchor {
705 707 display: block;
706 708 height: 40px;
707 709 margin-top: -40px;
708 710 visibility: hidden;
709 711 }
710 712
711 713 .filediffs .anchor:nth-of-type(1) {
712 714 display: block;
713 715 height: 80px;
714 716 margin-top: -80px;
715 717 visibility: hidden;
716 718 }
717 719
718 720 .cs_files {
719 721 clear: both;
720 722 }
721 723
722 724 #diff-file-sticky{
723 725 will-change: min-height;
724 726 }
725 727
726 728 .sidebar__inner{
727 729 transform: translate(0, 0); /* For browsers don't support translate3d. */
728 730 transform: translate3d(0, 0, 0);
729 731 will-change: position, transform;
730 732 height: 70px;
731 733 z-index: 30;
732 734 background-color: #fff;
733 735 padding: 5px 0px;
734 736 }
735 737
736 738 .sidebar__bar {
737 739 padding: 5px 0px 0px 0px
738 740 }
739 741
740 742 .fpath-placeholder {
741 743 clear: both;
742 744 visibility: hidden
743 745 }
744 746
745 747 .is-affixed {
746 748 .sidebar_inner_shadow {
747 749 position: fixed;
748 750 top: 75px;
749 751 right: -100%;
750 752 left: -100%;
751 753 z-index: 28;
752 754 display: block;
753 755 height: 5px;
754 756 content: "";
755 757 background: linear-gradient(rgba(0, 0, 0, 0.075), rgba(0, 0, 0, 0.001)) repeat-x 0 0;
756 758 border-top: 1px solid rgba(0, 0, 0, 0.15);
757 759 }
758 760 .fpath-placeholder {
759 761 visibility: visible !important;
760 762 }
761 763 }
762 764
763 765 .diffset-menu {
764 766 margin-bottom: 20px;
765 767 }
766 768 .diffset {
767 769 margin: 20px auto;
768 770 .diffset-heading {
769 771 border: 1px solid @grey5;
770 772 margin-bottom: -1px;
771 773 // margin-top: 20px;
772 774 h2 {
773 775 margin: 0;
774 776 line-height: 38px;
775 777 padding-left: 10px;
776 778 }
777 779 .btn {
778 780 margin: 0;
779 781 }
780 782 background: @grey6;
781 783 display: block;
782 784 padding: 5px;
783 785 }
784 786 .diffset-heading-warning {
785 787 background: @alert3-inner;
786 788 border: 1px solid @alert3;
787 789 }
788 790 &.diffset-comments-disabled {
789 791 .cb-comment-box-opener, .comment-inline-form, .cb-comment-add-button {
790 792 display: none !important;
791 793 }
792 794 }
793 795 }
794 796
795 797 .filelist {
796 798 .pill {
797 799 display: block;
798 800 float: left;
799 801 padding: @pill-padding-small;
800 802 }
801 803 }
802 804
803 805 .pill {
804 806 display: block;
805 807 float: left;
806 808 padding: @pill-padding;
807 809 }
808 810
809 811 .pill-group {
810 812 .pill {
811 813 opacity: .8;
812 814 margin-right: 3px;
813 815 font-size: 12px;
814 816 font-weight: normal;
815 817
816 818 &:first-child {
817 819 border-radius: @border-radius 0 0 @border-radius;
818 820 }
819 821 &:last-child {
820 822 border-radius: 0 @border-radius @border-radius 0;
821 823 }
822 824 &:only-child {
823 825 border-radius: @border-radius;
824 826 margin-right: 0;
825 827 }
826 828 }
827 829 }
828 830
829 831 /* Main comments*/
830 832 #comments {
831 833 .comment-selected {
832 834 border-left: 6px solid @comment-highlight-color;
833 835 padding-left: 3px;
834 836 margin-left: -9px;
835 837 }
836 838 }
837 839
838 840 .filediff {
839 841 border: 1px solid @grey5;
840 842
841 843 /* START OVERRIDES */
842 844 .code-highlight {
843 845 border: none; // TODO: remove this border from the global
844 846 // .code-highlight, it doesn't belong there
845 847 }
846 848 label {
847 849 margin: 0; // TODO: remove this margin definition from global label
848 850 // it doesn't belong there - if margin on labels
849 851 // are needed for a form they should be defined
850 852 // in the form's class
851 853 }
852 854 /* END OVERRIDES */
853 855
854 856 * {
855 857 box-sizing: border-box;
856 858 }
857 859 .filediff-anchor {
858 860 visibility: hidden;
859 861 }
860 862 &:hover {
861 863 .filediff-anchor {
862 864 visibility: visible;
863 865 }
864 866 }
865 867
866 868 .filediff-collapse-indicator {
867 869 border-style: solid;
868 870 float: left;
869 871 margin: 4px 0px 0 0;
870 872 cursor: pointer;
871 873 }
872 874
873 875 .filediff-heading {
874 876 background: @grey7;
875 877 cursor: pointer;
876 878 display: block;
877 879 padding: 5px 10px;
878 880 }
879 881 .filediff-heading:after {
880 882 content: "";
881 883 display: table;
882 884 clear: both;
883 885 }
884 886 .filediff-heading:hover {
885 887 background: #e1e9f4 !important;
886 888 }
887 889
888 890 .filediff-menu {
889 891 float: right;
890 892 text-align: right;
891 893 padding: 5px 5px 5px 0px;
892 894
893 895 &> a,
894 896 &> span {
895 897 padding: 1px;
896 898 }
897 899 }
898 900
899 901 .filediff-collapse-button, .filediff-expand-button {
900 902 cursor: pointer;
901 903 }
902 904 .filediff-collapse-button {
903 905 display: inline;
904 906 }
905 907 .filediff-expand-button {
906 908 display: none;
907 909 }
908 910 .filediff-collapsed .filediff-collapse-button {
909 911 display: none;
910 912 }
911 913 .filediff-collapsed .filediff-expand-button {
912 914 display: inline;
913 915 }
914 916
915 917 /**** COMMENTS ****/
916 918
917 919 .filediff-menu {
918 920 .show-comment-button {
919 921 display: none;
920 922 }
921 923 }
922 924 &.hide-comments {
923 925 .inline-comments {
924 926 display: none;
925 927 }
926 928 .filediff-menu {
927 929 .show-comment-button {
928 930 display: inline;
929 931 }
930 932 .hide-comment-button {
931 933 display: none;
932 934 }
933 935 }
934 936 }
935 937
936 938 .hide-line-comments {
937 939 .inline-comments {
938 940 display: none;
939 941 }
940 942 }
941 943
942 944 /**** END COMMENTS ****/
943 945
944 946 }
945 947
946 948
947 949
948 950 .filediff, .filelist {
949 951 .pill {
950 952 &[op="name"] {
951 953 background: none;
952 954 opacity: 1;
953 955 color: white;
954 956 }
955 957 &[op="limited"] {
956 958 background: @grey2;
957 959 color: white;
958 960 }
959 961 &[op="binary"] {
960 962 background: @color7;
961 963 color: white;
962 964 }
963 965 &[op="modified"] {
964 966 background: @alert1;
965 967 color: white;
966 968 }
967 969 &[op="renamed"] {
968 970 background: @color4;
969 971 color: white;
970 972 }
971 973 &[op="copied"] {
972 974 background: @color4;
973 975 color: white;
974 976 }
975 977 &[op="mode"] {
976 978 background: @grey3;
977 979 color: white;
978 980 }
979 981 &[op="symlink"] {
980 982 background: @color8;
981 983 color: white;
982 984 }
983 985
984 986 &[op="added"] { /* added lines */
985 987 background: @alert1;
986 988 color: white;
987 989 }
988 990 &[op="deleted"] { /* deleted lines */
989 991 background: @alert2;
990 992 color: white;
991 993 }
992 994
993 995 &[op="created"] { /* created file */
994 996 background: @alert1;
995 997 color: white;
996 998 }
997 999 &[op="removed"] { /* deleted file */
998 1000 background: @color5;
999 1001 color: white;
1000 1002 }
1001 1003 }
1002 1004 }
1003 1005
1004 1006
1005 1007 .filediff-outdated {
1006 1008 padding: 8px 0;
1007 1009
1008 1010 .filediff-heading {
1009 1011 opacity: .5;
1010 1012 }
1011 1013 }
1012 1014
1013 1015 table.cb {
1014 1016 width: 100%;
1015 1017 border-collapse: collapse;
1016 1018
1017 1019 .cb-text {
1018 1020 padding: @cb-text-padding;
1019 1021 }
1020 1022 .cb-hunk {
1021 1023 padding: @cb-text-padding;
1022 1024 }
1023 1025 .cb-expand {
1024 1026 display: none;
1025 1027 }
1026 1028 .cb-collapse {
1027 1029 display: inline;
1028 1030 }
1029 1031 &.cb-collapsed {
1030 1032 .cb-line {
1031 1033 display: none;
1032 1034 }
1033 1035 .cb-expand {
1034 1036 display: inline;
1035 1037 }
1036 1038 .cb-collapse {
1037 1039 display: none;
1038 1040 }
1039 1041 .cb-hunk {
1040 1042 display: none;
1041 1043 }
1042 1044 }
1043 1045
1044 1046 /* intentionally general selector since .cb-line-selected must override it
1045 1047 and they both use !important since the td itself may have a random color
1046 1048 generated by annotation blocks. TLDR: if you change it, make sure
1047 1049 annotated block selection and line selection in file view still work */
1048 1050 .cb-line-fresh .cb-content {
1049 1051 background: white !important;
1050 1052 }
1051 1053 .cb-warning {
1052 1054 background: #fff4dd;
1053 1055 }
1054 1056
1055 1057 &.cb-diff-sideside {
1056 1058 td {
1057 1059 &.cb-content {
1058 1060 width: 50%;
1059 1061 }
1060 1062 }
1061 1063 }
1062 1064
1063 1065 tr {
1064 1066 &.cb-annotate {
1065 1067 border-top: 1px solid #eee;
1066 1068 }
1067 1069
1068 1070 &.cb-comment-info {
1069 1071 border-top: 1px solid #eee;
1070 1072 color: rgba(0, 0, 0, 0.3);
1071 1073 background: #edf2f9;
1072 1074
1073 1075 td {
1074 1076
1075 1077 }
1076 1078 }
1077 1079
1078 1080 &.cb-hunk {
1079 1081 font-family: @text-monospace;
1080 1082 color: rgba(0, 0, 0, 0.3);
1081 1083
1082 1084 td {
1083 1085 &:first-child {
1084 1086 background: #edf2f9;
1085 1087 }
1086 1088 &:last-child {
1087 1089 background: #f4f7fb;
1088 1090 }
1089 1091 }
1090 1092 }
1091 1093 }
1092 1094
1093 1095
1094 1096 td {
1095 1097 vertical-align: top;
1096 1098 padding: 0;
1097 1099
1098 1100 &.cb-content {
1099 1101 font-size: 12.35px;
1100 1102
1101 1103 &.cb-line-selected .cb-code {
1102 1104 background: @comment-highlight-color !important;
1103 1105 }
1104 1106
1105 1107 span.cb-code {
1106 1108 line-height: @cb-line-height;
1107 1109 padding-left: @cb-line-code-padding;
1108 1110 padding-right: @cb-line-code-padding;
1109 1111 display: block;
1110 1112 white-space: pre-wrap;
1111 1113 font-family: @text-monospace;
1112 1114 word-break: break-all;
1113 1115 .nonl {
1114 1116 color: @color5;
1115 1117 }
1116 1118 .cb-action {
1117 1119 &:before {
1118 1120 content: " ";
1119 1121 }
1120 1122 &.cb-deletion:before {
1121 1123 content: "- ";
1122 1124 }
1123 1125 &.cb-addition:before {
1124 1126 content: "+ ";
1125 1127 }
1126 1128 }
1127 1129 }
1128 1130
1129 1131 &> button.cb-comment-box-opener {
1130 1132
1131 1133 padding: 2px 2px 1px 3px;
1132 1134 margin-left: -6px;
1133 1135 margin-top: -1px;
1134 1136
1135 1137 border-radius: @border-radius;
1136 1138 position: absolute;
1137 1139 display: none;
1138 1140 }
1139 1141 .cb-comment {
1140 1142 margin-top: 10px;
1141 1143 white-space: normal;
1142 1144 }
1143 1145 }
1144 1146 &:hover {
1145 1147 button.cb-comment-box-opener {
1146 1148 display: block;
1147 1149 }
1148 1150 &+ td button.cb-comment-box-opener {
1149 1151 display: block
1150 1152 }
1151 1153 }
1152 1154
1153 1155 &.cb-data {
1154 1156 text-align: right;
1155 1157 width: 30px;
1156 1158 font-family: @text-monospace;
1157 1159
1158 1160 .icon-comment {
1159 1161 cursor: pointer;
1160 1162 }
1161 1163 &.cb-line-selected {
1162 1164 background: @comment-highlight-color !important;
1163 1165 }
1164 1166 &.cb-line-selected > div {
1165 1167 display: block;
1166 1168 background: @comment-highlight-color !important;
1167 1169 line-height: @cb-line-height;
1168 1170 color: rgba(0, 0, 0, 0.3);
1169 1171 }
1170 1172 }
1171 1173
1172 1174 &.cb-lineno {
1173 1175 padding: 0;
1174 1176 width: 50px;
1175 1177 color: rgba(0, 0, 0, 0.3);
1176 1178 text-align: right;
1177 1179 border-right: 1px solid #eee;
1178 1180 font-family: @text-monospace;
1179 1181 -webkit-user-select: none;
1180 1182 -moz-user-select: none;
1181 1183 user-select: none;
1182 1184
1183 1185 a::before {
1184 1186 content: attr(data-line-no);
1185 1187 }
1186 1188 &.cb-line-selected {
1187 1189 background: @comment-highlight-color !important;
1188 1190 }
1189 1191
1190 1192 a {
1191 1193 display: block;
1192 1194 padding-right: @cb-line-code-padding;
1193 1195 padding-left: @cb-line-code-padding;
1194 1196 line-height: @cb-line-height;
1195 1197 color: rgba(0, 0, 0, 0.3);
1196 1198 }
1197 1199 }
1198 1200
1199 1201 &.cb-empty {
1200 1202 background: @grey7;
1201 1203 }
1202 1204
1203 1205 ins {
1204 1206 color: black;
1205 1207 background: #a6f3a6;
1206 1208 text-decoration: none;
1207 1209 }
1208 1210 del {
1209 1211 color: black;
1210 1212 background: #f8cbcb;
1211 1213 text-decoration: none;
1212 1214 }
1213 1215 &.cb-addition {
1214 1216 background: #ecffec;
1215 1217
1216 1218 &.blob-lineno {
1217 1219 background: #ddffdd;
1218 1220 }
1219 1221 }
1220 1222 &.cb-deletion {
1221 1223 background: #ffecec;
1222 1224
1223 1225 &.blob-lineno {
1224 1226 background: #ffdddd;
1225 1227 }
1226 1228 }
1227 1229 &.cb-annotate-message-spacer {
1228 1230 width:8px;
1229 1231 padding: 1px 0px 0px 3px;
1230 1232 }
1231 1233 &.cb-annotate-info {
1232 1234 width: 320px;
1233 1235 min-width: 320px;
1234 1236 max-width: 320px;
1235 1237 padding: 5px 2px;
1236 1238 font-size: 13px;
1237 1239
1238 1240 .cb-annotate-message {
1239 1241 padding: 2px 0px 0px 0px;
1240 1242 white-space: pre-line;
1241 1243 overflow: hidden;
1242 1244 }
1243 1245 .rc-user {
1244 1246 float: none;
1245 1247 padding: 0 6px 0 17px;
1246 1248 min-width: unset;
1247 1249 min-height: unset;
1248 1250 }
1249 1251 }
1250 1252
1251 1253 &.cb-annotate-revision {
1252 1254 cursor: pointer;
1253 1255 text-align: right;
1254 1256 padding: 1px 3px 0px 3px;
1255 1257 }
1256 1258 }
1257 1259 }
@@ -1,545 +1,544 b''
1 1 //
2 2 // Typography
3 3 // modified from Bootstrap
4 4 // --------------------------------------------------
5 5
6 6 // Base
7 7 body {
8 8 font-size: @basefontsize;
9 9 font-family: @text-light;
10 10 letter-spacing: .02em;
11 11 color: @grey2;
12 12 }
13 13
14 14 #content, label{
15 15 font-size: @basefontsize;
16 16 }
17 17
18 18 label {
19 19 color: @grey2;
20 20 }
21 21
22 22 ::selection { background: @rchighlightblue; }
23 23
24 24 // Headings
25 25 // -------------------------
26 26
27 27 h1, h2, h3, h4, h5, h6,
28 28 .h1, .h2, .h3, .h4, .h5, .h6 {
29 29 margin: 0 0 @textmargin 0;
30 30 padding: 0;
31 31 line-height: 1.8em;
32 32 color: @text-color;
33 33 a {
34 34 color: @rcblue;
35 35 }
36 36 }
37 37
38 38 h1, .h1 { font-size: 1.54em; font-weight: @text-bold-weight; font-family: @text-bold; }
39 39 h2, .h2 { font-size: 1.23em; font-weight: @text-semibold-weight; font-family: @text-semibold; }
40 40 h3, .h3 { font-size: 1.23em; font-family: @text-regular; }
41 41 h4, .h4 { font-size: 1em; font-weight: @text-bold-weight; font-family: @text-bold; }
42 42 h5, .h5 { font-size: 1em; font-weight: @text-bold-weight; font-family: @text-bold; }
43 43 h6, .h6 { font-size: 1em; font-weight: @text-bold-weight; font-family: @text-bold; }
44 44
45 45 // Breadcrumbs
46 46 .breadcrumbs {
47 47 font-size: @repo-title-fontsize;
48 48 margin: 0;
49 49 }
50 50
51 51 .breadcrumbs_light {
52 52 float:left;
53 53 font-size: 1.3em;
54 54 line-height: 38px;
55 55 }
56 56
57 57 // Body text
58 58 // -------------------------
59 59
60 60 p {
61 61 margin: 0 0 @textmargin 0;
62 62 padding: 0;
63 63 line-height: 2em;
64 64 }
65 65
66 66 .lead {
67 67 margin-bottom: @textmargin;
68 68 font-weight: 300;
69 69 line-height: 1.4;
70 70
71 71 @media (min-width: @screen-sm-min) {
72 72 font-size: (@basefontsize * 1.5);
73 73 }
74 74 }
75 75
76 76 a,
77 77 .link {
78 78 color: @rcblue;
79 79 text-decoration: none;
80 80 outline: none;
81 81 cursor: pointer;
82 82
83 83 &:focus {
84 84 outline: none;
85 85 }
86 86
87 87 &:hover {
88 88 color: @rcdarkblue;
89 89 }
90 90 }
91 91
92 92 img {
93 93 border: none;
94 94 outline: none;
95 95 }
96 96
97 97 strong {
98 98 font-weight: @text-bold-weight;
99 99 font-family: @text-bold;
100 100 }
101 101
102 102 em {
103 103 font-family: @text-italic;
104 104 font-style: italic;
105 105 }
106 106
107 107 strong em,
108 108 em strong {
109 109 font-style: italic;
110 110 font-weight: @text-bold-italic-weight;
111 111 font-family: @text-bold-italic;
112 112 }
113 113
114 114 //TODO: lisa: b and i are depreciated, but we are still using them in places.
115 115 // Should probably make some decision whether to keep or lose these.
116 116 b {
117 117
118 118 }
119 119
120 120 i {
121 121 font-style: normal;
122 122 }
123 123
124 124 label {
125 125 color: @text-color;
126 126
127 127 input[type="checkbox"] {
128 128 margin-right: 1em;
129 129 }
130 130 input[type="radio"] {
131 131 margin-right: 1em;
132 132 }
133 133 }
134 134
135 135 code,
136 136 .code {
137 137 font-size: .95em;
138 138 font-family: @text-code;
139 139 color: @grey3;
140 140
141 141 a {
142 142 color: lighten(@rcblue,10%)
143 143 }
144 144 }
145 145
146 146 pre {
147 147 margin: 0;
148 148 padding: 0;
149 149 border: 0;
150 150 outline: 0;
151 151 font-size: @basefontsize*.95;
152 152 line-height: 1.4em;
153 153 font-family: @text-code;
154 154 color: @grey3;
155 155 }
156 156
157 157 // Emphasis & misc
158 158 // -------------------------
159 159
160 160 small,
161 161 .small {
162 162 font-size: 75%;
163 163 font-weight: normal;
164 164 line-height: 1em;
165 165 }
166 166
167 167 mark,
168 168 .mark {
169 background-color: @rclightblue;
170 169 padding: .2em;
171 170 }
172 171
173 172 // Alignment
174 173 .text-left { text-align: left; }
175 174 .text-right { text-align: right; }
176 175 .text-center { text-align: center; }
177 176 .text-justify { text-align: justify; }
178 177 .text-nowrap { white-space: nowrap; }
179 178
180 179 // Transformation
181 180 .text-lowercase { text-transform: lowercase; }
182 181 .text-uppercase { text-transform: uppercase; }
183 182 .text-capitalize { text-transform: capitalize; }
184 183
185 184 // Contextual colors
186 185 .text-muted {
187 186 color: @grey4;
188 187 }
189 188 .text-primary {
190 189 color: @rcblue;
191 190 }
192 191 .text-success {
193 192 color: @alert1;
194 193 }
195 194 .text-info {
196 195 color: @alert4;
197 196 }
198 197 .text-warning {
199 198 color: @alert3;
200 199 }
201 200 .text-danger {
202 201 color: @alert2;
203 202 }
204 203
205 204 // Contextual backgrounds
206 205 .bg-primary {
207 206 background-color: white;
208 207 }
209 208 .bg-success {
210 209 background-color: @alert1;
211 210 }
212 211 .bg-info {
213 212 background-color: @alert4;
214 213 }
215 214 .bg-warning {
216 215 background-color: @alert3;
217 216 }
218 217 .bg-danger {
219 218 background-color: @alert2;
220 219 }
221 220
222 221
223 222 // Page header
224 223 // -------------------------
225 224
226 225 .page-header {
227 226 margin: @pagepadding 0 @textmargin;
228 227 border-bottom: @border-thickness solid @grey5;
229 228 }
230 229
231 230 .title {
232 231 clear: both;
233 232 float: left;
234 233 width: 100%;
235 234 margin: @pagepadding/2 0 @pagepadding;
236 235
237 236 .breadcrumbs {
238 237 float: left;
239 238 clear: both;
240 239 width: 700px;
241 240 margin: 0;
242 241
243 242 .q_filter_box {
244 243 margin-right: @padding;
245 244 }
246 245 }
247 246
248 247 h1 a {
249 248 color: @rcblue;
250 249 }
251 250
252 251 input{
253 252 margin-right: @padding;
254 253 }
255 254
256 255 h5, .h5 {
257 256 color: @grey1;
258 257 margin-bottom: @space;
259 258
260 259 span {
261 260 display: inline-block;
262 261 }
263 262 }
264 263
265 264 p {
266 265 margin-bottom: 0;
267 266 }
268 267
269 268 .links {
270 269 float: right;
271 270 display: inline;
272 271 margin: 0;
273 272 padding-left: 0;
274 273 list-style: none;
275 274 text-align: right;
276 275
277 276 li {
278 277 float: right;
279 278 list-style-type: none;
280 279 }
281 280
282 281 a {
283 282 display: inline-block;
284 283 margin-left: @textmargin/2;
285 284 }
286 285 }
287 286
288 287 .title-content {
289 288 float: left;
290 289 margin: 0;
291 290 padding: 0;
292 291
293 292 & + .breadcrumbs {
294 293 margin-top: @padding;
295 294 }
296 295
297 296 & + .links {
298 297 margin-top: -@button-padding;
299 298
300 299 & + .breadcrumbs {
301 300 margin-top: @padding;
302 301 }
303 302 }
304 303 }
305 304
306 305 .title-main {
307 306 font-size: @repo-title-fontsize;
308 307 }
309 308
310 309 .title-description {
311 310 margin-top: .5em;
312 311 }
313 312
314 313 .q_filter_box {
315 314 width: 200px;
316 315 }
317 316
318 317 }
319 318
320 319 #readme .title {
321 320 text-transform: none;
322 321 }
323 322
324 323 // Lists
325 324 // -------------------------
326 325
327 326 // Unordered and Ordered lists
328 327 ul,
329 328 ol {
330 329 margin-top: 0;
331 330 margin-bottom: @textmargin;
332 331 ul,
333 332 ol {
334 333 margin-bottom: 0;
335 334 }
336 335 }
337 336
338 337 li {
339 338 line-height: 2em;
340 339 }
341 340
342 341 ul li {
343 342 position: relative;
344 343 list-style-type: disc;
345 344
346 345 p:first-child {
347 346 display:inline;
348 347 }
349 348 }
350 349
351 350 // List options
352 351
353 352 // Unstyled keeps list items block level, just removes default browser padding and list-style
354 353 .list-unstyled {
355 354 padding-left: 0;
356 355 list-style: none;
357 356 li:before { content: none; }
358 357 }
359 358
360 359 // Inline turns list items into inline-block
361 360 .list-inline {
362 361 .list-unstyled();
363 362 margin-left: -5px;
364 363
365 364 > li {
366 365 display: inline-block;
367 366 padding-left: 5px;
368 367 padding-right: 5px;
369 368 }
370 369 }
371 370
372 371 // Description Lists
373 372
374 373 dl {
375 374 margin-top: 0; // Remove browser default
376 375 margin-bottom: @textmargin;
377 376 }
378 377
379 378 dt,
380 379 dd {
381 380 line-height: 1.4em;
382 381 }
383 382
384 383 dt {
385 384 margin: @textmargin 0 0 0;
386 385 font-weight: @text-bold-weight;
387 386 font-family: @text-bold;
388 387 }
389 388
390 389 dd {
391 390 margin-left: 0; // Undo browser default
392 391 }
393 392
394 393 // Horizontal description lists
395 394 // Defaults to being stacked without any of the below styles applied, until the
396 395 // grid breakpoint is reached (default of ~768px).
397 396 // These are used in forms as well; see style guide.
398 397 // TODO: lisa: These should really not be used in forms.
399 398
400 399 .dl-horizontal {
401 400
402 401 overflow: hidden;
403 402 margin-bottom: @space;
404 403
405 404 dt, dd {
406 405 float: left;
407 406 margin: 5px 0 5px 0;
408 407 }
409 408
410 409 dt {
411 410 clear: left;
412 411 width: @label-width - @form-vertical-margin;
413 412 }
414 413
415 414 dd {
416 415 &:extend(.clearfix all); // Clear the floated `dt` if an empty `dd` is present
417 416 margin-left: @form-vertical-margin;
418 417 max-width: @form-max-width - (@label-width - @form-vertical-margin) - @form-vertical-margin;
419 418 }
420 419
421 420 pre {
422 421 margin: 0;
423 422 }
424 423
425 424 &.settings {
426 425 dt {
427 426 text-align: left;
428 427 }
429 428 }
430 429
431 430 @media (min-width: 768px) {
432 431 dt {
433 432 float: left;
434 433 width: 185px;
435 434 clear: left;
436 435 text-align: right;
437 436 }
438 437 dd {
439 438 margin-left: 20px;
440 439 }
441 440 }
442 441 }
443 442
444 443
445 444 // Misc
446 445 // -------------------------
447 446
448 447 // Abbreviations and acronyms
449 448 abbr[title],
450 449 abbr[data-original-title] {
451 450 cursor: help;
452 451 border-bottom: @border-thickness dotted @grey4;
453 452 }
454 453 .initialism {
455 454 font-size: 90%;
456 455 text-transform: uppercase;
457 456 }
458 457
459 458 // Blockquotes
460 459 blockquote {
461 460 padding: 1em 2em;
462 461 margin: 0 0 2em;
463 462 font-size: @basefontsize;
464 463 border-left: 2px solid @grey6;
465 464
466 465 p,
467 466 ul,
468 467 ol {
469 468 &:last-child {
470 469 margin-bottom: 0;
471 470 }
472 471 }
473 472
474 473 footer,
475 474 small,
476 475 .small {
477 476 display: block;
478 477 font-size: 80%;
479 478
480 479 &:before {
481 480 content: '\2014 \00A0'; // em dash, nbsp
482 481 }
483 482 }
484 483 }
485 484
486 485 // Opposite alignment of blockquote
487 486 //
488 487 .blockquote-reverse,
489 488 blockquote.pull-right {
490 489 padding-right: 15px;
491 490 padding-left: 0;
492 491 border-right: 5px solid @grey6;
493 492 border-left: 0;
494 493 text-align: right;
495 494
496 495 // Account for citation
497 496 footer,
498 497 small,
499 498 .small {
500 499 &:before { content: ''; }
501 500 &:after {
502 501 content: '\00A0 \2014'; // nbsp, em dash
503 502 }
504 503 }
505 504 }
506 505
507 506 // Addresses
508 507 address {
509 508 margin-bottom: 2em;
510 509 font-style: normal;
511 510 line-height: 1.8em;
512 511 }
513 512
514 513 .error-message {
515 514 display: block;
516 515 margin: @padding/3 0;
517 516 color: @alert2;
518 517 }
519 518
520 519 .issue-tracker-link {
521 520 color: @rcblue;
522 521 }
523 522
524 523 .info_text{
525 524 font-size: @basefontsize;
526 525 color: @grey4;
527 526 font-family: @text-regular;
528 527 }
529 528
530 529 // help block text
531 530 .help-block {
532 531 display: block;
533 532 margin: 0 0 @padding;
534 533 color: @grey4;
535 534 font-family: @text-light;
536 535 &.pre-formatting {
537 536 white-space: pre-wrap;
538 537 }
539 538 }
540 539
541 540 .error-message {
542 541 display: block;
543 542 margin: @padding/3 0;
544 543 color: @alert2;
545 544 }
@@ -1,13 +1,18 b''
1 1 <div class="panel panel-default">
2 2 <div class="panel-heading">
3 3 <h3 class="panel-title">${_('RhodeCode Full Text Search')}</h3>
4 4 </div>
5 5 <div class="panel-body">
6 6 <dl class="dl-horizontal">
7 7 % for stat in c.statistics:
8 <dt>${stat['key']}</dt>
9 <dd>${stat['value']}</dd>
8 % if stat.get('sep'):
9 <dt></dt>
10 <dd>--</dd>
11 % else:
12 <dt>${stat['key']}</dt>
13 <dd>${stat['value']}</dd>
14 % endif
10 15 % endfor
11 16 </dl>
12 17 </div>
13 18 </div>
@@ -1,149 +1,158 b''
1 1 ## -*- coding: utf-8 -*-
2 2 <!DOCTYPE html>
3 3
4 4 <%
5 5 c.template_context['repo_name'] = getattr(c, 'repo_name', '')
6 6 go_import_header = ''
7 7 if hasattr(c, 'rhodecode_db_repo'):
8 8 c.template_context['repo_type'] = c.rhodecode_db_repo.repo_type
9 9 c.template_context['repo_landing_commit'] = c.rhodecode_db_repo.landing_rev[1]
10 ## check repo context
11 c.template_context['repo_view_type'] = h.get_repo_view_type(request)
10 12
11 13 if getattr(c, 'repo_group', None):
12 14 c.template_context['repo_group_id'] = c.repo_group.group_id
15 c.template_context['repo_group_name'] = c.repo_group.group_name
13 16
14 17 if getattr(c, 'rhodecode_user', None) and c.rhodecode_user.user_id:
15 18 c.template_context['rhodecode_user']['username'] = c.rhodecode_user.username
16 19 c.template_context['rhodecode_user']['email'] = c.rhodecode_user.email
17 20 c.template_context['rhodecode_user']['notification_status'] = c.rhodecode_user.get_instance().user_data.get('notification_status', True)
18 21 c.template_context['rhodecode_user']['first_name'] = c.rhodecode_user.first_name
19 22 c.template_context['rhodecode_user']['last_name'] = c.rhodecode_user.last_name
20 23
21 24 c.template_context['visual']['default_renderer'] = h.get_visual_attr(c, 'default_renderer')
22 25 c.template_context['default_user'] = {
23 26 'username': h.DEFAULT_USER,
24 27 'user_id': 1
25 28 }
29 c.template_context['search_context'] = {
30 'repo_group_id': c.template_context.get('repo_group_id'),
31 'repo_group_name': c.template_context.get('repo_group_name'),
32 'repo_name': c.template_context.get('repo_name'),
33 'repo_view_type': c.template_context.get('repo_view_type'),
34 }
26 35
27 36 %>
28 37 <html xmlns="http://www.w3.org/1999/xhtml">
29 38 <head>
30 39 <title>${self.title()}</title>
31 40 <meta http-equiv="Content-Type" content="text/html;charset=utf-8" />
32 41
33 42 ${h.go_import_header(request, getattr(c, 'rhodecode_db_repo', None))}
34 43
35 44 % if 'safari' in (request.user_agent or '').lower():
36 45 <meta name="referrer" content="origin">
37 46 % else:
38 47 <meta name="referrer" content="origin-when-cross-origin">
39 48 % endif
40 49
41 50 <%def name="robots()">
42 51 <meta name="robots" content="index, nofollow"/>
43 52 </%def>
44 53 ${self.robots()}
45 54 <link rel="icon" href="${h.asset('images/favicon.ico', ver=c.rhodecode_version_hash)}" sizes="16x16 32x32" type="image/png" />
46 55 <script src="${h.asset('js/vendors/webcomponentsjs/custom-elements-es5-adapter.js', ver=c.rhodecode_version_hash)}"></script>
47 56 <script src="${h.asset('js/vendors/webcomponentsjs/webcomponents-bundle.js', ver=c.rhodecode_version_hash)}"></script>
48 57
49 58 ## CSS definitions
50 59 <%def name="css()">
51 60 <link rel="stylesheet" type="text/css" href="${h.asset('css/style.css', ver=c.rhodecode_version_hash)}" media="screen"/>
52 61 ## EXTRA FOR CSS
53 62 ${self.css_extra()}
54 63 </%def>
55 64 ## CSS EXTRA - optionally inject some extra CSS stuff needed for specific websites
56 65 <%def name="css_extra()">
57 66 </%def>
58 67
59 68 ${self.css()}
60 69
61 70 ## JAVASCRIPT
62 71 <%def name="js()">
63 72
64 73 <script src="${h.asset('js/rhodecode/i18n/%s.js' % c.language, ver=c.rhodecode_version_hash)}"></script>
65 74 <script type="text/javascript">
66 75 // register templateContext to pass template variables to JS
67 76 var templateContext = ${h.json.dumps(c.template_context)|n};
68 77
69 78 var APPLICATION_URL = "${h.route_path('home').rstrip('/')}";
70 79 var APPLICATION_PLUGINS = [];
71 80 var ASSET_URL = "${h.asset('')}";
72 81 var DEFAULT_RENDERER = "${h.get_visual_attr(c, 'default_renderer')}";
73 82 var CSRF_TOKEN = "${getattr(c, 'csrf_token', '')}";
74 83
75 84 var APPENLIGHT = {
76 85 enabled: ${'true' if getattr(c, 'appenlight_enabled', False) else 'false'},
77 86 key: '${getattr(c, "appenlight_api_public_key", "")}',
78 87 % if getattr(c, 'appenlight_server_url', None):
79 88 serverUrl: '${getattr(c, "appenlight_server_url", "")}',
80 89 % endif
81 90 requestInfo: {
82 91 % if getattr(c, 'rhodecode_user', None):
83 92 ip: '${c.rhodecode_user.ip_addr}',
84 93 username: '${c.rhodecode_user.username}'
85 94 % endif
86 95 },
87 96 tags: {
88 97 rhodecode_version: '${c.rhodecode_version}',
89 98 rhodecode_edition: '${c.rhodecode_edition}'
90 99 }
91 100 };
92 101
93 102 </script>
94 103 <%include file="/base/plugins_base.mako"/>
95 104 <!--[if lt IE 9]>
96 105 <script language="javascript" type="text/javascript" src="${h.asset('js/src/excanvas.min.js')}"></script>
97 106 <![endif]-->
98 107 <script language="javascript" type="text/javascript" src="${h.asset('js/rhodecode/routes.js', ver=c.rhodecode_version_hash)}"></script>
99 108 <script> var alertMessagePayloads = ${h.flash.json_alerts(request=request)|n}; </script>
100 109 ## avoide escaping the %N
101 110 <script language="javascript" type="text/javascript" src="${h.asset('js/scripts.js', ver=c.rhodecode_version_hash)}"></script>
102 111 <script>CodeMirror.modeURL = "${h.asset('') + 'js/mode/%N/%N.js?ver='+c.rhodecode_version_hash}";</script>
103 112
104 113
105 114 ## JAVASCRIPT EXTRA - optionally inject some extra JS for specificed templates
106 115 ${self.js_extra()}
107 116
108 117 <script type="text/javascript">
109 118 Rhodecode = (function() {
110 119 function _Rhodecode() {
111 120 this.comments = new CommentsController();
112 121 }
113 122 return new _Rhodecode();
114 123 })();
115 124
116 125 $(document).ready(function(){
117 126 show_more_event();
118 127 timeagoActivate();
119 128 clipboardActivate();
120 129 })
121 130 </script>
122 131
123 132 </%def>
124 133
125 134 ## JAVASCRIPT EXTRA - optionally inject some extra JS for specificed templates
126 135 <%def name="js_extra()"></%def>
127 136 ${self.js()}
128 137
129 138 <%def name="head_extra()"></%def>
130 139 ${self.head_extra()}
131 140 ## extra stuff
132 141 %if c.pre_code:
133 142 ${c.pre_code|n}
134 143 %endif
135 144 </head>
136 145 <body id="body">
137 146 <noscript>
138 147 <div class="noscript-error">
139 148 ${_('Please enable JavaScript to use RhodeCode Enterprise')}
140 149 </div>
141 150 </noscript>
142 151
143 152 ${next.body()}
144 153 %if c.post_code:
145 154 ${c.post_code|n}
146 155 %endif
147 156 <rhodecode-app></rhodecode-app>
148 157 </body>
149 158 </html>
@@ -1,107 +1,152 b''
1 1 ## -*- coding: utf-8 -*-
2 2 <%inherit file="/base/base.mako"/>
3 3
4 4 <%def name="title()">
5 5 %if c.repo_name:
6 6 ${_('Search inside repository %(repo_name)s') % {'repo_name': c.repo_name}}
7 7 %else:
8 8 ${_('Search inside all accessible repositories')}
9 9 %endif
10 10 %if c.rhodecode_name:
11 11 &middot; ${h.branding(c.rhodecode_name)}
12 12 %endif
13 13 </%def>
14 14
15 15 <%def name="breadcrumbs_links()">
16 16 %if c.repo_name:
17 17 ${_('Search inside repository %(repo_name)s') % {'repo_name': c.repo_name}}
18 18 %else:
19 19 ${_('Search inside all accessible repositories')}
20 20 %endif
21 %if c.cur_query:
22 &raquo;
23 ${c.cur_query}
24 %endif
21
25 22 </%def>
26 23
27 24 <%def name="menu_bar_nav()">
28 25 %if c.repo_name:
29 26 ${self.menu_items(active='repositories')}
30 27 %else:
31 28 ${self.menu_items(active='search')}
32 29 %endif
33 30 </%def>
34 31
35 32 <%def name="menu_bar_subnav()">
36 33 %if c.repo_name:
37 34 ${self.repo_menu(active='options')}
38 35 %endif
39 36 </%def>
40 37
41 38 <%def name="main()">
42 39 <div class="box">
43 40 %if c.repo_name:
44 41 <!-- box / title -->
45 42 <div class="title">
46 43 ${self.repo_page_title(c.rhodecode_db_repo)}
47 44 </div>
48 45 ${h.form(h.route_path('search_repo',repo_name=c.repo_name),method='get')}
49 46 %else:
50 47 <!-- box / title -->
51 48 <div class="title">
52 49 ${self.breadcrumbs()}
53 50 <ul class="links">&nbsp;</ul>
54 51 </div>
55 52 <!-- end box / title -->
56 53 ${h.form(h.route_path('search'), method='get')}
57 54 %endif
58 55 <div class="form search-form">
59 56 <div class="fields">
60 57 ${h.text('q', c.cur_query, placeholder="Enter query...")}
61 58
62 ${h.select('type',c.search_type,[('content',_('File contents')), ('commit',_('Commit messages')), ('path',_('File names')),],id='id_search_type')}
59 ${h.select('type',c.search_type,[('content',_('Files')), ('path',_('File path')),('commit',_('Commits'))],id='id_search_type')}
60 ${h.hidden('max_lines', '10')}
63 61 <input type="submit" value="${_('Search')}" class="btn"/>
64 62 <br/>
65 63
66 64 <div class="search-feedback-items">
67 65 % for error in c.errors:
68 66 <span class="error-message">
69 67 % for k,v in error.asdict().items():
70 68 ${k} - ${v}
71 69 % endfor
72 70 </span>
73 71 % endfor
74 72 <div class="field">
75 <p class="filterexample" style="position: inherit" onclick="$('#search-help').toggle()">${_('Example Queries')}</p>
76 <pre id="search-help" style="display: none">${h.tooltip(h.search_filter_help(c.searcher, request))}</pre>
73 <p class="filterexample" style="position: inherit" onclick="$('#search-help').toggle()">${_('Query Langague examples')}</p>
74 <pre id="search-help" style="display: none">\
75
76 % if c.searcher.name == 'whoosh':
77 Example filter terms for `Whoosh` search:
78 query lang: <a href="${c.searcher.query_lang_doc}">Whoosh Query Language</a>
79 Whoosh has limited query capabilities. For advanced search use ElasticSearch 6 from RhodeCode EE edition.
80
81 Generate wildcards using '*' character:
82 "repo_name:vcs*" - search everything starting with 'vcs'
83 "repo_name:*vcs*" - search for repository containing 'vcs'
84
85 Optional AND / OR operators in queries
86 "repo_name:vcs OR repo_name:test"
87 "owner:test AND repo_name:test*" AND extension:py
88
89 Move advanced search is available via ElasticSearch6 backend in EE edition.
90 % elif c.searcher.name == 'elasticsearch' and c.searcher.es_version == '2':
91 Example filter terms for `ElasticSearch-${c.searcher.es_version}`search:
92 ElasticSearch-2 has limited query capabilities. For advanced search use ElasticSearch 6 from RhodeCode EE edition.
93
94 search type: content (File Content)
95 indexed fields: content
96
97 # search for `fix` string in all files
98 fix
99
100 search type: commit (Commit message)
101 indexed fields: message
102
103 search type: path (File name)
104 indexed fields: path
105
106 % else:
107 Example filter terms for `ElasticSearch-${c.searcher.es_version}`search:
108 query lang: <a href="${c.searcher.query_lang_doc}">ES 6 Query Language</a>
109 The reserved characters needed espace by `\`: + - = && || > < ! ( ) { } [ ] ^ " ~ * ? : \ /
110 % for handler in c.searcher.get_handlers().values():
111
112 search type: ${handler.search_type_label}
113 *indexed fields*: ${', '.join( [('\n ' if x[0]%4==0 else '')+x[1] for x in enumerate(handler.es_6_field_names)])}
114 % for entry in handler.es_6_example_queries:
115 ${entry.rstrip()}
116 % endfor
117 % endfor
118
119 % endif
120 </pre>
77 121 </div>
78 122
79 123 <div class="field">${c.runtime}</div>
80 124 </div>
81 125 </div>
82 126 </div>
83 127
84 128 ${h.end_form()}
85 129 <div class="search">
86 130 % if c.search_type == 'content':
87 131 <%include file='search_content.mako'/>
88 132 % elif c.search_type == 'path':
89 133 <%include file='search_path.mako'/>
90 134 % elif c.search_type == 'commit':
91 135 <%include file='search_commit.mako'/>
92 136 % elif c.search_type == 'repository':
93 137 <%include file='search_repository.mako'/>
94 138 % endif
95 139 </div>
96 140 </div>
97 141 <script>
98 142 $(document).ready(function(){
143 $('#q').autoGrowInput();
99 144 $("#id_search_type").select2({
100 145 'containerCssClass': "drop-menu",
101 146 'dropdownCssClass': "drop-menu-dropdown",
102 147 'dropdownAutoWidth': true,
103 148 'minimumResultsForSearch': -1
104 149 });
105 150 })
106 151 </script>
107 152 </%def>
@@ -1,82 +1,102 b''
1 1 <%namespace name="base" file="/base/base.mako"/>
2 2
3 % if c.formatted_results:
4
3 5 <table class="rctable search-results">
4 6 <tr>
5 7 <th>${_('Repository')}</th>
6 8 <th>${_('Commit')}</th>
7 9 <th></th>
8 10 <th>${_('Commit message')}</th>
9 11 <th>
10 12 %if c.sort == 'newfirst':
11 13 <a href="${c.url_generator(sort='oldfirst')}">${_('Age (new first)')}</a>
12 14 %else:
13 15 <a href="${c.url_generator(sort='newfirst')}">${_('Age (old first)')}</a>
14 16 %endif
15 17 </th>
16 18 <th>${_('Author')}</th>
17 19 </tr>
18 20 %for entry in c.formatted_results:
19 21 ## search results are additionally filtered, and this check is just a safe gate
20 22 % if h.HasRepoPermissionAny('repository.write','repository.read','repository.admin')(entry['repository'], 'search results commit check'):
21 23 <tr class="body">
22 24 <td class="td-componentname">
23 25 %if h.get_repo_type_by_name(entry.get('repository')) == 'hg':
24 26 <i class="icon-hg"></i>
25 27 %elif h.get_repo_type_by_name(entry.get('repository')) == 'git':
26 28 <i class="icon-git"></i>
27 29 %elif h.get_repo_type_by_name(entry.get('repository')) == 'svn':
28 30 <i class="icon-svn"></i>
29 31 %endif
30 32 ${h.link_to(entry['repository'], h.route_path('repo_summary',repo_name=entry['repository']))}
31 33 </td>
32 34 <td class="td-commit">
33 35 ${h.link_to(h._shorten_commit_id(entry['commit_id']),
34 36 h.route_path('repo_commit',repo_name=entry['repository'],commit_id=entry['commit_id']))}
35 37 </td>
36 38 <td class="td-message expand_commit search open" data-commit-id="${h.md5_safe(entry['repository'])+entry['commit_id']}" id="t-${h.md5_safe(entry['repository'])+entry['commit_id']}" title="${_('Expand commit message')}">
37 39 <div>
38 40 <i class="icon-expand-linked"></i>&nbsp;
39 41 </div>
40 42 </td>
41 43 <td data-commit-id="${h.md5_safe(entry['repository'])+entry['commit_id']}" id="c-${h.md5_safe(entry['repository'])+entry['commit_id']}" class="message td-description open">
42 44 %if entry.get('message_hl'):
43 45 ${h.literal(entry['message_hl'])}
44 46 %else:
45 47 ${h.urlify_commit_message(entry['message'], entry['repository'])}
46 48 %endif
47 49 </td>
48 50 <td class="td-time">
49 51 ${h.age_component(h.time_to_utcdatetime(entry['date']))}
50 52 </td>
51 53
52 54 <td class="td-user author">
53 ${base.gravatar_with_user(entry['author'])}
55 <%
56 ## es6 stores this as object
57 author = entry['author']
58 if isinstance(author, dict):
59 author = author['email']
60 %>
61 ${base.gravatar_with_user(author)}
54 62 </td>
55 63 </tr>
56 64 % endif
57 65 %endfor
58 66 </table>
59 67
60 %if c.cur_query and c.formatted_results:
68 %if c.cur_query:
61 69 <div class="pagination-wh pagination-left">
62 70 ${c.formatted_results.pager('$link_previous ~2~ $link_next')}
63 71 </div>
64 72 %endif
65 73
66 74 <script>
67 75 $('.expand_commit').on('click',function(e){
68 76 var target_expand = $(this);
69 77 var cid = target_expand.data('commit-id');
70 78
71 79 if (target_expand.hasClass('open')){
72 80 $('#c-'+cid).css({'height': '1.5em', 'white-space': 'nowrap', 'text-overflow': 'ellipsis', 'overflow':'hidden'})
73 81 $('#t-'+cid).css({'height': 'auto', 'line-height': '.9em', 'text-overflow': 'ellipsis', 'overflow':'hidden'})
74 82 target_expand.removeClass('open');
75 83 }
76 84 else {
77 85 $('#c-'+cid).css({'height': 'auto', 'white-space': 'normal', 'text-overflow': 'initial', 'overflow':'visible'})
78 86 $('#t-'+cid).css({'height': 'auto', 'max-height': 'none', 'text-overflow': 'initial', 'overflow':'visible'})
79 87 target_expand.addClass('open');
80 88 }
81 89 });
90
91 $(".message.td-description").mark(
92 "${c.searcher.query_to_mark(c.cur_query, 'message')}",
93 {
94 "className": 'match',
95 "accuracy": "complementary",
96 "ignorePunctuation": ":._(){}[]!'+=".split("")
97 }
98 );
99
82 100 </script>
101
102 % endif
@@ -1,100 +1,151 b''
1 <%def name="highlight_text_file(terms, text, url, line_context=3,
2 max_lines=10,
3 mimetype=None, filepath=None)">
4 <%
5 lines = text.split('\n')
6 lines_of_interest = set()
7 matching_lines = h.get_matching_line_offsets(lines, terms)
8 shown_matching_lines = 0
9 1
10 for line_number in matching_lines:
11 if len(lines_of_interest) < max_lines:
12 lines_of_interest |= set(range(
13 max(line_number - line_context, 0),
14 min(line_number + line_context, len(lines) + 1)))
15 shown_matching_lines += 1
16
17 %>
18 ${h.code_highlight(
19 text,
20 h.get_lexer_safe(
21 mimetype=mimetype,
22 filepath=filepath,
23 ),
24 h.SearchContentCodeHtmlFormatter(
25 linenos=True,
26 cssclass="code-highlight",
27 url=url,
28 query_terms=terms,
29 only_line_numbers=lines_of_interest
30 ))|n}
2 <%def name="highlight_text_file(has_matched_content, file_content, lexer, html_formatter, matching_lines, shown_matching_lines, url, use_hl_filter)">
3 % if has_matched_content:
4 ${h.code_highlight(file_content, lexer, html_formatter, use_hl_filter=use_hl_filter)|n}
5 % else:
6 ${_('No content matched')} <br/>
7 % endif
31 8
32 9 %if len(matching_lines) > shown_matching_lines:
33 10 <a href="${url}">
34 11 ${len(matching_lines) - shown_matching_lines} ${_('more matches in this file')}
35 12 </a>
36 13 %endif
37 14 </%def>
38 15
39 16 <div class="search-results">
17 <% query_mark = c.searcher.query_to_mark(c.cur_query, 'content') %>
18
40 19 %for entry in c.formatted_results:
20
21 <%
22 file_content = entry['content_highlight'] or entry['content']
23 mimetype = entry.get('mimetype')
24 filepath = entry.get('path')
25 max_lines = h.safe_int(request.GET.get('max_lines', '10'))
26 line_context = h.safe_int(request.GET.get('line_contenxt', '3'))
27
28 match_file_url=h.route_path('repo_files',repo_name=entry['repository'], commit_id=entry.get('commit_id', 'tip'),f_path=entry['f_path'], _query={"mark": query_mark})
29 terms = c.cur_query
30
31 if c.searcher.is_es_6:
32 # use empty terms so we default to markers usage
33 total_lines, matching_lines = h.get_matching_line_offsets(file_content, terms=None)
34 else:
35 total_lines, matching_lines = h.get_matching_line_offsets(file_content, terms)
36
37 shown_matching_lines = 0
38 lines_of_interest = set()
39 for line_number in matching_lines:
40 if len(lines_of_interest) < max_lines:
41 lines_of_interest |= set(range(
42 max(line_number - line_context, 0),
43 min(line_number + line_context, total_lines + 1)))
44 shown_matching_lines += 1
45 lexer = h.get_lexer_safe(mimetype=mimetype, filepath=filepath)
46
47 html_formatter = h.SearchContentCodeHtmlFormatter(
48 linenos=True,
49 cssclass="code-highlight",
50 url=match_file_url,
51 query_terms=terms,
52 only_line_numbers=lines_of_interest
53 )
54
55 has_matched_content = len(lines_of_interest) >= 1
56
57 %>
41 58 ## search results are additionally filtered, and this check is just a safe gate
42 59 % if h.HasRepoPermissionAny('repository.write','repository.read','repository.admin')(entry['repository'], 'search results content check'):
43 60 <div id="codeblock" class="codeblock">
44 61 <div class="codeblock-header">
45 <h2>
62 <h1>
46 63 %if h.get_repo_type_by_name(entry.get('repository')) == 'hg':
47 64 <i class="icon-hg"></i>
48 65 %elif h.get_repo_type_by_name(entry.get('repository')) == 'git':
49 66 <i class="icon-git"></i>
50 67 %elif h.get_repo_type_by_name(entry.get('repository')) == 'svn':
51 68 <i class="icon-svn"></i>
52 69 %endif
53 70 ${h.link_to(entry['repository'], h.route_path('repo_summary',repo_name=entry['repository']))}
54 </h2>
71 </h1>
72
55 73 <div class="stats">
56 ${h.link_to(h.literal(entry['f_path']), h.route_path('repo_files',repo_name=entry['repository'],commit_id=entry.get('commit_id', 'tip'),f_path=entry['f_path']))}
57 %if entry.get('lines'):
58 | ${entry.get('lines', 0.)} ${_ungettext('line', 'lines', entry.get('lines', 0.))}
59 %endif
60 %if entry.get('size'):
61 | ${h.format_byte_size_binary(entry['size'])}
62 %endif
63 %if entry.get('mimetype'):
64 | ${entry.get('mimetype', "unknown mimetype")}
65 %endif
74 <span class="stats-filename">
75 <strong>
76 <i class="icon-file-text"></i>
77 ${h.link_to(h.literal(entry['f_path']), h.route_path('repo_files',repo_name=entry['repository'],commit_id=entry.get('commit_id', 'tip'),f_path=entry['f_path']))}
78 </strong>
79 </span>
80 <span class="item last"><i class="tooltip icon-clipboard clipboard-action" data-clipboard-text="${entry['f_path']}" title="${_('Copy the full path')}"></i></span>
81 <br/>
82 <span class="stats-first-item">
83 ${len(matching_lines)} ${_ungettext('search match', 'search matches', len(matching_lines))}
84 </span>
85
86 <span >
87 %if entry.get('lines'):
88 | ${entry.get('lines', 0.)} ${_ungettext('line', 'lines', entry.get('lines', 0.))}
89 %endif
90 </span>
91
92 <span>
93 %if entry.get('size'):
94 | ${h.format_byte_size_binary(entry['size'])}
95 %endif
96 </span>
97
98 <span>
99 %if entry.get('mimetype'):
100 | ${entry.get('mimetype', "unknown mimetype")}
101 %endif
102 </span>
103
66 104 </div>
67 105 <div class="buttons">
68 106 <a id="file_history_overview_full" href="${h.route_path('repo_changelog_file',repo_name=entry.get('repository',''),commit_id=entry.get('commit_id', 'tip'),f_path=entry.get('f_path',''))}">
69 107 ${_('Show Full History')}
70 108 </a>
71 109 | ${h.link_to(_('Annotation'), h.route_path('repo_files:annotated', repo_name=entry.get('repository',''),commit_id=entry.get('commit_id', 'tip'),f_path=entry.get('f_path','')))}
72 110 | ${h.link_to(_('Raw'), h.route_path('repo_file_raw', repo_name=entry.get('repository',''),commit_id=entry.get('commit_id', 'tip'),f_path=entry.get('f_path','')))}
73 111 | ${h.link_to(_('Download'), h.route_path('repo_file_download',repo_name=entry.get('repository',''),commit_id=entry.get('commit_id', 'tip'),f_path=entry.get('f_path','')))}
74 112 </div>
75 113 </div>
76 114 <div class="code-body search-code-body">
77 ${highlight_text_file(c.cur_query, entry['content'],
78 url=h.route_path('repo_files',repo_name=entry['repository'],commit_id=entry.get('commit_id', 'tip'),f_path=entry['f_path']),
79 mimetype=entry.get('mimetype'), filepath=entry.get('path'))}
115
116 ${highlight_text_file(
117 has_matched_content=has_matched_content,
118 file_content=file_content,
119 lexer=lexer,
120 html_formatter=html_formatter,
121 matching_lines=matching_lines,
122 shown_matching_lines=shown_matching_lines,
123 url=match_file_url,
124 use_hl_filter=c.searcher.is_es_6
125 )}
80 126 </div>
127
81 128 </div>
82 129 % endif
83 130 %endfor
84 131 </div>
85 132 %if c.cur_query and c.formatted_results:
86 133 <div class="pagination-wh pagination-left" >
87 134 ${c.formatted_results.pager('$link_previous ~2~ $link_next')}
88 135 </div>
89 136 %endif
90 137
91 138 %if c.cur_query:
92 139 <script type="text/javascript">
93 140 $(function(){
94 $(".code").mark(
95 '${' '.join(h.normalize_text_for_matching(c.cur_query).split())}',
96 {"className": 'match',
97 });
141 $(".search-code-body").mark(
142 "${query_mark}",
143 {
144 "className": 'match',
145 "accuracy": "complementary",
146 "ignorePunctuation": ":._(){}[]!'+=".split("")
147 }
148 );
98 149 })
99 150 </script>
100 %endif No newline at end of file
151 %endif
@@ -1,34 +1,38 b''
1 % if c.formatted_results:
2
1 3 <table class="rctable search-results">
2 4 <tr>
3 5 <th>${_('Repository')}</th>
4 6 <th>${_('File')}</th>
5 7 ##TODO: add 'Last Change' and 'Author' here
6 8 </tr>
7 9 %for entry in c.formatted_results:
8 10 ## search results are additionally filtered, and this check is just a safe gate
9 11 % if h.HasRepoPermissionAny('repository.write','repository.read','repository.admin')(entry['repository'], 'search results path check'):
10 12 <tr class="body">
11 13 <td class="td-componentname">
12 14 %if h.get_repo_type_by_name(entry.get('repository')) == 'hg':
13 15 <i class="icon-hg"></i>
14 16 %elif h.get_repo_type_by_name(entry.get('repository')) == 'git':
15 17 <i class="icon-git"></i>
16 18 %elif h.get_repo_type_by_name(entry.get('repository')) == 'svn':
17 19 <i class="icon-svn"></i>
18 20 %endif
19 21 ${h.link_to(entry['repository'], h.route_path('repo_summary',repo_name=entry['repository']))}
20 22 </td>
21 23 <td class="td-componentname">
22 24 ${h.link_to(h.literal(entry['f_path']),
23 25 h.route_path('repo_files',repo_name=entry['repository'],commit_id='tip',f_path=entry['f_path']))}
24 26 </td>
25 27 </tr>
26 28 % endif
27 29 %endfor
28 30 </table>
29 31
30 %if c.cur_query and c.formatted_results:
32 %if c.cur_query:
31 33 <div class="pagination-wh pagination-left">
32 34 ${c.formatted_results.pager('$link_previous ~2~ $link_next')}
33 35 </div>
34 %endif No newline at end of file
36 %endif
37
38 % endif
@@ -1,251 +1,210 b''
1 1 # -*- coding: utf-8 -*-
2 2
3 3 # Copyright (C) 2010-2018 RhodeCode GmbH
4 4 #
5 5 # This program is free software: you can redistribute it and/or modify
6 6 # it under the terms of the GNU Affero General Public License, version 3
7 7 # (only), as published by the Free Software Foundation.
8 8 #
9 9 # This program is distributed in the hope that it will be useful,
10 10 # but WITHOUT ANY WARRANTY; without even the implied warranty of
11 11 # MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
12 12 # GNU General Public License for more details.
13 13 #
14 14 # You should have received a copy of the GNU Affero General Public License
15 15 # along with this program. If not, see <http://www.gnu.org/licenses/>.
16 16 #
17 17 # This program is dual-licensed. If you wish to learn more about the
18 18 # RhodeCode Enterprise Edition, including its added features, Support services,
19 19 # and proprietary license terms, please see https://rhodecode.com/licenses/
20 20
21 21 import copy
22 22 import mock
23 23 import pytest
24 24
25 25 from rhodecode.lib import helpers
26 26 from rhodecode.lib.utils2 import AttributeDict
27 27 from rhodecode.model.settings import IssueTrackerSettingsModel
28 28 from rhodecode.tests import no_newline_id_generator
29 29
30 30
31 31 @pytest.mark.parametrize('url, expected_url', [
32 32 ('http://rc.rc/test', '<a href="http://rc.rc/test">http://rc.rc/test</a>'),
33 33 ('http://rc.rc/@foo', '<a href="http://rc.rc/@foo">http://rc.rc/@foo</a>'),
34 34 ('http://rc.rc/!foo', '<a href="http://rc.rc/!foo">http://rc.rc/!foo</a>'),
35 35 ('http://rc.rc/&foo', '<a href="http://rc.rc/&foo">http://rc.rc/&foo</a>'),
36 36 ('http://rc.rc/#foo', '<a href="http://rc.rc/#foo">http://rc.rc/#foo</a>'),
37 37 ])
38 38 def test_urlify_text(url, expected_url):
39 39 assert helpers.urlify_text(url) == expected_url
40 40
41 41
42 42 @pytest.mark.parametrize('repo_name, commit_id, path, expected_result', [
43 43 ('rX<X', 'cX<X', 'pX<X/aX<X/bX<X',
44 44 '<a class="pjax-link" href="/rX%3CX/files/cX%3CX/">rX&lt;X</a>/'
45 45 '<a class="pjax-link" href="/rX%3CX/files/cX%3CX/pX%3CX">pX&lt;X</a>/'
46 46 '<a class="pjax-link" href="/rX%3CX/files/cX%3CX/pX%3CX/aX%3CX">aX&lt;X'
47 47 '</a>/bX&lt;X'),
48 48 # Path with only one segment
49 49 ('rX<X', 'cX<X', 'pX<X',
50 50 '<a class="pjax-link" href="/rX%3CX/files/cX%3CX/">rX&lt;X</a>/pX&lt;X'),
51 51 # Empty path
52 52 ('rX<X', 'cX<X', '', 'rX&lt;X'),
53 53 ('rX"X', 'cX"X', 'pX"X/aX"X/bX"X',
54 54 '<a class="pjax-link" href="/rX%22X/files/cX%22X/">rX&#34;X</a>/'
55 55 '<a class="pjax-link" href="/rX%22X/files/cX%22X/pX%22X">pX&#34;X</a>/'
56 56 '<a class="pjax-link" href="/rX%22X/files/cX%22X/pX%22X/aX%22X">aX&#34;X'
57 57 '</a>/bX&#34;X'),
58 58 ], ids=['simple', 'one_segment', 'empty_path', 'simple_quote'])
59 59 def test_files_breadcrumbs_xss(
60 60 repo_name, commit_id, path, app, expected_result):
61 61 result = helpers.files_breadcrumbs(repo_name, commit_id, path)
62 62 # Expect it to encode all path fragments properly. This is important
63 63 # because it returns an instance of `literal`.
64 64 assert result == expected_result
65 65
66 66
67 67 def test_format_binary():
68 68 assert helpers.format_byte_size_binary(298489462784) == '278.0 GiB'
69 69
70 70
71 71 @pytest.mark.parametrize('text_string, pattern, expected', [
72 72 ('No issue here', '(?:#)(?P<issue_id>\d+)', []),
73 73 ('Fix #42', '(?:#)(?P<issue_id>\d+)',
74 74 [{'url': 'http://r.io/{repo}/i/42', 'id': '42'}]),
75 75 ('Fix #42, #53', '(?:#)(?P<issue_id>\d+)', [
76 76 {'url': 'http://r.io/{repo}/i/42', 'id': '42'},
77 77 {'url': 'http://r.io/{repo}/i/53', 'id': '53'}]),
78 78 ('Fix #42', '(?:#)?<issue_id>\d+)', []), # Broken regex
79 79 ])
80 80 def test_extract_issues(backend, text_string, pattern, expected):
81 81 repo = backend.create_repo()
82 82 config = {
83 83 '123': {
84 84 'uid': '123',
85 85 'pat': pattern,
86 86 'url': 'http://r.io/${repo}/i/${issue_id}',
87 87 'pref': '#',
88 88 }
89 89 }
90 90
91 91 def get_settings_mock(self, cache=True):
92 92 return config
93 93
94 94 with mock.patch.object(IssueTrackerSettingsModel,
95 95 'get_settings', get_settings_mock):
96 96 text, issues = helpers.process_patterns(text_string, repo.repo_name)
97 97
98 98 expected = copy.deepcopy(expected)
99 99 for item in expected:
100 100 item['url'] = item['url'].format(repo=repo.repo_name)
101 101
102 102 assert issues == expected
103 103
104 104
105 105 @pytest.mark.parametrize('text_string, pattern, link_format, expected_text', [
106 106 ('Fix #42', '(?:#)(?P<issue_id>\d+)', 'html',
107 107 'Fix <a class="issue-tracker-link" href="http://r.io/{repo}/i/42">#42</a>'),
108 108
109 109 ('Fix #42', '(?:#)(?P<issue_id>\d+)', 'markdown',
110 110 'Fix [#42](http://r.io/{repo}/i/42)'),
111 111
112 112 ('Fix #42', '(?:#)(?P<issue_id>\d+)', 'rst',
113 113 'Fix `#42 <http://r.io/{repo}/i/42>`_'),
114 114
115 115 ('Fix #42', '(?:#)?<issue_id>\d+)', 'html',
116 116 'Fix #42'), # Broken regex
117 117 ])
118 118 def test_process_patterns_repo(backend, text_string, pattern, expected_text, link_format):
119 119 repo = backend.create_repo()
120 120
121 121 def get_settings_mock(self, cache=True):
122 122 return {
123 123 '123': {
124 124 'uid': '123',
125 125 'pat': pattern,
126 126 'url': 'http://r.io/${repo}/i/${issue_id}',
127 127 'pref': '#',
128 128 }
129 129 }
130 130
131 131 with mock.patch.object(IssueTrackerSettingsModel,
132 132 'get_settings', get_settings_mock):
133 133 processed_text, issues = helpers.process_patterns(
134 134 text_string, repo.repo_name, link_format)
135 135
136 136 assert processed_text == expected_text.format(repo=repo.repo_name)
137 137
138 138
139 139 @pytest.mark.parametrize('text_string, pattern, expected_text', [
140 140 ('Fix #42', '(?:#)(?P<issue_id>\d+)',
141 141 'Fix <a class="issue-tracker-link" href="http://r.io/i/42">#42</a>'),
142 142 ('Fix #42', '(?:#)?<issue_id>\d+)',
143 143 'Fix #42'), # Broken regex
144 144 ])
145 145 def test_process_patterns_no_repo(text_string, pattern, expected_text):
146 146
147 147 def get_settings_mock(self, cache=True):
148 148 return {
149 149 '123': {
150 150 'uid': '123',
151 151 'pat': pattern,
152 152 'url': 'http://r.io/i/${issue_id}',
153 153 'pref': '#',
154 154 }
155 155 }
156 156
157 157 with mock.patch.object(IssueTrackerSettingsModel,
158 158 'get_global_settings', get_settings_mock):
159 159 processed_text, issues = helpers.process_patterns(
160 160 text_string, '')
161 161
162 162 assert processed_text == expected_text
163 163
164 164
165 165 def test_process_patterns_non_existent_repo_name(backend):
166 166 text_string = 'Fix #42'
167 167 pattern = '(?:#)(?P<issue_id>\d+)'
168 168 expected_text = ('Fix <a class="issue-tracker-link" '
169 169 'href="http://r.io/do-not-exist/i/42">#42</a>')
170 170
171 171 def get_settings_mock(self, cache=True):
172 172 return {
173 173 '123': {
174 174 'uid': '123',
175 175 'pat': pattern,
176 176 'url': 'http://r.io/${repo}/i/${issue_id}',
177 177 'pref': '#',
178 178 }
179 179 }
180 180
181 181 with mock.patch.object(IssueTrackerSettingsModel,
182 182 'get_global_settings', get_settings_mock):
183 183 processed_text, issues = helpers.process_patterns(
184 184 text_string, 'do-not-exist')
185 185
186 186 assert processed_text == expected_text
187 187
188 188
189 189 def test_get_visual_attr(baseapp):
190 190 from rhodecode.apps._base import TemplateArgs
191 191 c = TemplateArgs()
192 192 assert None is helpers.get_visual_attr(c, 'fakse')
193 193
194 194 # emulate the c.visual behaviour
195 195 c.visual = AttributeDict({})
196 196 assert None is helpers.get_visual_attr(c, 'some_var')
197 197
198 198 c.visual.some_var = 'foobar'
199 199 assert 'foobar' == helpers.get_visual_attr(c, 'some_var')
200 200
201 201
202 202 @pytest.mark.parametrize('test_text, inclusive, expected_text', [
203 203 ('just a string', False, 'just a string'),
204 204 ('just a string\n', False, 'just a string'),
205 205 ('just a string\n next line', False, 'just a string...'),
206 206 ('just a string\n next line', True, 'just a string\n...'),
207 207 ], ids=no_newline_id_generator)
208 208 def test_chop_at(test_text, inclusive, expected_text):
209 209 assert helpers.chop_at_smart(
210 210 test_text, '\n', inclusive, '...') == expected_text
211
212
213 @pytest.mark.parametrize('test_text, expected_output', [
214 ('some text', ['some', 'text']),
215 ('some text', ['some', 'text']),
216 ('some text "with a phrase"', ['some', 'text', 'with a phrase']),
217 ('"a phrase" "another phrase"', ['a phrase', 'another phrase']),
218 ('"justphrase"', ['justphrase']),
219 ('""', []),
220 ('', []),
221 (' ', []),
222 ('" "', []),
223 ])
224 def test_extract_phrases(test_text, expected_output):
225 assert helpers.extract_phrases(test_text) == expected_output
226
227
228 @pytest.mark.parametrize('test_text, text_phrases, expected_output', [
229 ('some text here', ['some', 'here'], [(0, 4), (10, 14)]),
230 ('here here there', ['here'], [(0, 4), (5, 9), (11, 15)]),
231 ('irrelevant', ['not found'], []),
232 ('irrelevant', ['not found'], []),
233 ])
234 def test_get_matching_offsets(test_text, text_phrases, expected_output):
235 assert helpers.get_matching_offsets(
236 test_text, text_phrases) == expected_output
237
238
239 def test_normalize_text_for_matching():
240 assert helpers.normalize_text_for_matching(
241 'OJjfe)*#$*@)$JF*)3r2f80h') == 'ojjfe jf 3r2f80h'
242
243
244 def test_get_matching_line_offsets():
245 assert helpers.get_matching_line_offsets([
246 'words words words',
247 'words words words',
248 'some text some',
249 'words words words',
250 'words words words',
251 'text here what'], 'text') == {3: [(5, 9)], 6: [(0, 4)]}
General Comments 0
You need to be logged in to leave comments. Login now