##// END OF EJS Templates
styling: use sandboxed style for ipython so it doesn't affect style of application anymore.
marcink -
r3784:cc55d3ca default
parent child Browse files
Show More

The requested changes are too big and content was truncated. Show full diff

1 NO CONTENT: new file 100644
NO CONTENT: new file 100644
The requested commit or file is too big and content was truncated. Show full diff
@@ -1,296 +1,296 b''
1 # Nix environment for the community edition
1 # Nix environment for the community edition
2 #
2 #
3 # This shall be as lean as possible, just producing the enterprise-ce
3 # This shall be as lean as possible, just producing the enterprise-ce
4 # derivation. For advanced tweaks to pimp up the development environment we use
4 # derivation. For advanced tweaks to pimp up the development environment we use
5 # "shell.nix" so that it does not have to clutter this file.
5 # "shell.nix" so that it does not have to clutter this file.
6 #
6 #
7 # Configuration, set values in "~/.nixpkgs/config.nix".
7 # Configuration, set values in "~/.nixpkgs/config.nix".
8 # example
8 # example
9 # {
9 # {
10 # # Thoughts on how to configure the dev environment
10 # # Thoughts on how to configure the dev environment
11 # rc = {
11 # rc = {
12 # codeInternalUrl = "https://usr:token@code.rhodecode.com/internal";
12 # codeInternalUrl = "https://usr:token@code.rhodecode.com/internal";
13 # sources = {
13 # sources = {
14 # rhodecode-vcsserver = "/home/user/work/rhodecode-vcsserver";
14 # rhodecode-vcsserver = "/home/user/work/rhodecode-vcsserver";
15 # rhodecode-enterprise-ce = "/home/user/work/rhodecode-enterprise-ce";
15 # rhodecode-enterprise-ce = "/home/user/work/rhodecode-enterprise-ce";
16 # rhodecode-enterprise-ee = "/home/user/work/rhodecode-enterprise-ee";
16 # rhodecode-enterprise-ee = "/home/user/work/rhodecode-enterprise-ee";
17 # };
17 # };
18 # };
18 # };
19 # }
19 # }
20
20
21 args@
21 args@
22 { system ? builtins.currentSystem
22 { system ? builtins.currentSystem
23 , pythonPackages ? "python27Packages"
23 , pythonPackages ? "python27Packages"
24 , pythonExternalOverrides ? self: super: {}
24 , pythonExternalOverrides ? self: super: {}
25 , doCheck ? false
25 , doCheck ? false
26 , ...
26 , ...
27 }:
27 }:
28
28
29 let
29 let
30 pkgs_ = args.pkgs or (import <nixpkgs> { inherit system; });
30 pkgs_ = args.pkgs or (import <nixpkgs> { inherit system; });
31 in
31 in
32
32
33 let
33 let
34 pkgs = import <nixpkgs> {
34 pkgs = import <nixpkgs> {
35 overlays = [
35 overlays = [
36 (import ./pkgs/overlays.nix)
36 (import ./pkgs/overlays.nix)
37 ];
37 ];
38 inherit
38 inherit
39 (pkgs_)
39 (pkgs_)
40 system;
40 system;
41 };
41 };
42
42
43 # Works with the new python-packages, still can fallback to the old
43 # Works with the new python-packages, still can fallback to the old
44 # variant.
44 # variant.
45 basePythonPackagesUnfix = basePythonPackages.__unfix__ or (
45 basePythonPackagesUnfix = basePythonPackages.__unfix__ or (
46 self: basePythonPackages.override (a: { inherit self; }));
46 self: basePythonPackages.override (a: { inherit self; }));
47
47
48 # Evaluates to the last segment of a file system path.
48 # Evaluates to the last segment of a file system path.
49 basename = path: with pkgs.lib; last (splitString "/" path);
49 basename = path: with pkgs.lib; last (splitString "/" path);
50
50
51 # source code filter used as arugment to builtins.filterSource.
51 # source code filter used as arugment to builtins.filterSource.
52 src-filter = path: type: with pkgs.lib;
52 src-filter = path: type: with pkgs.lib;
53 let
53 let
54 ext = last (splitString "." path);
54 ext = last (splitString "." path);
55 in
55 in
56 !builtins.elem (basename path) [
56 !builtins.elem (basename path) [
57 ".git" ".hg" "__pycache__" ".eggs" ".idea" ".dev"
57 ".git" ".hg" "__pycache__" ".eggs" ".idea" ".dev"
58 "node_modules" "node_binaries"
58 "node_modules" "node_binaries"
59 "build" "data" "result" "tmp"] &&
59 "build" "data" "result" "tmp"] &&
60 !builtins.elem ext ["egg-info" "pyc"] &&
60 !builtins.elem ext ["egg-info" "pyc"] &&
61 # TODO: johbo: This check is wrong, since "path" contains an absolute path,
61 # TODO: johbo: This check is wrong, since "path" contains an absolute path,
62 # it would still be good to restore it since we want to ignore "result-*".
62 # it would still be good to restore it since we want to ignore "result-*".
63 !hasPrefix "result" path;
63 !hasPrefix "result" path;
64
64
65 sources =
65 sources =
66 let
66 let
67 inherit
67 inherit
68 (pkgs.lib)
68 (pkgs.lib)
69 all
69 all
70 isString
70 isString
71 attrValues;
71 attrValues;
72 sourcesConfig = pkgs.config.rc.sources or {};
72 sourcesConfig = pkgs.config.rc.sources or {};
73 in
73 in
74 # Ensure that sources are configured as strings. Using a path
74 # Ensure that sources are configured as strings. Using a path
75 # would result in a copy into the nix store.
75 # would result in a copy into the nix store.
76 assert all isString (attrValues sourcesConfig);
76 assert all isString (attrValues sourcesConfig);
77 sourcesConfig;
77 sourcesConfig;
78
78
79 version = builtins.readFile "${rhodecode-enterprise-ce-src}/rhodecode/VERSION";
79 version = builtins.readFile "${rhodecode-enterprise-ce-src}/rhodecode/VERSION";
80 rhodecode-enterprise-ce-src = builtins.filterSource src-filter ./.;
80 rhodecode-enterprise-ce-src = builtins.filterSource src-filter ./.;
81
81
82 nodeEnv = import ./pkgs/node-default.nix {
82 nodeEnv = import ./pkgs/node-default.nix {
83 inherit
83 inherit
84 pkgs
84 pkgs
85 system;
85 system;
86 };
86 };
87 nodeDependencies = nodeEnv.shell.nodeDependencies;
87 nodeDependencies = nodeEnv.shell.nodeDependencies;
88
88
89 rhodecode-testdata-src = sources.rhodecode-testdata or (
89 rhodecode-testdata-src = sources.rhodecode-testdata or (
90 pkgs.fetchhg {
90 pkgs.fetchhg {
91 url = "https://code.rhodecode.com/upstream/rc_testdata";
91 url = "https://code.rhodecode.com/upstream/rc_testdata";
92 rev = "v0.10.0";
92 rev = "v0.10.0";
93 sha256 = "0zn9swwvx4vgw4qn8q3ri26vvzgrxn15x6xnjrysi1bwmz01qjl0";
93 sha256 = "0zn9swwvx4vgw4qn8q3ri26vvzgrxn15x6xnjrysi1bwmz01qjl0";
94 });
94 });
95
95
96 rhodecode-testdata = import "${rhodecode-testdata-src}/default.nix" {
96 rhodecode-testdata = import "${rhodecode-testdata-src}/default.nix" {
97 inherit
97 inherit
98 doCheck
98 doCheck
99 pkgs
99 pkgs
100 pythonPackages;
100 pythonPackages;
101 };
101 };
102
102
103 pythonLocalOverrides = self: super: {
103 pythonLocalOverrides = self: super: {
104 rhodecode-enterprise-ce =
104 rhodecode-enterprise-ce =
105 let
105 let
106 linkNodePackages = ''
106 linkNodePackages = ''
107 export RHODECODE_CE_PATH=${rhodecode-enterprise-ce-src}
107 export RHODECODE_CE_PATH=${rhodecode-enterprise-ce-src}
108
108
109 echo "[BEGIN]: Link node packages and binaries"
109 echo "[BEGIN]: Link node packages and binaries"
110 # johbo: Linking individual packages allows us to run "npm install"
110 # johbo: Linking individual packages allows us to run "npm install"
111 # inside of a shell to try things out. Re-entering the shell will
111 # inside of a shell to try things out. Re-entering the shell will
112 # restore a clean environment.
112 # restore a clean environment.
113 rm -fr node_modules
113 rm -fr node_modules
114 mkdir node_modules
114 mkdir node_modules
115 ln -s ${nodeDependencies}/lib/node_modules/* node_modules/
115 ln -s ${nodeDependencies}/lib/node_modules/* node_modules/
116 export NODE_PATH=./node_modules
116 export NODE_PATH=./node_modules
117
117
118 rm -fr node_binaries
118 rm -fr node_binaries
119 mkdir node_binaries
119 mkdir node_binaries
120 ln -s ${nodeDependencies}/bin/* node_binaries/
120 ln -s ${nodeDependencies}/bin/* node_binaries/
121 echo "[DONE ]: Link node packages and binaries"
121 echo "[DONE ]: Link node packages and binaries"
122 '';
122 '';
123
123
124 releaseName = "RhodeCodeEnterpriseCE-${version}";
124 releaseName = "RhodeCodeEnterpriseCE-${version}";
125 in super.rhodecode-enterprise-ce.override (attrs: {
125 in super.rhodecode-enterprise-ce.override (attrs: {
126 inherit
126 inherit
127 doCheck
127 doCheck
128 version;
128 version;
129
129
130 name = "rhodecode-enterprise-ce-${version}";
130 name = "rhodecode-enterprise-ce-${version}";
131 releaseName = releaseName;
131 releaseName = releaseName;
132 src = rhodecode-enterprise-ce-src;
132 src = rhodecode-enterprise-ce-src;
133 dontStrip = true; # prevent strip, we don't need it.
133 dontStrip = true; # prevent strip, we don't need it.
134
134
135 # expose following attributed outside
135 # expose following attributed outside
136 passthru = {
136 passthru = {
137 inherit
137 inherit
138 rhodecode-testdata
138 rhodecode-testdata
139 linkNodePackages
139 linkNodePackages
140 myPythonPackagesUnfix
140 myPythonPackagesUnfix
141 pythonLocalOverrides
141 pythonLocalOverrides
142 pythonCommunityOverrides;
142 pythonCommunityOverrides;
143
143
144 pythonPackages = self;
144 pythonPackages = self;
145 };
145 };
146
146
147 buildInputs =
147 buildInputs =
148 attrs.buildInputs or [] ++ [
148 attrs.buildInputs or [] ++ [
149 rhodecode-testdata
149 rhodecode-testdata
150 ];
150 ];
151
151
152 #NOTE: option to inject additional propagatedBuildInputs
152 #NOTE: option to inject additional propagatedBuildInputs
153 propagatedBuildInputs =
153 propagatedBuildInputs =
154 attrs.propagatedBuildInputs or [] ++ [
154 attrs.propagatedBuildInputs or [] ++ [
155
155
156 ];
156 ];
157
157
158 LC_ALL = "en_US.UTF-8";
158 LC_ALL = "en_US.UTF-8";
159 LOCALE_ARCHIVE =
159 LOCALE_ARCHIVE =
160 if pkgs.stdenv.isLinux
160 if pkgs.stdenv.isLinux
161 then "${pkgs.glibcLocales}/lib/locale/locale-archive"
161 then "${pkgs.glibcLocales}/lib/locale/locale-archive"
162 else "";
162 else "";
163
163
164 # Add bin directory to path so that tests can find 'rhodecode'.
164 # Add bin directory to path so that tests can find 'rhodecode'.
165 preCheck = ''
165 preCheck = ''
166 export PATH="$out/bin:$PATH"
166 export PATH="$out/bin:$PATH"
167 '';
167 '';
168
168
169 # custom check phase for testing
169 # custom check phase for testing
170 checkPhase = ''
170 checkPhase = ''
171 runHook preCheck
171 runHook preCheck
172 PYTHONHASHSEED=random py.test -vv -p no:sugar -r xw --cov-config=.coveragerc --cov=rhodecode --cov-report=term-missing rhodecode
172 PYTHONHASHSEED=random py.test -vv -p no:sugar -r xw --cov-config=.coveragerc --cov=rhodecode --cov-report=term-missing rhodecode
173 runHook postCheck
173 runHook postCheck
174 '';
174 '';
175
175
176 postCheck = ''
176 postCheck = ''
177 echo "Cleanup of rhodecode/tests"
177 echo "Cleanup of rhodecode/tests"
178 rm -rf $out/lib/${self.python.libPrefix}/site-packages/rhodecode/tests
178 rm -rf $out/lib/${self.python.libPrefix}/site-packages/rhodecode/tests
179 '';
179 '';
180
180
181 preBuild = ''
181 preBuild = ''
182 echo "[BEGIN]: Building frontend assets"
182 echo "[BEGIN]: Building frontend assets"
183 ${linkNodePackages}
183 ${linkNodePackages}
184 make web-build
184 make web-build
185 rm -fr node_modules
185 rm -fr node_modules
186 rm -fr node_binaries
186 rm -fr node_binaries
187 echo "[DONE ]: Building frontend assets"
187 echo "[DONE ]: Building frontend assets"
188 '';
188 '';
189
189
190 postInstall = ''
190 postInstall = ''
191 # check required files
191 # check required files
192 STATIC_CHECK="/robots.txt /502.html
192 STATIC_CHECK="/robots.txt /502.html
193 /js/scripts.js /js/rhodecode-components.js
193 /js/scripts.js /js/rhodecode-components.js
194 /css/style.css /css/style-polymer.css"
194 /css/style.css /css/style-polymer.css /css/style-ipython.css"
195
195
196 for file in $STATIC_CHECK;
196 for file in $STATIC_CHECK;
197 do
197 do
198 if [ ! -f rhodecode/public/$file ]; then
198 if [ ! -f rhodecode/public/$file ]; then
199 echo "Missing $file"
199 echo "Missing $file"
200 exit 1
200 exit 1
201 fi
201 fi
202 done
202 done
203
203
204 echo "Writing enterprise-ce meta information for rccontrol to nix-support/rccontrol"
204 echo "Writing enterprise-ce meta information for rccontrol to nix-support/rccontrol"
205 mkdir -p $out/nix-support/rccontrol
205 mkdir -p $out/nix-support/rccontrol
206 cp -v rhodecode/VERSION $out/nix-support/rccontrol/version
206 cp -v rhodecode/VERSION $out/nix-support/rccontrol/version
207 echo "[DONE ]: enterprise-ce meta information for rccontrol written"
207 echo "[DONE ]: enterprise-ce meta information for rccontrol written"
208
208
209 mkdir -p $out/etc
209 mkdir -p $out/etc
210 cp configs/production.ini $out/etc
210 cp configs/production.ini $out/etc
211 echo "[DONE ]: saved enterprise-ce production.ini into $out/etc"
211 echo "[DONE ]: saved enterprise-ce production.ini into $out/etc"
212
212
213 cp -Rf rhodecode/config/rcextensions $out/etc/rcextensions.tmpl
213 cp -Rf rhodecode/config/rcextensions $out/etc/rcextensions.tmpl
214 echo "[DONE ]: saved enterprise-ce rcextensions into $out/etc/rcextensions.tmpl"
214 echo "[DONE ]: saved enterprise-ce rcextensions into $out/etc/rcextensions.tmpl"
215
215
216 # python based programs need to be wrapped
216 # python based programs need to be wrapped
217 mkdir -p $out/bin
217 mkdir -p $out/bin
218
218
219 # required binaries from dependencies
219 # required binaries from dependencies
220 ln -s ${self.supervisor}/bin/supervisorctl $out/bin/
220 ln -s ${self.supervisor}/bin/supervisorctl $out/bin/
221 ln -s ${self.supervisor}/bin/supervisord $out/bin/
221 ln -s ${self.supervisor}/bin/supervisord $out/bin/
222 ln -s ${self.pastescript}/bin/paster $out/bin/
222 ln -s ${self.pastescript}/bin/paster $out/bin/
223 ln -s ${self.channelstream}/bin/channelstream $out/bin/
223 ln -s ${self.channelstream}/bin/channelstream $out/bin/
224 ln -s ${self.celery}/bin/celery $out/bin/
224 ln -s ${self.celery}/bin/celery $out/bin/
225 ln -s ${self.gunicorn}/bin/gunicorn $out/bin/
225 ln -s ${self.gunicorn}/bin/gunicorn $out/bin/
226 ln -s ${self.pyramid}/bin/prequest $out/bin/
226 ln -s ${self.pyramid}/bin/prequest $out/bin/
227 ln -s ${self.pyramid}/bin/pserve $out/bin/
227 ln -s ${self.pyramid}/bin/pserve $out/bin/
228
228
229 echo "[DONE ]: created symlinks into $out/bin"
229 echo "[DONE ]: created symlinks into $out/bin"
230 DEPS="$out/bin/supervisorctl \
230 DEPS="$out/bin/supervisorctl \
231 $out/bin/supervisord \
231 $out/bin/supervisord \
232 $out/bin/paster \
232 $out/bin/paster \
233 $out/bin/channelstream \
233 $out/bin/channelstream \
234 $out/bin/celery \
234 $out/bin/celery \
235 $out/bin/gunicorn \
235 $out/bin/gunicorn \
236 $out/bin/prequest \
236 $out/bin/prequest \
237 $out/bin/pserve"
237 $out/bin/pserve"
238
238
239 # wrap only dependency scripts, they require to have full PYTHONPATH set
239 # wrap only dependency scripts, they require to have full PYTHONPATH set
240 # to be able to import all packages
240 # to be able to import all packages
241 for file in $DEPS;
241 for file in $DEPS;
242 do
242 do
243 wrapProgram $file \
243 wrapProgram $file \
244 --prefix PATH : $PATH \
244 --prefix PATH : $PATH \
245 --prefix PYTHONPATH : $PYTHONPATH \
245 --prefix PYTHONPATH : $PYTHONPATH \
246 --set PYTHONHASHSEED random
246 --set PYTHONHASHSEED random
247 done
247 done
248
248
249 echo "[DONE ]: enterprise-ce binary wrapping"
249 echo "[DONE ]: enterprise-ce binary wrapping"
250
250
251 # rhodecode-tools don't need wrapping
251 # rhodecode-tools don't need wrapping
252 ln -s ${self.rhodecode-tools}/bin/rhodecode-* $out/bin/
252 ln -s ${self.rhodecode-tools}/bin/rhodecode-* $out/bin/
253
253
254 # expose sources of CE
254 # expose sources of CE
255 ln -s $out $out/etc/rhodecode_enterprise_ce_source
255 ln -s $out $out/etc/rhodecode_enterprise_ce_source
256
256
257 # expose static files folder
257 # expose static files folder
258 cp -Rf $out/lib/${self.python.libPrefix}/site-packages/rhodecode/public/ $out/etc/static
258 cp -Rf $out/lib/${self.python.libPrefix}/site-packages/rhodecode/public/ $out/etc/static
259 chmod 755 -R $out/etc/static
259 chmod 755 -R $out/etc/static
260
260
261 '';
261 '';
262 });
262 });
263
263
264 };
264 };
265
265
266 basePythonPackages = with builtins;
266 basePythonPackages = with builtins;
267 if isAttrs pythonPackages then
267 if isAttrs pythonPackages then
268 pythonPackages
268 pythonPackages
269 else
269 else
270 getAttr pythonPackages pkgs;
270 getAttr pythonPackages pkgs;
271
271
272 pythonGeneratedPackages = import ./pkgs/python-packages.nix {
272 pythonGeneratedPackages = import ./pkgs/python-packages.nix {
273 inherit
273 inherit
274 pkgs;
274 pkgs;
275 inherit
275 inherit
276 (pkgs)
276 (pkgs)
277 fetchurl
277 fetchurl
278 fetchgit
278 fetchgit
279 fetchhg;
279 fetchhg;
280 };
280 };
281
281
282 pythonCommunityOverrides = import ./pkgs/python-packages-overrides.nix {
282 pythonCommunityOverrides = import ./pkgs/python-packages-overrides.nix {
283 inherit pkgs basePythonPackages;
283 inherit pkgs basePythonPackages;
284 };
284 };
285
285
286 # Apply all overrides and fix the final package set
286 # Apply all overrides and fix the final package set
287 myPythonPackagesUnfix = with pkgs.lib;
287 myPythonPackagesUnfix = with pkgs.lib;
288 (extends pythonExternalOverrides
288 (extends pythonExternalOverrides
289 (extends pythonLocalOverrides
289 (extends pythonLocalOverrides
290 (extends pythonCommunityOverrides
290 (extends pythonCommunityOverrides
291 (extends pythonGeneratedPackages
291 (extends pythonGeneratedPackages
292 basePythonPackagesUnfix))));
292 basePythonPackagesUnfix))));
293
293
294 myPythonPackages = (pkgs.lib.fix myPythonPackagesUnfix);
294 myPythonPackages = (pkgs.lib.fix myPythonPackagesUnfix);
295
295
296 in myPythonPackages.rhodecode-enterprise-ce
296 in myPythonPackages.rhodecode-enterprise-ce
@@ -1,175 +1,177 b''
1 {
1 {
2 "dirs": {
2 "dirs": {
3 "css": {
3 "css": {
4 "src": "rhodecode/public/css",
4 "src": "rhodecode/public/css",
5 "dest": "rhodecode/public/css"
5 "dest": "rhodecode/public/css"
6 },
6 },
7 "js": {
7 "js": {
8 "src": "rhodecode/public/js/src",
8 "src": "rhodecode/public/js/src",
9 "src_rc": "rhodecode/public/js/rhodecode",
9 "src_rc": "rhodecode/public/js/rhodecode",
10 "dest": "rhodecode/public/js",
10 "dest": "rhodecode/public/js",
11 "node_modules": "node_modules"
11 "node_modules": "node_modules"
12 }
12 }
13 },
13 },
14 "copy": {
14 "copy": {
15 "main": {
15 "main": {
16 "files": [
16 "files": [
17 {
17 {
18 "expand": true,
18 "expand": true,
19 "cwd": "node_modules/@webcomponents",
19 "cwd": "node_modules/@webcomponents",
20 "src": "webcomponentsjs/*.*",
20 "src": "webcomponentsjs/*.*",
21 "dest": "<%= dirs.js.dest %>/vendors"
21 "dest": "<%= dirs.js.dest %>/vendors"
22 },
22 },
23 {
23 {
24 "src": "<%= dirs.css.src %>/style-polymer.css",
24 "src": "<%= dirs.css.src %>/style-polymer.css",
25 "dest": "<%= dirs.js.dest %>/src/components/style-polymer.css"
25 "dest": "<%= dirs.js.dest %>/src/components/style-polymer.css"
26 }
26 }
27 ]
27 ]
28 }
28 }
29 },
29 },
30 "concat": {
30 "concat": {
31 "dist": {
31 "dist": {
32 "src": [
32 "src": [
33 "<%= dirs.js.node_modules %>/jquery/dist/jquery.min.js",
33 "<%= dirs.js.node_modules %>/jquery/dist/jquery.min.js",
34 "<%= dirs.js.node_modules %>/mousetrap/mousetrap.min.js",
34 "<%= dirs.js.node_modules %>/mousetrap/mousetrap.min.js",
35 "<%= dirs.js.node_modules %>/moment/min/moment.min.js",
35 "<%= dirs.js.node_modules %>/moment/min/moment.min.js",
36 "<%= dirs.js.node_modules %>/clipboard/dist/clipboard.min.js",
36 "<%= dirs.js.node_modules %>/clipboard/dist/clipboard.min.js",
37 "<%= dirs.js.node_modules %>/favico.js/favico-0.3.10.min.js",
37 "<%= dirs.js.node_modules %>/favico.js/favico-0.3.10.min.js",
38 "<%= dirs.js.node_modules %>/dropzone/dist/dropzone.js",
38 "<%= dirs.js.node_modules %>/dropzone/dist/dropzone.js",
39 "<%= dirs.js.node_modules %>/sticky-sidebar/dist/sticky-sidebar.min.js",
39 "<%= dirs.js.node_modules %>/sticky-sidebar/dist/sticky-sidebar.min.js",
40 "<%= dirs.js.node_modules %>/sticky-sidebar/dist/jquery.sticky-sidebar.min.js",
40 "<%= dirs.js.node_modules %>/sticky-sidebar/dist/jquery.sticky-sidebar.min.js",
41 "<%= dirs.js.node_modules %>/waypoints/lib/noframework.waypoints.min.js",
41 "<%= dirs.js.node_modules %>/waypoints/lib/noframework.waypoints.min.js",
42 "<%= dirs.js.node_modules %>/waypoints/lib/jquery.waypoints.min.js",
42 "<%= dirs.js.node_modules %>/waypoints/lib/jquery.waypoints.min.js",
43 "<%= dirs.js.node_modules %>/appenlight-client/appenlight-client.min.js",
43 "<%= dirs.js.node_modules %>/appenlight-client/appenlight-client.min.js",
44 "<%= dirs.js.src %>/logging.js",
44 "<%= dirs.js.src %>/logging.js",
45 "<%= dirs.js.src %>/bootstrap.js",
45 "<%= dirs.js.src %>/bootstrap.js",
46 "<%= dirs.js.src %>/i18n_utils.js",
46 "<%= dirs.js.src %>/i18n_utils.js",
47 "<%= dirs.js.src %>/deform.js",
47 "<%= dirs.js.src %>/deform.js",
48 "<%= dirs.js.src %>/ejs.js",
48 "<%= dirs.js.src %>/ejs.js",
49 "<%= dirs.js.src %>/ejs_templates/utils.js",
49 "<%= dirs.js.src %>/ejs_templates/utils.js",
50 "<%= dirs.js.src %>/plugins/jquery.pjax.js",
50 "<%= dirs.js.src %>/plugins/jquery.pjax.js",
51 "<%= dirs.js.src %>/plugins/jquery.dataTables.js",
51 "<%= dirs.js.src %>/plugins/jquery.dataTables.js",
52 "<%= dirs.js.src %>/plugins/flavoured_checkbox.js",
52 "<%= dirs.js.src %>/plugins/flavoured_checkbox.js",
53 "<%= dirs.js.src %>/plugins/jquery.auto-grow-input.js",
53 "<%= dirs.js.src %>/plugins/jquery.auto-grow-input.js",
54 "<%= dirs.js.src %>/plugins/jquery.autocomplete.js",
54 "<%= dirs.js.src %>/plugins/jquery.autocomplete.js",
55 "<%= dirs.js.src %>/plugins/jquery.debounce.js",
55 "<%= dirs.js.src %>/plugins/jquery.debounce.js",
56 "<%= dirs.js.node_modules %>/mark.js/dist/jquery.mark.min.js",
56 "<%= dirs.js.node_modules %>/mark.js/dist/jquery.mark.min.js",
57 "<%= dirs.js.src %>/plugins/jquery.timeago.js",
57 "<%= dirs.js.src %>/plugins/jquery.timeago.js",
58 "<%= dirs.js.src %>/plugins/jquery.timeago-extension.js",
58 "<%= dirs.js.src %>/plugins/jquery.timeago-extension.js",
59 "<%= dirs.js.src %>/select2/select2.js",
59 "<%= dirs.js.src %>/select2/select2.js",
60 "<%= dirs.js.src %>/codemirror/codemirror.js",
60 "<%= dirs.js.src %>/codemirror/codemirror.js",
61 "<%= dirs.js.src %>/codemirror/codemirror_loadmode.js",
61 "<%= dirs.js.src %>/codemirror/codemirror_loadmode.js",
62 "<%= dirs.js.src %>/codemirror/codemirror_hint.js",
62 "<%= dirs.js.src %>/codemirror/codemirror_hint.js",
63 "<%= dirs.js.src %>/codemirror/codemirror_overlay.js",
63 "<%= dirs.js.src %>/codemirror/codemirror_overlay.js",
64 "<%= dirs.js.src %>/codemirror/codemirror_placeholder.js",
64 "<%= dirs.js.src %>/codemirror/codemirror_placeholder.js",
65 "<%= dirs.js.src %>/codemirror/codemirror_simplemode.js",
65 "<%= dirs.js.src %>/codemirror/codemirror_simplemode.js",
66 "<%= dirs.js.dest %>/mode/meta.js",
66 "<%= dirs.js.dest %>/mode/meta.js",
67 "<%= dirs.js.dest %>/mode/meta_ext.js",
67 "<%= dirs.js.dest %>/mode/meta_ext.js",
68 "<%= dirs.js.src_rc %>/i18n/select2/translations.js",
68 "<%= dirs.js.src_rc %>/i18n/select2/translations.js",
69 "<%= dirs.js.src %>/rhodecode/utils/array.js",
69 "<%= dirs.js.src %>/rhodecode/utils/array.js",
70 "<%= dirs.js.src %>/rhodecode/utils/string.js",
70 "<%= dirs.js.src %>/rhodecode/utils/string.js",
71 "<%= dirs.js.src %>/rhodecode/utils/pyroutes.js",
71 "<%= dirs.js.src %>/rhodecode/utils/pyroutes.js",
72 "<%= dirs.js.src %>/rhodecode/utils/ajax.js",
72 "<%= dirs.js.src %>/rhodecode/utils/ajax.js",
73 "<%= dirs.js.src %>/rhodecode/utils/autocomplete.js",
73 "<%= dirs.js.src %>/rhodecode/utils/autocomplete.js",
74 "<%= dirs.js.src %>/rhodecode/utils/colorgenerator.js",
74 "<%= dirs.js.src %>/rhodecode/utils/colorgenerator.js",
75 "<%= dirs.js.src %>/rhodecode/utils/ie.js",
75 "<%= dirs.js.src %>/rhodecode/utils/ie.js",
76 "<%= dirs.js.src %>/rhodecode/utils/os.js",
76 "<%= dirs.js.src %>/rhodecode/utils/os.js",
77 "<%= dirs.js.src %>/rhodecode/utils/topics.js",
77 "<%= dirs.js.src %>/rhodecode/utils/topics.js",
78 "<%= dirs.js.src %>/rhodecode/init.js",
78 "<%= dirs.js.src %>/rhodecode/init.js",
79 "<%= dirs.js.src %>/rhodecode/changelog.js",
79 "<%= dirs.js.src %>/rhodecode/changelog.js",
80 "<%= dirs.js.src %>/rhodecode/codemirror.js",
80 "<%= dirs.js.src %>/rhodecode/codemirror.js",
81 "<%= dirs.js.src %>/rhodecode/comments.js",
81 "<%= dirs.js.src %>/rhodecode/comments.js",
82 "<%= dirs.js.src %>/rhodecode/constants.js",
82 "<%= dirs.js.src %>/rhodecode/constants.js",
83 "<%= dirs.js.src %>/rhodecode/files.js",
83 "<%= dirs.js.src %>/rhodecode/files.js",
84 "<%= dirs.js.src %>/rhodecode/followers.js",
84 "<%= dirs.js.src %>/rhodecode/followers.js",
85 "<%= dirs.js.src %>/rhodecode/menus.js",
85 "<%= dirs.js.src %>/rhodecode/menus.js",
86 "<%= dirs.js.src %>/rhodecode/notifications.js",
86 "<%= dirs.js.src %>/rhodecode/notifications.js",
87 "<%= dirs.js.src %>/rhodecode/permissions.js",
87 "<%= dirs.js.src %>/rhodecode/permissions.js",
88 "<%= dirs.js.src %>/rhodecode/pjax.js",
88 "<%= dirs.js.src %>/rhodecode/pjax.js",
89 "<%= dirs.js.src %>/rhodecode/pullrequests.js",
89 "<%= dirs.js.src %>/rhodecode/pullrequests.js",
90 "<%= dirs.js.src %>/rhodecode/settings.js",
90 "<%= dirs.js.src %>/rhodecode/settings.js",
91 "<%= dirs.js.src %>/rhodecode/select2_widgets.js",
91 "<%= dirs.js.src %>/rhodecode/select2_widgets.js",
92 "<%= dirs.js.src %>/rhodecode/tooltips.js",
92 "<%= dirs.js.src %>/rhodecode/tooltips.js",
93 "<%= dirs.js.src %>/rhodecode/users.js",
93 "<%= dirs.js.src %>/rhodecode/users.js",
94 "<%= dirs.js.src %>/rhodecode/appenlight.js",
94 "<%= dirs.js.src %>/rhodecode/appenlight.js",
95 "<%= dirs.js.src %>/rhodecode.js",
95 "<%= dirs.js.src %>/rhodecode.js",
96 "<%= dirs.js.dest %>/rhodecode-components.js"
96 "<%= dirs.js.dest %>/rhodecode-components.js"
97 ],
97 ],
98 "dest": "<%= dirs.js.dest %>/scripts.js",
98 "dest": "<%= dirs.js.dest %>/scripts.js",
99 "nonull": true
99 "nonull": true
100 }
100 }
101 },
101 },
102 "less": {
102 "less": {
103 "development": {
103 "development": {
104 "options": {
104 "options": {
105 "compress": false,
105 "compress": false,
106 "yuicompress": false,
106 "yuicompress": false,
107 "optimization": 0
107 "optimization": 0
108 },
108 },
109 "files": {
109 "files": {
110 "<%= dirs.css.dest %>/style.css": "<%= dirs.css.src %>/main.less",
110 "<%= dirs.css.dest %>/style.css": "<%= dirs.css.src %>/main.less",
111 "<%= dirs.css.dest %>/style-polymer.css": "<%= dirs.css.src %>/polymer.less"
111 "<%= dirs.css.dest %>/style-polymer.css": "<%= dirs.css.src %>/polymer.less",
112 "<%= dirs.css.dest %>/style-ipython.css": "<%= dirs.css.src %>/ipython.less"
112 }
113 }
113 },
114 },
114 "production": {
115 "production": {
115 "options": {
116 "options": {
116 "compress": true,
117 "compress": true,
117 "yuicompress": true,
118 "yuicompress": true,
118 "optimization": 2
119 "optimization": 2
119 },
120 },
120 "files": {
121 "files": {
121 "<%= dirs.css.dest %>/style.css": "<%= dirs.css.src %>/main.less",
122 "<%= dirs.css.dest %>/style.css": "<%= dirs.css.src %>/main.less",
122 "<%= dirs.css.dest %>/style-polymer.css": "<%= dirs.css.src %>/polymer.less"
123 "<%= dirs.css.dest %>/style-polymer.css": "<%= dirs.css.src %>/polymer.less",
124 "<%= dirs.css.dest %>/style-ipython.css": "<%= dirs.css.src %>/ipython.less"
123 }
125 }
124 },
126 },
125 "components": {
127 "components": {
126 "files": [
128 "files": [
127 {
129 {
128 "cwd": "<%= dirs.js.src %>/components/",
130 "cwd": "<%= dirs.js.src %>/components/",
129 "dest": "<%= dirs.js.src %>/components/",
131 "dest": "<%= dirs.js.src %>/components/",
130 "src": [
132 "src": [
131 "**/*.less"
133 "**/*.less"
132 ],
134 ],
133 "expand": true,
135 "expand": true,
134 "ext": ".css"
136 "ext": ".css"
135 }
137 }
136 ]
138 ]
137 }
139 }
138 },
140 },
139 "watch": {
141 "watch": {
140 "less": {
142 "less": {
141 "files": [
143 "files": [
142 "<%= dirs.css.src %>/**/*.less",
144 "<%= dirs.css.src %>/**/*.less",
143 "<%= dirs.js.src %>/components/**/*.less"
145 "<%= dirs.js.src %>/components/**/*.less"
144 ],
146 ],
145 "tasks": [
147 "tasks": [
146 "less:development",
148 "less:development",
147 "less:components",
149 "less:components",
148 "concat:polymercss",
150 "concat:polymercss",
149 "webpack",
151 "webpack",
150 "concat:dist"
152 "concat:dist"
151 ]
153 ]
152 },
154 },
153 "js": {
155 "js": {
154 "files": [
156 "files": [
155 "!<%= dirs.js.src %>/components/root-styles.gen.html",
157 "!<%= dirs.js.src %>/components/root-styles.gen.html",
156 "<%= dirs.js.src %>/**/*.js",
158 "<%= dirs.js.src %>/**/*.js",
157 "<%= dirs.js.src %>/components/**/*.html"
159 "<%= dirs.js.src %>/components/**/*.html"
158 ],
160 ],
159 "tasks": [
161 "tasks": [
160 "less:components",
162 "less:components",
161 "concat:polymercss",
163 "concat:polymercss",
162 "webpack",
164 "webpack",
163 "concat:dist"
165 "concat:dist"
164 ]
166 ]
165 }
167 }
166 },
168 },
167 "jshint": {
169 "jshint": {
168 "rhodecode": {
170 "rhodecode": {
169 "src": "<%= dirs.js.src %>/rhodecode/**/*.js",
171 "src": "<%= dirs.js.src %>/rhodecode/**/*.js",
170 "options": {
172 "options": {
171 "jshintrc": ".jshintrc"
173 "jshintrc": ".jshintrc"
172 }
174 }
173 }
175 }
174 }
176 }
175 }
177 }
@@ -1,559 +1,559 b''
1 # -*- coding: utf-8 -*-
1 # -*- coding: utf-8 -*-
2
2
3 # Copyright (C) 2011-2019 RhodeCode GmbH
3 # Copyright (C) 2011-2019 RhodeCode GmbH
4 #
4 #
5 # This program is free software: you can redistribute it and/or modify
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
6 # it under the terms of the GNU Affero General Public License, version 3
7 # (only), as published by the Free Software Foundation.
7 # (only), as published by the Free Software Foundation.
8 #
8 #
9 # This program is distributed in the hope that it will be useful,
9 # This program is distributed in the hope that it will be useful,
10 # but WITHOUT ANY WARRANTY; without even the implied warranty of
10 # but WITHOUT ANY WARRANTY; without even the implied warranty of
11 # MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
11 # MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
12 # GNU General Public License for more details.
12 # GNU General Public License for more details.
13 #
13 #
14 # You should have received a copy of the GNU Affero General Public License
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/>.
15 # along with this program. If not, see <http://www.gnu.org/licenses/>.
16 #
16 #
17 # This program is dual-licensed. If you wish to learn more about the
17 # This program is dual-licensed. If you wish to learn more about the
18 # RhodeCode Enterprise Edition, including its added features, Support services,
18 # RhodeCode Enterprise Edition, including its added features, Support services,
19 # and proprietary license terms, please see https://rhodecode.com/licenses/
19 # and proprietary license terms, please see https://rhodecode.com/licenses/
20
20
21
21
22 """
22 """
23 Renderer for markup languages with ability to parse using rst or markdown
23 Renderer for markup languages with ability to parse using rst or markdown
24 """
24 """
25
25
26 import re
26 import re
27 import os
27 import os
28 import lxml
28 import lxml
29 import logging
29 import logging
30 import urlparse
30 import urlparse
31 import bleach
31 import bleach
32
32
33 from mako.lookup import TemplateLookup
33 from mako.lookup import TemplateLookup
34 from mako.template import Template as MakoTemplate
34 from mako.template import Template as MakoTemplate
35
35
36 from docutils.core import publish_parts
36 from docutils.core import publish_parts
37 from docutils.parsers.rst import directives
37 from docutils.parsers.rst import directives
38 from docutils import writers
38 from docutils import writers
39 from docutils.writers import html4css1
39 from docutils.writers import html4css1
40 import markdown
40 import markdown
41
41
42 from rhodecode.lib.markdown_ext import GithubFlavoredMarkdownExtension
42 from rhodecode.lib.markdown_ext import GithubFlavoredMarkdownExtension
43 from rhodecode.lib.utils2 import (safe_unicode, md5_safe, MENTIONS_REGEX)
43 from rhodecode.lib.utils2 import (safe_unicode, md5_safe, MENTIONS_REGEX)
44
44
45 log = logging.getLogger(__name__)
45 log = logging.getLogger(__name__)
46
46
47 # default renderer used to generate automated comments
47 # default renderer used to generate automated comments
48 DEFAULT_COMMENTS_RENDERER = 'rst'
48 DEFAULT_COMMENTS_RENDERER = 'rst'
49
49
50
50
51 class CustomHTMLTranslator(writers.html4css1.HTMLTranslator):
51 class CustomHTMLTranslator(writers.html4css1.HTMLTranslator):
52 """
52 """
53 Custom HTML Translator used for sandboxing potential
53 Custom HTML Translator used for sandboxing potential
54 JS injections in ref links
54 JS injections in ref links
55 """
55 """
56
56
57 def visit_reference(self, node):
57 def visit_reference(self, node):
58 if 'refuri' in node.attributes:
58 if 'refuri' in node.attributes:
59 refuri = node['refuri']
59 refuri = node['refuri']
60 if ':' in refuri:
60 if ':' in refuri:
61 prefix, link = refuri.lstrip().split(':', 1)
61 prefix, link = refuri.lstrip().split(':', 1)
62 prefix = prefix or ''
62 prefix = prefix or ''
63
63
64 if prefix.lower() == 'javascript':
64 if prefix.lower() == 'javascript':
65 # we don't allow javascript type of refs...
65 # we don't allow javascript type of refs...
66 node['refuri'] = 'javascript:alert("SandBoxedJavascript")'
66 node['refuri'] = 'javascript:alert("SandBoxedJavascript")'
67
67
68 # old style class requires this...
68 # old style class requires this...
69 return html4css1.HTMLTranslator.visit_reference(self, node)
69 return html4css1.HTMLTranslator.visit_reference(self, node)
70
70
71
71
72 class RhodeCodeWriter(writers.html4css1.Writer):
72 class RhodeCodeWriter(writers.html4css1.Writer):
73 def __init__(self):
73 def __init__(self):
74 writers.Writer.__init__(self)
74 writers.Writer.__init__(self)
75 self.translator_class = CustomHTMLTranslator
75 self.translator_class = CustomHTMLTranslator
76
76
77
77
78 def relative_links(html_source, server_paths):
78 def relative_links(html_source, server_paths):
79 if not html_source:
79 if not html_source:
80 return html_source
80 return html_source
81
81
82 try:
82 try:
83 from lxml.html import fromstring
83 from lxml.html import fromstring
84 from lxml.html import tostring
84 from lxml.html import tostring
85 except ImportError:
85 except ImportError:
86 log.exception('Failed to import lxml')
86 log.exception('Failed to import lxml')
87 return html_source
87 return html_source
88
88
89 try:
89 try:
90 doc = lxml.html.fromstring(html_source)
90 doc = lxml.html.fromstring(html_source)
91 except Exception:
91 except Exception:
92 return html_source
92 return html_source
93
93
94 for el in doc.cssselect('img, video'):
94 for el in doc.cssselect('img, video'):
95 src = el.attrib.get('src')
95 src = el.attrib.get('src')
96 if src:
96 if src:
97 el.attrib['src'] = relative_path(src, server_paths['raw'])
97 el.attrib['src'] = relative_path(src, server_paths['raw'])
98
98
99 for el in doc.cssselect('a:not(.gfm)'):
99 for el in doc.cssselect('a:not(.gfm)'):
100 src = el.attrib.get('href')
100 src = el.attrib.get('href')
101 if src:
101 if src:
102 raw_mode = el.attrib['href'].endswith('?raw=1')
102 raw_mode = el.attrib['href'].endswith('?raw=1')
103 if raw_mode:
103 if raw_mode:
104 el.attrib['href'] = relative_path(src, server_paths['raw'])
104 el.attrib['href'] = relative_path(src, server_paths['raw'])
105 else:
105 else:
106 el.attrib['href'] = relative_path(src, server_paths['standard'])
106 el.attrib['href'] = relative_path(src, server_paths['standard'])
107
107
108 return lxml.html.tostring(doc)
108 return lxml.html.tostring(doc)
109
109
110
110
111 def relative_path(path, request_path, is_repo_file=None):
111 def relative_path(path, request_path, is_repo_file=None):
112 """
112 """
113 relative link support, path is a rel path, and request_path is current
113 relative link support, path is a rel path, and request_path is current
114 server path (not absolute)
114 server path (not absolute)
115
115
116 e.g.
116 e.g.
117
117
118 path = '../logo.png'
118 path = '../logo.png'
119 request_path= '/repo/files/path/file.md'
119 request_path= '/repo/files/path/file.md'
120 produces: '/repo/files/logo.png'
120 produces: '/repo/files/logo.png'
121 """
121 """
122 # TODO(marcink): unicode/str support ?
122 # TODO(marcink): unicode/str support ?
123 # maybe=> safe_unicode(urllib.quote(safe_str(final_path), '/:'))
123 # maybe=> safe_unicode(urllib.quote(safe_str(final_path), '/:'))
124
124
125 def dummy_check(p):
125 def dummy_check(p):
126 return True # assume default is a valid file path
126 return True # assume default is a valid file path
127
127
128 is_repo_file = is_repo_file or dummy_check
128 is_repo_file = is_repo_file or dummy_check
129 if not path:
129 if not path:
130 return request_path
130 return request_path
131
131
132 path = safe_unicode(path)
132 path = safe_unicode(path)
133 request_path = safe_unicode(request_path)
133 request_path = safe_unicode(request_path)
134
134
135 if path.startswith((u'data:', u'javascript:', u'#', u':')):
135 if path.startswith((u'data:', u'javascript:', u'#', u':')):
136 # skip data, anchor, invalid links
136 # skip data, anchor, invalid links
137 return path
137 return path
138
138
139 is_absolute = bool(urlparse.urlparse(path).netloc)
139 is_absolute = bool(urlparse.urlparse(path).netloc)
140 if is_absolute:
140 if is_absolute:
141 return path
141 return path
142
142
143 if not request_path:
143 if not request_path:
144 return path
144 return path
145
145
146 if path.startswith(u'/'):
146 if path.startswith(u'/'):
147 path = path[1:]
147 path = path[1:]
148
148
149 if path.startswith(u'./'):
149 if path.startswith(u'./'):
150 path = path[2:]
150 path = path[2:]
151
151
152 parts = request_path.split('/')
152 parts = request_path.split('/')
153 # compute how deep we need to traverse the request_path
153 # compute how deep we need to traverse the request_path
154 depth = 0
154 depth = 0
155
155
156 if is_repo_file(request_path):
156 if is_repo_file(request_path):
157 # if request path is a VALID file, we use a relative path with
157 # if request path is a VALID file, we use a relative path with
158 # one level up
158 # one level up
159 depth += 1
159 depth += 1
160
160
161 while path.startswith(u'../'):
161 while path.startswith(u'../'):
162 depth += 1
162 depth += 1
163 path = path[3:]
163 path = path[3:]
164
164
165 if depth > 0:
165 if depth > 0:
166 parts = parts[:-depth]
166 parts = parts[:-depth]
167
167
168 parts.append(path)
168 parts.append(path)
169 final_path = u'/'.join(parts).lstrip(u'/')
169 final_path = u'/'.join(parts).lstrip(u'/')
170
170
171 return u'/' + final_path
171 return u'/' + final_path
172
172
173
173
174 _cached_markdown_renderer = None
174 _cached_markdown_renderer = None
175
175
176
176
177 def get_markdown_renderer(extensions, output_format):
177 def get_markdown_renderer(extensions, output_format):
178 global _cached_markdown_renderer
178 global _cached_markdown_renderer
179
179
180 if _cached_markdown_renderer is None:
180 if _cached_markdown_renderer is None:
181 _cached_markdown_renderer = markdown.Markdown(
181 _cached_markdown_renderer = markdown.Markdown(
182 extensions=extensions,
182 extensions=extensions,
183 enable_attributes=False, output_format=output_format)
183 enable_attributes=False, output_format=output_format)
184 return _cached_markdown_renderer
184 return _cached_markdown_renderer
185
185
186
186
187 _cached_markdown_renderer_flavored = None
187 _cached_markdown_renderer_flavored = None
188
188
189
189
190 def get_markdown_renderer_flavored(extensions, output_format):
190 def get_markdown_renderer_flavored(extensions, output_format):
191 global _cached_markdown_renderer_flavored
191 global _cached_markdown_renderer_flavored
192
192
193 if _cached_markdown_renderer_flavored is None:
193 if _cached_markdown_renderer_flavored is None:
194 _cached_markdown_renderer_flavored = markdown.Markdown(
194 _cached_markdown_renderer_flavored = markdown.Markdown(
195 extensions=extensions + [GithubFlavoredMarkdownExtension()],
195 extensions=extensions + [GithubFlavoredMarkdownExtension()],
196 enable_attributes=False, output_format=output_format)
196 enable_attributes=False, output_format=output_format)
197 return _cached_markdown_renderer_flavored
197 return _cached_markdown_renderer_flavored
198
198
199
199
200 class MarkupRenderer(object):
200 class MarkupRenderer(object):
201 RESTRUCTUREDTEXT_DISALLOWED_DIRECTIVES = ['include', 'meta', 'raw']
201 RESTRUCTUREDTEXT_DISALLOWED_DIRECTIVES = ['include', 'meta', 'raw']
202
202
203 MARKDOWN_PAT = re.compile(r'\.(md|mkdn?|mdown|markdown)$', re.IGNORECASE)
203 MARKDOWN_PAT = re.compile(r'\.(md|mkdn?|mdown|markdown)$', re.IGNORECASE)
204 RST_PAT = re.compile(r'\.re?st$', re.IGNORECASE)
204 RST_PAT = re.compile(r'\.re?st$', re.IGNORECASE)
205 JUPYTER_PAT = re.compile(r'\.(ipynb)$', re.IGNORECASE)
205 JUPYTER_PAT = re.compile(r'\.(ipynb)$', re.IGNORECASE)
206 PLAIN_PAT = re.compile(r'^readme$', re.IGNORECASE)
206 PLAIN_PAT = re.compile(r'^readme$', re.IGNORECASE)
207
207
208 URL_PAT = re.compile(r'(http[s]?://(?:[a-zA-Z]|[0-9]|[$-_@.&+]'
208 URL_PAT = re.compile(r'(http[s]?://(?:[a-zA-Z]|[0-9]|[$-_@.&+]'
209 r'|[!*\(\),]|(?:%[0-9a-fA-F][0-9a-fA-F]))+)')
209 r'|[!*\(\),]|(?:%[0-9a-fA-F][0-9a-fA-F]))+)')
210
210
211 extensions = ['markdown.extensions.codehilite', 'markdown.extensions.extra',
211 extensions = ['markdown.extensions.codehilite', 'markdown.extensions.extra',
212 'markdown.extensions.def_list', 'markdown.extensions.sane_lists']
212 'markdown.extensions.def_list', 'markdown.extensions.sane_lists']
213
213
214 output_format = 'html4'
214 output_format = 'html4'
215
215
216 # extension together with weights. Lower is first means we control how
216 # extension together with weights. Lower is first means we control how
217 # extensions are attached to readme names with those.
217 # extensions are attached to readme names with those.
218 PLAIN_EXTS = [
218 PLAIN_EXTS = [
219 # prefer no extension
219 # prefer no extension
220 ('', 0), # special case that renders READMES names without extension
220 ('', 0), # special case that renders READMES names without extension
221 ('.text', 2), ('.TEXT', 2),
221 ('.text', 2), ('.TEXT', 2),
222 ('.txt', 3), ('.TXT', 3)
222 ('.txt', 3), ('.TXT', 3)
223 ]
223 ]
224
224
225 RST_EXTS = [
225 RST_EXTS = [
226 ('.rst', 1), ('.rest', 1),
226 ('.rst', 1), ('.rest', 1),
227 ('.RST', 2), ('.REST', 2)
227 ('.RST', 2), ('.REST', 2)
228 ]
228 ]
229
229
230 MARKDOWN_EXTS = [
230 MARKDOWN_EXTS = [
231 ('.md', 1), ('.MD', 1),
231 ('.md', 1), ('.MD', 1),
232 ('.mkdn', 2), ('.MKDN', 2),
232 ('.mkdn', 2), ('.MKDN', 2),
233 ('.mdown', 3), ('.MDOWN', 3),
233 ('.mdown', 3), ('.MDOWN', 3),
234 ('.markdown', 4), ('.MARKDOWN', 4)
234 ('.markdown', 4), ('.MARKDOWN', 4)
235 ]
235 ]
236
236
237 def _detect_renderer(self, source, filename=None):
237 def _detect_renderer(self, source, filename=None):
238 """
238 """
239 runs detection of what renderer should be used for generating html
239 runs detection of what renderer should be used for generating html
240 from a markup language
240 from a markup language
241
241
242 filename can be also explicitly a renderer name
242 filename can be also explicitly a renderer name
243
243
244 :param source:
244 :param source:
245 :param filename:
245 :param filename:
246 """
246 """
247
247
248 if MarkupRenderer.MARKDOWN_PAT.findall(filename):
248 if MarkupRenderer.MARKDOWN_PAT.findall(filename):
249 detected_renderer = 'markdown'
249 detected_renderer = 'markdown'
250 elif MarkupRenderer.RST_PAT.findall(filename):
250 elif MarkupRenderer.RST_PAT.findall(filename):
251 detected_renderer = 'rst'
251 detected_renderer = 'rst'
252 elif MarkupRenderer.JUPYTER_PAT.findall(filename):
252 elif MarkupRenderer.JUPYTER_PAT.findall(filename):
253 detected_renderer = 'jupyter'
253 detected_renderer = 'jupyter'
254 elif MarkupRenderer.PLAIN_PAT.findall(filename):
254 elif MarkupRenderer.PLAIN_PAT.findall(filename):
255 detected_renderer = 'plain'
255 detected_renderer = 'plain'
256 else:
256 else:
257 detected_renderer = 'plain'
257 detected_renderer = 'plain'
258
258
259 return getattr(MarkupRenderer, detected_renderer)
259 return getattr(MarkupRenderer, detected_renderer)
260
260
261 @classmethod
261 @classmethod
262 def bleach_clean(cls, text):
262 def bleach_clean(cls, text):
263 from .bleach_whitelist import markdown_attrs, markdown_tags
263 from .bleach_whitelist import markdown_attrs, markdown_tags
264 allowed_tags = markdown_tags
264 allowed_tags = markdown_tags
265 allowed_attrs = markdown_attrs
265 allowed_attrs = markdown_attrs
266
266
267 try:
267 try:
268 return bleach.clean(text, tags=allowed_tags, attributes=allowed_attrs)
268 return bleach.clean(text, tags=allowed_tags, attributes=allowed_attrs)
269 except Exception:
269 except Exception:
270 return 'UNPARSEABLE TEXT'
270 return 'UNPARSEABLE TEXT'
271
271
272 @classmethod
272 @classmethod
273 def renderer_from_filename(cls, filename, exclude):
273 def renderer_from_filename(cls, filename, exclude):
274 """
274 """
275 Detect renderer markdown/rst from filename and optionally use exclude
275 Detect renderer markdown/rst from filename and optionally use exclude
276 list to remove some options. This is mostly used in helpers.
276 list to remove some options. This is mostly used in helpers.
277 Returns None when no renderer can be detected.
277 Returns None when no renderer can be detected.
278 """
278 """
279 def _filter(elements):
279 def _filter(elements):
280 if isinstance(exclude, (list, tuple)):
280 if isinstance(exclude, (list, tuple)):
281 return [x for x in elements if x not in exclude]
281 return [x for x in elements if x not in exclude]
282 return elements
282 return elements
283
283
284 if filename.endswith(
284 if filename.endswith(
285 tuple(_filter([x[0] for x in cls.MARKDOWN_EXTS if x[0]]))):
285 tuple(_filter([x[0] for x in cls.MARKDOWN_EXTS if x[0]]))):
286 return 'markdown'
286 return 'markdown'
287 if filename.endswith(tuple(_filter([x[0] for x in cls.RST_EXTS if x[0]]))):
287 if filename.endswith(tuple(_filter([x[0] for x in cls.RST_EXTS if x[0]]))):
288 return 'rst'
288 return 'rst'
289
289
290 return None
290 return None
291
291
292 def render(self, source, filename=None):
292 def render(self, source, filename=None):
293 """
293 """
294 Renders a given filename using detected renderer
294 Renders a given filename using detected renderer
295 it detects renderers based on file extension or mimetype.
295 it detects renderers based on file extension or mimetype.
296 At last it will just do a simple html replacing new lines with <br/>
296 At last it will just do a simple html replacing new lines with <br/>
297
297
298 :param file_name:
298 :param file_name:
299 :param source:
299 :param source:
300 """
300 """
301
301
302 renderer = self._detect_renderer(source, filename)
302 renderer = self._detect_renderer(source, filename)
303 readme_data = renderer(source)
303 readme_data = renderer(source)
304 return readme_data
304 return readme_data
305
305
306 @classmethod
306 @classmethod
307 def _flavored_markdown(cls, text):
307 def _flavored_markdown(cls, text):
308 """
308 """
309 Github style flavored markdown
309 Github style flavored markdown
310
310
311 :param text:
311 :param text:
312 """
312 """
313
313
314 # Extract pre blocks.
314 # Extract pre blocks.
315 extractions = {}
315 extractions = {}
316
316
317 def pre_extraction_callback(matchobj):
317 def pre_extraction_callback(matchobj):
318 digest = md5_safe(matchobj.group(0))
318 digest = md5_safe(matchobj.group(0))
319 extractions[digest] = matchobj.group(0)
319 extractions[digest] = matchobj.group(0)
320 return "{gfm-extraction-%s}" % digest
320 return "{gfm-extraction-%s}" % digest
321 pattern = re.compile(r'<pre>.*?</pre>', re.MULTILINE | re.DOTALL)
321 pattern = re.compile(r'<pre>.*?</pre>', re.MULTILINE | re.DOTALL)
322 text = re.sub(pattern, pre_extraction_callback, text)
322 text = re.sub(pattern, pre_extraction_callback, text)
323
323
324 # Prevent foo_bar_baz from ending up with an italic word in the middle.
324 # Prevent foo_bar_baz from ending up with an italic word in the middle.
325 def italic_callback(matchobj):
325 def italic_callback(matchobj):
326 s = matchobj.group(0)
326 s = matchobj.group(0)
327 if list(s).count('_') >= 2:
327 if list(s).count('_') >= 2:
328 return s.replace('_', r'\_')
328 return s.replace('_', r'\_')
329 return s
329 return s
330 text = re.sub(r'^(?! {4}|\t)\w+_\w+_\w[\w_]*', italic_callback, text)
330 text = re.sub(r'^(?! {4}|\t)\w+_\w+_\w[\w_]*', italic_callback, text)
331
331
332 # Insert pre block extractions.
332 # Insert pre block extractions.
333 def pre_insert_callback(matchobj):
333 def pre_insert_callback(matchobj):
334 return '\n\n' + extractions[matchobj.group(1)]
334 return '\n\n' + extractions[matchobj.group(1)]
335 text = re.sub(r'\{gfm-extraction-([0-9a-f]{32})\}',
335 text = re.sub(r'\{gfm-extraction-([0-9a-f]{32})\}',
336 pre_insert_callback, text)
336 pre_insert_callback, text)
337
337
338 return text
338 return text
339
339
340 @classmethod
340 @classmethod
341 def urlify_text(cls, text):
341 def urlify_text(cls, text):
342 def url_func(match_obj):
342 def url_func(match_obj):
343 url_full = match_obj.groups()[0]
343 url_full = match_obj.groups()[0]
344 return '<a href="%(url)s">%(url)s</a>' % ({'url': url_full})
344 return '<a href="%(url)s">%(url)s</a>' % ({'url': url_full})
345
345
346 return cls.URL_PAT.sub(url_func, text)
346 return cls.URL_PAT.sub(url_func, text)
347
347
348 @classmethod
348 @classmethod
349 def plain(cls, source, universal_newline=True, leading_newline=True):
349 def plain(cls, source, universal_newline=True, leading_newline=True):
350 source = safe_unicode(source)
350 source = safe_unicode(source)
351 if universal_newline:
351 if universal_newline:
352 newline = '\n'
352 newline = '\n'
353 source = newline.join(source.splitlines())
353 source = newline.join(source.splitlines())
354
354
355 rendered_source = cls.urlify_text(source)
355 rendered_source = cls.urlify_text(source)
356 source = ''
356 source = ''
357 if leading_newline:
357 if leading_newline:
358 source += '<br />'
358 source += '<br />'
359 source += rendered_source.replace("\n", '<br />')
359 source += rendered_source.replace("\n", '<br />')
360
360
361 rendered = cls.bleach_clean(source)
361 rendered = cls.bleach_clean(source)
362 return rendered
362 return rendered
363
363
364 @classmethod
364 @classmethod
365 def markdown(cls, source, safe=True, flavored=True, mentions=False,
365 def markdown(cls, source, safe=True, flavored=True, mentions=False,
366 clean_html=True):
366 clean_html=True):
367 """
367 """
368 returns markdown rendered code cleaned by the bleach library
368 returns markdown rendered code cleaned by the bleach library
369 """
369 """
370
370
371 if flavored:
371 if flavored:
372 markdown_renderer = get_markdown_renderer_flavored(
372 markdown_renderer = get_markdown_renderer_flavored(
373 cls.extensions, cls.output_format)
373 cls.extensions, cls.output_format)
374 else:
374 else:
375 markdown_renderer = get_markdown_renderer(
375 markdown_renderer = get_markdown_renderer(
376 cls.extensions, cls.output_format)
376 cls.extensions, cls.output_format)
377
377
378 if mentions:
378 if mentions:
379 mention_pat = re.compile(MENTIONS_REGEX)
379 mention_pat = re.compile(MENTIONS_REGEX)
380
380
381 def wrapp(match_obj):
381 def wrapp(match_obj):
382 uname = match_obj.groups()[0]
382 uname = match_obj.groups()[0]
383 return ' **@%(uname)s** ' % {'uname': uname}
383 return ' **@%(uname)s** ' % {'uname': uname}
384 mention_hl = mention_pat.sub(wrapp, source).strip()
384 mention_hl = mention_pat.sub(wrapp, source).strip()
385 # we extracted mentions render with this using Mentions false
385 # we extracted mentions render with this using Mentions false
386 return cls.markdown(mention_hl, safe=safe, flavored=flavored,
386 return cls.markdown(mention_hl, safe=safe, flavored=flavored,
387 mentions=False)
387 mentions=False)
388
388
389 source = safe_unicode(source)
389 source = safe_unicode(source)
390
390
391 try:
391 try:
392 if flavored:
392 if flavored:
393 source = cls._flavored_markdown(source)
393 source = cls._flavored_markdown(source)
394 rendered = markdown_renderer.convert(source)
394 rendered = markdown_renderer.convert(source)
395 except Exception:
395 except Exception:
396 log.exception('Error when rendering Markdown')
396 log.exception('Error when rendering Markdown')
397 if safe:
397 if safe:
398 log.debug('Fallback to render in plain mode')
398 log.debug('Fallback to render in plain mode')
399 rendered = cls.plain(source)
399 rendered = cls.plain(source)
400 else:
400 else:
401 raise
401 raise
402
402
403 if clean_html:
403 if clean_html:
404 rendered = cls.bleach_clean(rendered)
404 rendered = cls.bleach_clean(rendered)
405 return rendered
405 return rendered
406
406
407 @classmethod
407 @classmethod
408 def rst(cls, source, safe=True, mentions=False, clean_html=False):
408 def rst(cls, source, safe=True, mentions=False, clean_html=False):
409 if mentions:
409 if mentions:
410 mention_pat = re.compile(MENTIONS_REGEX)
410 mention_pat = re.compile(MENTIONS_REGEX)
411
411
412 def wrapp(match_obj):
412 def wrapp(match_obj):
413 uname = match_obj.groups()[0]
413 uname = match_obj.groups()[0]
414 return ' **@%(uname)s** ' % {'uname': uname}
414 return ' **@%(uname)s** ' % {'uname': uname}
415 mention_hl = mention_pat.sub(wrapp, source).strip()
415 mention_hl = mention_pat.sub(wrapp, source).strip()
416 # we extracted mentions render with this using Mentions false
416 # we extracted mentions render with this using Mentions false
417 return cls.rst(mention_hl, safe=safe, mentions=False)
417 return cls.rst(mention_hl, safe=safe, mentions=False)
418
418
419 source = safe_unicode(source)
419 source = safe_unicode(source)
420 try:
420 try:
421 docutils_settings = dict(
421 docutils_settings = dict(
422 [(alias, None) for alias in
422 [(alias, None) for alias in
423 cls.RESTRUCTUREDTEXT_DISALLOWED_DIRECTIVES])
423 cls.RESTRUCTUREDTEXT_DISALLOWED_DIRECTIVES])
424
424
425 docutils_settings.update({
425 docutils_settings.update({
426 'input_encoding': 'unicode', 'report_level': 4})
426 'input_encoding': 'unicode', 'report_level': 4})
427
427
428 for k, v in docutils_settings.iteritems():
428 for k, v in docutils_settings.iteritems():
429 directives.register_directive(k, v)
429 directives.register_directive(k, v)
430
430
431 parts = publish_parts(source=source,
431 parts = publish_parts(source=source,
432 writer=RhodeCodeWriter(),
432 writer=RhodeCodeWriter(),
433 settings_overrides=docutils_settings)
433 settings_overrides=docutils_settings)
434 rendered = parts["fragment"]
434 rendered = parts["fragment"]
435 if clean_html:
435 if clean_html:
436 rendered = cls.bleach_clean(rendered)
436 rendered = cls.bleach_clean(rendered)
437 return parts['html_title'] + rendered
437 return parts['html_title'] + rendered
438 except Exception:
438 except Exception:
439 log.exception('Error when rendering RST')
439 log.exception('Error when rendering RST')
440 if safe:
440 if safe:
441 log.debug('Fallbacking to render in plain mode')
441 log.debug('Fallbacking to render in plain mode')
442 return cls.plain(source)
442 return cls.plain(source)
443 else:
443 else:
444 raise
444 raise
445
445
446 @classmethod
446 @classmethod
447 def jupyter(cls, source, safe=True):
447 def jupyter(cls, source, safe=True):
448 from rhodecode.lib import helpers
448 from rhodecode.lib import helpers
449
449
450 from traitlets.config import Config
450 from traitlets.config import Config
451 import nbformat
451 import nbformat
452 from nbconvert import HTMLExporter
452 from nbconvert import HTMLExporter
453 from nbconvert.preprocessors import Preprocessor
453 from nbconvert.preprocessors import Preprocessor
454
454
455 class CustomHTMLExporter(HTMLExporter):
455 class CustomHTMLExporter(HTMLExporter):
456 def _template_file_default(self):
456 def _template_file_default(self):
457 return 'basic'
457 return 'basic'
458
458
459 class Sandbox(Preprocessor):
459 class Sandbox(Preprocessor):
460
460
461 def preprocess(self, nb, resources):
461 def preprocess(self, nb, resources):
462 sandbox_text = 'SandBoxed(IPython.core.display.Javascript object)'
462 sandbox_text = 'SandBoxed(IPython.core.display.Javascript object)'
463 for cell in nb['cells']:
463 for cell in nb['cells']:
464 if not safe:
464 if not safe:
465 continue
465 continue
466
466
467 if 'outputs' in cell:
467 if 'outputs' in cell:
468 for cell_output in cell['outputs']:
468 for cell_output in cell['outputs']:
469 if 'data' in cell_output:
469 if 'data' in cell_output:
470 if 'application/javascript' in cell_output['data']:
470 if 'application/javascript' in cell_output['data']:
471 cell_output['data']['text/plain'] = sandbox_text
471 cell_output['data']['text/plain'] = sandbox_text
472 cell_output['data'].pop('application/javascript', None)
472 cell_output['data'].pop('application/javascript', None)
473
473
474 if 'source' in cell and cell['cell_type'] == 'markdown':
474 if 'source' in cell and cell['cell_type'] == 'markdown':
475 # sanitize similar like in markdown
475 # sanitize similar like in markdown
476 cell['source'] = cls.bleach_clean(cell['source'])
476 cell['source'] = cls.bleach_clean(cell['source'])
477
477
478 return nb, resources
478 return nb, resources
479
479
480 def _sanitize_resources(input_resources):
480 def _sanitize_resources(input_resources):
481 """
481 """
482 Skip/sanitize some of the CSS generated and included in jupyter
482 Skip/sanitize some of the CSS generated and included in jupyter
483 so it doesn't messes up UI so much
483 so it doesn't messes up UI so much
484 """
484 """
485
485
486 # TODO(marcink): probably we should replace this with whole custom
486 # TODO(marcink): probably we should replace this with whole custom
487 # CSS set that doesn't screw up, but jupyter generated html has some
487 # CSS set that doesn't screw up, but jupyter generated html has some
488 # special markers, so it requires Custom HTML exporter template with
488 # special markers, so it requires Custom HTML exporter template with
489 # _default_template_path_default, to achieve that
489 # _default_template_path_default, to achieve that
490
490
491 # strip the reset CSS
491 # strip the reset CSS
492 input_resources[0] = input_resources[0][input_resources[0].find('/*! Source'):]
492 input_resources[0] = input_resources[0][input_resources[0].find('/*! Source'):]
493 return input_resources
493 return input_resources
494
494
495 def as_html(notebook):
495 def as_html(notebook):
496 conf = Config()
496 conf = Config()
497 conf.CustomHTMLExporter.preprocessors = [Sandbox]
497 conf.CustomHTMLExporter.preprocessors = [Sandbox]
498 html_exporter = CustomHTMLExporter(config=conf)
498 html_exporter = CustomHTMLExporter(config=conf)
499
499
500 (body, resources) = html_exporter.from_notebook_node(notebook)
500 (body, resources) = html_exporter.from_notebook_node(notebook)
501 header = '<!-- ## IPYTHON NOTEBOOK RENDERING ## -->'
501 header = '<!-- ## IPYTHON NOTEBOOK RENDERING ## -->'
502 js = MakoTemplate(r'''
502 js = MakoTemplate(r'''
503 <!-- Load mathjax -->
503 <!-- MathJax configuration -->
504 <!-- MathJax configuration -->
504 <script type="text/x-mathjax-config">
505 <script type="text/x-mathjax-config">
505 MathJax.Hub.Config({
506 MathJax.Hub.Config({
506 jax: ["input/TeX","output/HTML-CSS", "output/PreviewHTML"],
507 jax: ["input/TeX","output/HTML-CSS", "output/PreviewHTML"],
507 extensions: ["tex2jax.js","MathMenu.js","MathZoom.js", "fast-preview.js", "AssistiveMML.js", "[Contrib]/a11y/accessibility-menu.js"],
508 extensions: ["tex2jax.js","MathMenu.js","MathZoom.js", "fast-preview.js", "AssistiveMML.js", "[Contrib]/a11y/accessibility-menu.js"],
508 TeX: {
509 TeX: {
509 extensions: ["AMSmath.js","AMSsymbols.js","noErrors.js","noUndefined.js"]
510 extensions: ["AMSmath.js","AMSsymbols.js","noErrors.js","noUndefined.js"]
510 },
511 },
511 tex2jax: {
512 tex2jax: {
512 inlineMath: [ ['$','$'], ["\\(","\\)"] ],
513 inlineMath: [ ['$','$'], ["\\(","\\)"] ],
513 displayMath: [ ['$$','$$'], ["\\[","\\]"] ],
514 displayMath: [ ['$$','$$'], ["\\[","\\]"] ],
514 processEscapes: true,
515 processEscapes: true,
515 processEnvironments: true
516 processEnvironments: true
516 },
517 },
517 // Center justify equations in code and markdown cells. Elsewhere
518 // Center justify equations in code and markdown cells. Elsewhere
518 // we use CSS to left justify single line equations in code cells.
519 // we use CSS to left justify single line equations in code cells.
519 displayAlign: 'center',
520 displayAlign: 'center',
520 "HTML-CSS": {
521 "HTML-CSS": {
521 styles: {'.MathJax_Display': {"margin": 0}},
522 styles: {'.MathJax_Display': {"margin": 0}},
522 linebreaks: { automatic: true },
523 linebreaks: { automatic: true },
523 availableFonts: ["STIX", "TeX"]
524 availableFonts: ["STIX", "TeX"]
524 },
525 },
525 showMathMenu: false
526 showMathMenu: false
526 });
527 });
527 </script>
528 </script>
528 <!-- End of MathJax configuration -->
529 <!-- End of mathjax configuration -->
529 <script src="${h.asset('js/src/math_jax/MathJax.js')}"></script>
530 <script src="${h.asset('js/src/math_jax/MathJax.js')}"></script>
531 ''').render(h=helpers)
530 ''').render(h=helpers)
532
531
533 css = '<style>{}</style>'.format(
532 css = MakoTemplate(r'''
534 ''.join(_sanitize_resources(resources['inlining']['css'])))
533 <link rel="stylesheet" type="text/css" href="${h.asset('css/style-ipython.css', ver=ver)}" media="screen"/>
534 ''').render(h=helpers, ver='ver1')
535
535
536 body = '\n'.join([header, css, js, body])
536 body = '\n'.join([header, css, js, body])
537 return body, resources
537 return body, resources
538
538
539 notebook = nbformat.reads(source, as_version=4)
539 notebook = nbformat.reads(source, as_version=4)
540 (body, resources) = as_html(notebook)
540 (body, resources) = as_html(notebook)
541 return body
541 return body
542
542
543
543
544 class RstTemplateRenderer(object):
544 class RstTemplateRenderer(object):
545
545
546 def __init__(self):
546 def __init__(self):
547 base = os.path.abspath(os.path.dirname(os.path.dirname(__file__)))
547 base = os.path.abspath(os.path.dirname(os.path.dirname(__file__)))
548 rst_template_dirs = [os.path.join(base, 'templates', 'rst_templates')]
548 rst_template_dirs = [os.path.join(base, 'templates', 'rst_templates')]
549 self.template_store = TemplateLookup(
549 self.template_store = TemplateLookup(
550 directories=rst_template_dirs,
550 directories=rst_template_dirs,
551 input_encoding='utf-8',
551 input_encoding='utf-8',
552 imports=['from rhodecode.lib import helpers as h'])
552 imports=['from rhodecode.lib import helpers as h'])
553
553
554 def _get_template(self, templatename):
554 def _get_template(self, templatename):
555 return self.template_store.get_template(templatename)
555 return self.template_store.get_template(templatename)
556
556
557 def render(self, template_name, **kwargs):
557 def render(self, template_name, **kwargs):
558 template = self._get_template(template_name)
558 template = self._get_template(template_name)
559 return template.render(**kwargs)
559 return template.render(**kwargs)
General Comments 0
You need to be logged in to leave comments. Login now