Merge ~twom/launchpad:sassy-css-no-op-css-compilation into launchpad:master
- Git
- lp:~twom/launchpad
- sassy-css-no-op-css-compilation
- Merge into master
Proposed by
Tom Wardill
Status: | Merged |
---|---|
Approved by: | Tom Wardill |
Approved revision: | c0d06b882638c5151b0314906767d6bb27a899c7 |
Merge reported by: | Otto Co-Pilot |
Merged at revision: | not available |
Proposed branch: | ~twom/launchpad:sassy-css-no-op-css-compilation |
Merge into: | launchpad:master |
Prerequisite: | ~twom/launchpad:sassy-css-install-node-sass |
Diff against target: |
806 lines (+49/-519) 7 files modified
Makefile (+8/-2) dev/null (+0/-514) lib/canonical/launchpad/icing/combo.scss (+28/-0) lib/canonical/launchpad/icing/css/components/_index.scss (+8/-0) lib/lp/app/templates/base-layout-macros.pt (+1/-1) lib/lp/scripts/utilities/js/jsbuild.py (+4/-1) setup.py (+0/-1) |
Related bugs: |
Reviewer | Review Type | Date Requested | Status |
---|---|---|---|
Thiago F. Pappacena (community) | Approve | ||
Kristian Glass (community) | Approve | ||
Ioana Lasc (community) | Approve | ||
Review via email: mp+384706@code.launchpad.net |
This proposal supersedes a proposal from 2020-05-28.
Commit message
Move to node-sass for CSS concatenation
Description of the change
Use node-sass to combine and minify our CSS.
Also remove the combinecss file and entry point as we're not using it anymore.
To post a comment you must log in.
- f2baea6... by Tom Wardill
-
Add watch command for css building
- f5b0bf6... by Tom Wardill
-
Remove more combo code
- a916108... by Tom Wardill
-
Rewrite urls to the correct build location
Revision history for this message
Kristian Glass (doismellburning) wrote : | # |
Reads well to me - I've added a few minor quibbles/thoughts inline
review:
Approve
Revision history for this message
Thiago F. Pappacena (pappacena) wrote : | # |
Ran it locally, and it seems to work fine.
review:
Approve
- c0d06b8... by Tom Wardill
-
Comment and review tweaks
Preview Diff
[H/L] Next/Prev Comment, [J/K] Next/Prev File, [N/P] Next/Prev Hunk
1 | diff --git a/Makefile b/Makefile |
2 | index 1f01368..74f285e 100644 |
3 | --- a/Makefile |
4 | +++ b/Makefile |
5 | @@ -75,7 +75,6 @@ PIP_BIN = \ |
6 | $(PY) \ |
7 | bin/bingtestservice \ |
8 | bin/build-twisted-plugin-cache \ |
9 | - bin/combine-css \ |
10 | bin/harness \ |
11 | bin/iharness \ |
12 | bin/ipy \ |
13 | @@ -183,7 +182,14 @@ css_combine: jsbuild_widget_css |
14 | ${SHHH} bin/sprite-util create-image |
15 | ${SHHH} bin/sprite-util create-css |
16 | ln -sfn ../../../../yarn/node_modules/yui $(ICING)/yui |
17 | - ${SHHH} bin/combine-css |
18 | + SASS_BINARY_PATH=$(NODE_SASS_BINARY) $(YARN) run node-sass --output-style compressed --include-path $(WD)/$(ICING) --follow --output $(WD)/$(ICING) $(WD)/$(ICING)/combo.scss |
19 | + |
20 | +css_watch: jsbuild_widget_css |
21 | + ${SHHH} bin/sprite-util create-image |
22 | + ${SHHH} bin/sprite-util create-css |
23 | + ln -sfn ../../../../yarn/node_modules/yui $(ICING)/yui |
24 | + SASS_BINARY_PATH=$(NODE_SASS_BINARY) $(YARN) run node-sass --include-path $(WD)/$(ICING) --follow --output $(WD)/$(ICING) $(WD)/$(ICING)/ --watch --recursive |
25 | + |
26 | |
27 | jsbuild_widget_css: bin/jsbuild |
28 | ${SHHH} bin/jsbuild \ |
29 | diff --git a/lib/canonical/launchpad/icing/combo.scss b/lib/canonical/launchpad/icing/combo.scss |
30 | new file mode 100644 |
31 | index 0000000..62afbfb |
32 | --- /dev/null |
33 | +++ b/lib/canonical/launchpad/icing/combo.scss |
34 | @@ -0,0 +1,28 @@ |
35 | +@import 'css/base', |
36 | +'ubuntu-webfonts', |
37 | +'style', |
38 | +'yui/cssreset/cssreset', |
39 | +'yui/autocomplete-list/assets/skins/sam/autocomplete-list', |
40 | +'yui/calendar-base/assets/skins/sam/calendar-base', |
41 | +'yui/calendar/assets/skins/sam/calendar', |
42 | +'yui/calendarnavigator/assets/skins/sam/calendarnavigator', |
43 | +// Since the old cssgrids uses yui-, and the new uses yui3-, it is only |
44 | +// used for the calendar. |
45 | +'yui/cssgrids/cssgrids', |
46 | +// Use the old cssgrids instead of the new cssgrids. |
47 | +'cssgrids/grids', |
48 | +'build/launchpad-sam', |
49 | +'build/inline-sprites-1', |
50 | +'build/inline-sprites-2', |
51 | +'build/block-sprites-1', |
52 | +// Include our main stylesheets at the end so they |
53 | +// take precedence over the others. |
54 | +'css/base', |
55 | +'css/typography', |
56 | +'css/colours', |
57 | +'css/forms', |
58 | +'css/layout', |
59 | +'css/modifiers', |
60 | +// This shouldn't require the _index, but doesn't appear to be allowable |
61 | +// in this version of node-sass (v4.14.4) |
62 | +'css/components/_index'; |
63 | diff --git a/lib/canonical/launchpad/icing/css/components/_index.scss b/lib/canonical/launchpad/icing/css/components/_index.scss |
64 | new file mode 100644 |
65 | index 0000000..e1623c8 |
66 | --- /dev/null |
67 | +++ b/lib/canonical/launchpad/icing/css/components/_index.scss |
68 | @@ -0,0 +1,8 @@ |
69 | +@import 'batch_navigation', |
70 | + 'bug_picker', |
71 | + 'profiling_info', |
72 | + 'sidebar_portlets', |
73 | + 'bug_listing', |
74 | + 'portlets', |
75 | + 'sharing', |
76 | + 'yui_picker'; |
77 | diff --git a/lib/lp/app/templates/base-layout-macros.pt b/lib/lp/app/templates/base-layout-macros.pt |
78 | index 6d9be5d..938d47d 100644 |
79 | --- a/lib/lp/app/templates/base-layout-macros.pt |
80 | +++ b/lib/lp/app/templates/base-layout-macros.pt |
81 | @@ -190,7 +190,7 @@ |
82 | <tal:comment replace="nothing"> |
83 | This macro loads a single css file containing all our stylesheets. |
84 | If you need to include a new css file here, add it to |
85 | - lib/lp/scripts/utilities/js/combinecss.py instead. |
86 | + lib/canonical/launchpad/icing/combo.scss instead. |
87 | |
88 | We load the CSS from the same host that served the HTML in order to optimize |
89 | IE caching over SSL. This is inefficient when you cross subdomains (from |
90 | diff --git a/lib/lp/scripts/utilities/js/combinecss.py b/lib/lp/scripts/utilities/js/combinecss.py |
91 | deleted file mode 100755 |
92 | index 885d11c..0000000 |
93 | --- a/lib/lp/scripts/utilities/js/combinecss.py |
94 | +++ /dev/null |
95 | @@ -1,83 +0,0 @@ |
96 | -# Copyright 2009-2020 Canonical Ltd. This software is licensed under the |
97 | -# GNU Affero General Public License version 3 (see the file LICENSE). |
98 | - |
99 | -from __future__ import absolute_import, print_function, unicode_literals |
100 | - |
101 | -import os |
102 | - |
103 | -import scandir |
104 | - |
105 | -from lp.scripts.utilities.js.combo import combine_files |
106 | -from lp.scripts.utilities.js.jsbuild import ComboFile |
107 | -from lp.services.config import config |
108 | - |
109 | -# It'd probably be nice to have this script find all the CSS files we might |
110 | -# need and combine them together, but if we do that we'd certainly end up |
111 | -# including lots of styles that we don't need/want, so keeping this hard-coded |
112 | -# list seems like the best option for now. |
113 | -names = [ |
114 | - 'ubuntu-webfonts.css', |
115 | - 'style.css', |
116 | - 'yui/cssreset/cssreset.css', |
117 | - 'yui/autocomplete-list/assets/skins/sam/autocomplete-list.css', |
118 | - 'yui/calendar-base/assets/skins/sam/calendar-base.css', |
119 | - 'yui/calendar/assets/skins/sam/calendar.css', |
120 | - 'yui/calendarnavigator/assets/skins/sam/calendarnavigator.css', |
121 | - # Since the old cssgrids uses yui-, and the new uses yui3-, it is only |
122 | - # used for the calendar. |
123 | - 'yui/cssgrids/cssgrids.css', |
124 | - # Use the old cssgrids instead of the new cssgrids. |
125 | - 'cssgrids/grids.css', |
126 | - 'build/ui/assets/skins/sam/lazr.css', |
127 | - 'build/ui/assets/skins/sam/banner.css', |
128 | - 'build/inlineedit/assets/skins/sam/editor.css', |
129 | - 'build/overlay/assets/skins/sam/pretty-overlay.css', |
130 | - 'build/formoverlay/assets/formoverlay-core.css', |
131 | - 'build/inlinehelp/assets/inlinehelp-core.css', |
132 | - 'build/indicator/assets/indicator-core.css', |
133 | - 'build/confirmationoverlay/assets/confirmationoverlay-core.css', |
134 | - 'build/picker/assets/skins/sam/picker.css', |
135 | - 'build/activator/assets/skins/sam/activator.css', |
136 | - 'build/choiceedit/assets/choiceedit-core.css', |
137 | - 'build/ordering/assets/ordering-core.css', |
138 | - 'build/gallery-accordion/assets/gallery-accordion-core.css', |
139 | - 'build/gallery-accordion/assets/skins/sam/gallery-accordion-skin.css', |
140 | - 'build/inline-sprites-1.css', |
141 | - 'build/inline-sprites-2.css', |
142 | - 'build/block-sprites-1.css', |
143 | - # Include our main stylesheets at the end so they |
144 | - # take precedence over the others. |
145 | - 'css/base.css', |
146 | - 'css/typography.css', |
147 | - 'css/colours.css', |
148 | - 'css/forms.css', |
149 | - 'css/layout.css', |
150 | - 'css/modifiers.css'] |
151 | - |
152 | - |
153 | -def main(): |
154 | - icing = os.path.join(config.root, 'lib/canonical/launchpad/icing') |
155 | - target = os.path.join(icing, 'combo.css') |
156 | - |
157 | - # Get all the component css files so we don't have to edit this file |
158 | - # every time a new component is added. |
159 | - component_dir = 'css/components' |
160 | - component_path = os.path.abspath(os.path.join(icing, component_dir)) |
161 | - for root, dirs, files in scandir.walk(component_path): |
162 | - for file in files: |
163 | - if file.endswith('.css'): |
164 | - names.append('%s/%s' % (component_dir, file)) |
165 | - |
166 | - absolute_names = [] |
167 | - for name in names: |
168 | - full_path_name = os.path.abspath(os.path.join(icing, name)) |
169 | - absolute_names.append(full_path_name) |
170 | - |
171 | - combo = ComboFile(absolute_names, target) |
172 | - if combo.needs_update(): |
173 | - result = b'' |
174 | - for content in combine_files(names, icing): |
175 | - result += content |
176 | - |
177 | - with open(target, 'wb') as f: |
178 | - f.write(result) |
179 | diff --git a/lib/lp/scripts/utilities/js/combo.py b/lib/lp/scripts/utilities/js/combo.py |
180 | deleted file mode 100644 |
181 | index 8b0969f..0000000 |
182 | --- a/lib/lp/scripts/utilities/js/combo.py |
183 | +++ /dev/null |
184 | @@ -1,69 +0,0 @@ |
185 | -# Copyright 2011-2019 Canonical Ltd. This software is licensed under the |
186 | -# GNU Affero General Public License version 3 (see the file LICENSE). |
187 | - |
188 | -from __future__ import absolute_import, print_function, unicode_literals |
189 | - |
190 | -__metaclass__ = type |
191 | - |
192 | -import os |
193 | - |
194 | -from six.moves.urllib.parse import ( |
195 | - parse_qsl, |
196 | - urlsplit, |
197 | - ) |
198 | - |
199 | -from lp.scripts.utilities.js.jsbuild import ( |
200 | - CSSComboFile, |
201 | - JSComboFile, |
202 | - ) |
203 | - |
204 | - |
205 | -def parse_url(url): |
206 | - """Parse a combo URL. |
207 | - |
208 | - Returns the list of arguments in the original order. |
209 | - """ |
210 | - scheme, loc, path, query, frag = urlsplit(url) |
211 | - return parse_qs(query) |
212 | - |
213 | - |
214 | -def parse_qs(query): |
215 | - """Parse a query string. |
216 | - |
217 | - Returns the list of arguments in the original order. |
218 | - """ |
219 | - params = parse_qsl(query, keep_blank_values=True) |
220 | - return tuple([param for param, value in params]) |
221 | - |
222 | - |
223 | -def combine_files(fnames, root, resource_prefix=b"", |
224 | - minify_css=True, rewrite_urls=True): |
225 | - """Combine many files into one. |
226 | - |
227 | - Returns an iterator with the combined content of all the |
228 | - files. The relative path to root will be included as a comment |
229 | - between each file. |
230 | - |
231 | - Although CSS files are conceptually closer to text than bytes, we always |
232 | - yield bytes here since that's closer to what cssutils gives us, and it |
233 | - saves having to know the encoding. |
234 | - """ |
235 | - |
236 | - combo_by_kind = { |
237 | - ".css": CSSComboFile([], os.path.join(root, "combo.css"), |
238 | - resource_prefix, minify_css, rewrite_urls), |
239 | - ".js": JSComboFile([], os.path.join(root, "combo.js")), |
240 | - } |
241 | - |
242 | - for fname in fnames: |
243 | - combo = combo_by_kind.get(os.path.splitext(fname)[-1]) |
244 | - if combo is None: |
245 | - continue |
246 | - full = os.path.abspath(os.path.join(root, fname)) |
247 | - yield combo.get_file_header(full) |
248 | - if not full.startswith(root) or not os.path.exists(full): |
249 | - yield combo.get_comment("[missing]") |
250 | - else: |
251 | - with open(full, "rb") as f: |
252 | - content = f.read() |
253 | - yield combo.filter_file_content(content, full) |
254 | diff --git a/lib/lp/scripts/utilities/js/jsbuild.py b/lib/lp/scripts/utilities/js/jsbuild.py |
255 | index 7af59d3..a1bdd2f 100644 |
256 | --- a/lib/lp/scripts/utilities/js/jsbuild.py |
257 | +++ b/lib/lp/scripts/utilities/js/jsbuild.py |
258 | @@ -343,7 +343,10 @@ class Builder: |
259 | (self.name, skin_name)) |
260 | |
261 | css_files = extra_css_files + self.skins[skin_name] |
262 | - combined_css = CSSComboFile(css_files, skin_build_file) |
263 | + # Embedded URL rewrite should start with build/ for correct |
264 | + # filesystem location, as node-sass cannot add it. |
265 | + combined_css = CSSComboFile( |
266 | + css_files, skin_build_file, resource_prefix="build/") |
267 | if combined_css.needs_update(): |
268 | self.log('Updating %s...' % skin_build_file) |
269 | combined_css.update() |
270 | diff --git a/lib/lp/scripts/utilities/js/tests/__init__.py b/lib/lp/scripts/utilities/js/tests/__init__.py |
271 | deleted file mode 100644 |
272 | index e69de29..0000000 |
273 | --- a/lib/lp/scripts/utilities/js/tests/__init__.py |
274 | +++ /dev/null |
275 | diff --git a/lib/lp/scripts/utilities/js/tests/test_combo.py b/lib/lp/scripts/utilities/js/tests/test_combo.py |
276 | deleted file mode 100644 |
277 | index c745a6c..0000000 |
278 | --- a/lib/lp/scripts/utilities/js/tests/test_combo.py |
279 | +++ /dev/null |
280 | @@ -1,514 +0,0 @@ |
281 | -# Copyright 2011-2012 Canonical Ltd. This software is licensed under the |
282 | -# GNU Affero General Public License version 3 (see the file LICENSE). |
283 | - |
284 | -from __future__ import absolute_import, print_function, unicode_literals |
285 | - |
286 | -__metaclass__ = type |
287 | - |
288 | -import os |
289 | -import shutil |
290 | -import tempfile |
291 | - |
292 | -from lp.scripts.utilities.js.combo import ( |
293 | - combine_files, |
294 | - parse_url, |
295 | - ) |
296 | -from lp.testing import TestCase |
297 | - |
298 | - |
299 | -class ComboTestBase(TestCase): |
300 | - |
301 | - def setUp(self): |
302 | - self.__cleanup_paths = [] |
303 | - self.addCleanup(self.__cleanup) |
304 | - super(ComboTestBase, self).setUp() |
305 | - |
306 | - def __cleanup(self): |
307 | - for path in self.__cleanup_paths: |
308 | - if os.path.isfile(path): |
309 | - os.unlink(path) |
310 | - elif os.path.isdir(path): |
311 | - shutil.rmtree(path) |
312 | - |
313 | - def makeFile(self, content=None, suffix="", prefix="tmp", basename=None, |
314 | - dirname=None, path=None): |
315 | - """Create a temporary file and return the path to it. |
316 | - |
317 | - @param content: Initial content for the file. |
318 | - @param suffix: Suffix to be given to the file's basename. |
319 | - @param prefix: Prefix to be given to the file's basename. |
320 | - @param basename: Full basename for the file. |
321 | - @param dirname: Put file inside this directory. |
322 | - |
323 | - The file is removed after the test runs. |
324 | - """ |
325 | - if path is not None: |
326 | - self.__cleanup_paths.append(path) |
327 | - elif basename is not None: |
328 | - if dirname is None: |
329 | - dirname = tempfile.mkdtemp() |
330 | - self.__cleanup_paths.append(dirname) |
331 | - path = os.path.join(dirname, basename) |
332 | - else: |
333 | - fd, path = tempfile.mkstemp(suffix, prefix, dirname) |
334 | - self.__cleanup_paths.append(path) |
335 | - os.close(fd) |
336 | - if content is None: |
337 | - os.unlink(path) |
338 | - if content is not None: |
339 | - file = open(path, "w") |
340 | - file.write(content) |
341 | - file.close() |
342 | - return path |
343 | - |
344 | - def makeDir(self, suffix="", prefix="tmp", dirname=None, path=None): |
345 | - """Create a temporary directory and return the path to it. |
346 | - |
347 | - @param suffix: Suffix to be given to the file's basename. |
348 | - @param prefix: Prefix to be given to the file's basename. |
349 | - @param dirname: Put directory inside this parent directory. |
350 | - |
351 | - The directory is removed after the test runs. |
352 | - """ |
353 | - if path is not None: |
354 | - os.makedirs(path) |
355 | - else: |
356 | - path = tempfile.mkdtemp(suffix, prefix, dirname) |
357 | - self.__cleanup_paths.append(path) |
358 | - return path |
359 | - |
360 | - def makeSampleFile(self, root, fname, content): |
361 | - full = os.path.join(root, fname) |
362 | - parent = os.path.dirname(full) |
363 | - if not os.path.exists(parent): |
364 | - os.makedirs(parent) |
365 | - return self.makeFile(content=content, path=full) |
366 | - |
367 | - |
368 | -class TestCombo(ComboTestBase): |
369 | - |
370 | - def test_parse_url_keeps_order(self): |
371 | - """Parsing a combo loader URL returns an ordered list of filenames.""" |
372 | - self.assertEqual( |
373 | - parse_url(("http://yui.yahooapis.com/combo?" |
374 | - "3.0.0/build/yui/yui-min.js&" |
375 | - "3.0.0/build/oop/oop-min.js&" |
376 | - "3.0.0/build/event-custom/event-custom-min.js&")), |
377 | - ("3.0.0/build/yui/yui-min.js", |
378 | - "3.0.0/build/oop/oop-min.js", |
379 | - "3.0.0/build/event-custom/event-custom-min.js")) |
380 | - |
381 | - def test_combine_files_includes_filename(self): |
382 | - """ |
383 | - Combining files should include their relative filename at the top. |
384 | - """ |
385 | - test_dir = self.makeDir() |
386 | - |
387 | - self.makeSampleFile( |
388 | - test_dir, |
389 | - os.path.join("yui", "yui-min.js"), |
390 | - "** yui-min **") |
391 | - self.makeSampleFile( |
392 | - test_dir, |
393 | - os.path.join("oop", "oop-min.js"), |
394 | - "** oop-min **") |
395 | - self.makeSampleFile( |
396 | - test_dir, |
397 | - os.path.join("event-custom", "event-custom-min.js"), |
398 | - "** event-custom-min **") |
399 | - |
400 | - expected = "\n".join(("// yui/yui-min.js", |
401 | - "** yui-min **", |
402 | - "// oop/oop-min.js", |
403 | - "** oop-min **", |
404 | - "// event-custom/event-custom-min.js", |
405 | - "** event-custom-min **")) |
406 | - self.assertEqual( |
407 | - "".join(combine_files(["yui/yui-min.js", |
408 | - "oop/oop-min.js", |
409 | - "event-custom/event-custom-min.js"], |
410 | - root=test_dir)).strip(), |
411 | - expected) |
412 | - |
413 | - def test_combine_css_minifies_and_makes_relative(self): |
414 | - """ |
415 | - Combining CSS files minifies and makes URLs in CSS |
416 | - declarations relative to the target path. |
417 | - """ |
418 | - test_dir = self.makeDir() |
419 | - |
420 | - self.makeSampleFile( |
421 | - test_dir, |
422 | - os.path.join("widget", "assets", "skins", "sam", "widget.css"), |
423 | - """\ |
424 | - /* widget skin */ |
425 | - .yui-widget { |
426 | - background: url("img/bg.png"); |
427 | - } |
428 | - """) |
429 | - self.makeSampleFile( |
430 | - test_dir, |
431 | - os.path.join("editor", "assets", "skins", "sam", "editor.css"), |
432 | - """\ |
433 | - /* editor skin */ |
434 | - .yui-editor { |
435 | - background: url("img/bg.png"); |
436 | - } |
437 | - """) |
438 | - |
439 | - expected = "\n".join( |
440 | - ("/* widget/assets/skins/sam/widget.css */", |
441 | - ".yui-widget{background:url(widget/assets/skins/sam/img/bg.png)}", |
442 | - "/* editor/assets/skins/sam/editor.css */", |
443 | - ".yui-editor{background:url(editor/assets/skins/sam/img/bg.png)}", |
444 | - )) |
445 | - self.assertEqual( |
446 | - "".join(combine_files(["widget/assets/skins/sam/widget.css", |
447 | - "editor/assets/skins/sam/editor.css"], |
448 | - root=test_dir)).strip(), |
449 | - expected) |
450 | - |
451 | - def test_combine_css_handles_relative_links_from_top_level(self): |
452 | - """ |
453 | - Combining CSS files handles making URLs relative even from the top |
454 | - level (alongside combo.css). |
455 | - """ |
456 | - test_dir = self.makeDir() |
457 | - |
458 | - self.makeSampleFile( |
459 | - test_dir, "ubuntu-webfonts.css", |
460 | - """\ |
461 | - @font-face { |
462 | - src: url('fonts/Ubuntu.woff'); |
463 | - } |
464 | - """) |
465 | - |
466 | - expected = "\n".join( |
467 | - ("/* ubuntu-webfonts.css */", |
468 | - "@font-face{src:url(fonts/Ubuntu.woff)}", |
469 | - )) |
470 | - self.assertEqual( |
471 | - "".join(combine_files(["ubuntu-webfonts.css"], |
472 | - root=test_dir)).strip(), |
473 | - expected) |
474 | - |
475 | - def test_combine_css_leaves_absolute_urls_untouched(self): |
476 | - """ |
477 | - Combining CSS files does not touch absolute URLs in |
478 | - declarations. |
479 | - """ |
480 | - test_dir = self.makeDir() |
481 | - |
482 | - self.makeSampleFile( |
483 | - test_dir, |
484 | - os.path.join("widget", "assets", "skins", "sam", "widget.css"), |
485 | - """\ |
486 | - /* widget skin */ |
487 | - .yui-widget { |
488 | - background: url("/static/img/bg.png"); |
489 | - } |
490 | - """) |
491 | - self.makeSampleFile( |
492 | - test_dir, |
493 | - os.path.join("editor", "assets", "skins", "sam", "editor.css"), |
494 | - """\ |
495 | - /* editor skin */ |
496 | - .yui-editor { |
497 | - background: url("http://foo/static/img/bg.png"); |
498 | - } |
499 | - """) |
500 | - |
501 | - expected = "\n".join( |
502 | - ("/* widget/assets/skins/sam/widget.css */", |
503 | - ".yui-widget{background:url(/static/img/bg.png)}", |
504 | - "/* editor/assets/skins/sam/editor.css */", |
505 | - ".yui-editor{background:url(http://foo/static/img/bg.png)}", |
506 | - )) |
507 | - self.assertEqual( |
508 | - "".join(combine_files(["widget/assets/skins/sam/widget.css", |
509 | - "editor/assets/skins/sam/editor.css"], |
510 | - root=test_dir)).strip(), |
511 | - expected) |
512 | - |
513 | - def test_combine_css_leaves_data_uris_untouched(self): |
514 | - """ |
515 | - Combining CSS files does not touch data uris in |
516 | - declarations. |
517 | - """ |
518 | - test_dir = self.makeDir() |
519 | - |
520 | - self.makeSampleFile( |
521 | - test_dir, |
522 | - os.path.join("widget", "assets", "skins", "sam", "widget.css"), |
523 | - """\ |
524 | - /* widget skin */ |
525 | - .yui-widget { |
526 | - background: url("data:image/gif;base64,base64-data"); |
527 | - } |
528 | - """) |
529 | - self.makeSampleFile( |
530 | - test_dir, |
531 | - os.path.join("editor", "assets", "skins", "sam", "editor.css"), |
532 | - """\ |
533 | - /* editor skin */ |
534 | - .yui-editor { |
535 | - background: url(data:image/gif;base64,base64-data); |
536 | - } |
537 | - """) |
538 | - |
539 | - expected = "\n".join( |
540 | - ('/* widget/assets/skins/sam/widget.css */', |
541 | - '.yui-widget{background:url("data:image/gif;base64,base64-data")}', |
542 | - '/* editor/assets/skins/sam/editor.css */', |
543 | - '.yui-editor{background:url("data:image/gif;base64,base64-data")}', |
544 | - )) |
545 | - self.assertEqual( |
546 | - "".join(combine_files(["widget/assets/skins/sam/widget.css", |
547 | - "editor/assets/skins/sam/editor.css"], |
548 | - root=test_dir)).strip(), |
549 | - expected) |
550 | - |
551 | - def test_combine_css_disable_minify(self): |
552 | - """ |
553 | - It is possible to disable CSS minification altogether, while |
554 | - keeping the URL rewriting behaviour. |
555 | - """ |
556 | - test_dir = self.makeDir() |
557 | - |
558 | - self.makeSampleFile( |
559 | - test_dir, |
560 | - os.path.join("widget", "assets", "skins", "sam", "widget.css"), |
561 | - "\n".join( |
562 | - ('/* widget skin */', |
563 | - '.yui-widget {', |
564 | - ' background: url("img/bg.png");', |
565 | - '}')) |
566 | - ) |
567 | - self.makeSampleFile( |
568 | - test_dir, |
569 | - os.path.join("editor", "assets", "skins", "sam", "editor.css"), |
570 | - "\n".join(('/* editor skin */', |
571 | - '.yui-editor {', |
572 | - ' background: url("img/bg.png");', |
573 | - '}')) |
574 | - ) |
575 | - |
576 | - expected = "\n".join( |
577 | - ("/* widget/assets/skins/sam/widget.css */", |
578 | - "/* widget skin */", |
579 | - ".yui-widget {", |
580 | - " background: url(widget/assets/skins/sam/img/bg.png);", |
581 | - "}", |
582 | - "/* editor/assets/skins/sam/editor.css */", |
583 | - "/* editor skin */", |
584 | - ".yui-editor {", |
585 | - " background: url(editor/assets/skins/sam/img/bg.png);", |
586 | - "}", |
587 | - )) |
588 | - self.assertEqual( |
589 | - "".join(combine_files(["widget/assets/skins/sam/widget.css", |
590 | - "editor/assets/skins/sam/editor.css"], |
591 | - root=test_dir, minify_css=False)).strip(), |
592 | - expected) |
593 | - |
594 | - def test_combine_css_disable_rewrite_url(self): |
595 | - """ |
596 | - It is possible to disable the rewriting of urls in the CSS |
597 | - file. |
598 | - """ |
599 | - test_dir = self.makeDir() |
600 | - |
601 | - self.makeSampleFile( |
602 | - test_dir, |
603 | - os.path.join("widget", "assets", "skins", "sam", "widget.css"), |
604 | - """\ |
605 | - /* widget skin */ |
606 | - .yui-widget { |
607 | - background: url("img/bg.png"); |
608 | - } |
609 | - """) |
610 | - self.makeSampleFile( |
611 | - test_dir, |
612 | - os.path.join("editor", "assets", "skins", "sam", "editor.css"), |
613 | - """\ |
614 | - /* editor skin */ |
615 | - .yui-editor { |
616 | - background: url("img/bg.png"); |
617 | - } |
618 | - """) |
619 | - |
620 | - expected = "\n".join( |
621 | - ("/* widget/assets/skins/sam/widget.css */", |
622 | - ".yui-widget{background:url(img/bg.png)}", |
623 | - "/* editor/assets/skins/sam/editor.css */", |
624 | - ".yui-editor{background:url(img/bg.png)}", |
625 | - )) |
626 | - self.assertEqual( |
627 | - "".join(combine_files(["widget/assets/skins/sam/widget.css", |
628 | - "editor/assets/skins/sam/editor.css"], |
629 | - root=test_dir, rewrite_urls=False)).strip(), |
630 | - expected) |
631 | - |
632 | - def test_combine_css_disable_rewrite_url_and_minify(self): |
633 | - """ |
634 | - It is possible to disable both the rewriting of urls in the |
635 | - CSS file and minification, in which case the files are |
636 | - returned unchanged. |
637 | - """ |
638 | - test_dir = self.makeDir() |
639 | - |
640 | - self.makeSampleFile( |
641 | - test_dir, |
642 | - os.path.join("widget", "assets", "skins", "sam", "widget.css"), |
643 | - "\n".join( |
644 | - ('/* widget skin */', |
645 | - '.yui-widget {', |
646 | - ' background: url("img/bg.png");', |
647 | - '}')) |
648 | - ) |
649 | - self.makeSampleFile( |
650 | - test_dir, |
651 | - os.path.join("editor", "assets", "skins", "sam", "editor.css"), |
652 | - "\n".join(('/* editor skin */', |
653 | - '.yui-editor {', |
654 | - ' background: url("img/bg.png");', |
655 | - '}')) |
656 | - ) |
657 | - |
658 | - expected = "\n".join( |
659 | - ('/* widget/assets/skins/sam/widget.css */', |
660 | - '/* widget skin */', |
661 | - '.yui-widget {', |
662 | - ' background: url("img/bg.png");', |
663 | - '}', |
664 | - '/* editor/assets/skins/sam/editor.css */', |
665 | - '/* editor skin */', |
666 | - '.yui-editor {', |
667 | - ' background: url("img/bg.png");', |
668 | - '}', |
669 | - )) |
670 | - self.assertEqual( |
671 | - "".join(combine_files(["widget/assets/skins/sam/widget.css", |
672 | - "editor/assets/skins/sam/editor.css"], |
673 | - root=test_dir, |
674 | - minify_css=False, |
675 | - rewrite_urls=False)).strip(), |
676 | - expected) |
677 | - |
678 | - def test_combine_css_adds_custom_prefix(self): |
679 | - """ |
680 | - Combining CSS files minifies and makes URLs in CSS |
681 | - declarations relative to the target path. It's also possible |
682 | - to specify an additional prefix for the rewritten URLs. |
683 | - """ |
684 | - test_dir = self.makeDir() |
685 | - |
686 | - self.makeSampleFile( |
687 | - test_dir, |
688 | - os.path.join("widget", "assets", "skins", "sam", "widget.css"), |
689 | - """\ |
690 | - /* widget skin */ |
691 | - .yui-widget { |
692 | - background: url("img/bg.png"); |
693 | - } |
694 | - """) |
695 | - self.makeSampleFile( |
696 | - test_dir, |
697 | - os.path.join("editor", "assets", "skins", "sam", "editor.css"), |
698 | - """\ |
699 | - /* editor skin */ |
700 | - .yui-editor { |
701 | - background: url("img/bg.png"); |
702 | - } |
703 | - """) |
704 | - |
705 | - expected = "\n".join( |
706 | - ("/* widget/assets/skins/sam/widget.css */", |
707 | - ".yui-widget{background:url(" + |
708 | - "/static/widget/assets/skins/sam/img/bg.png)}", |
709 | - "/* editor/assets/skins/sam/editor.css */", |
710 | - ".yui-editor{background:url(" + |
711 | - "/static/editor/assets/skins/sam/img/bg.png)}", |
712 | - )) |
713 | - self.assertEqual( |
714 | - "".join(combine_files(["widget/assets/skins/sam/widget.css", |
715 | - "editor/assets/skins/sam/editor.css"], |
716 | - root=test_dir, |
717 | - resource_prefix=b"/static/")).strip(), |
718 | - expected) |
719 | - |
720 | - def test_missing_file_is_ignored(self): |
721 | - """ |
722 | - If a missing file is requested we should still combine the others. |
723 | - """ |
724 | - test_dir = self.makeDir() |
725 | - |
726 | - self.makeSampleFile( |
727 | - test_dir, |
728 | - os.path.join("yui", "yui-min.js"), |
729 | - "** yui-min **") |
730 | - self.makeSampleFile( |
731 | - test_dir, |
732 | - os.path.join("event-custom", "event-custom-min.js"), |
733 | - "** event-custom-min **") |
734 | - |
735 | - expected = "\n".join(("// yui/yui-min.js", |
736 | - "** yui-min **", |
737 | - "// oop/oop-min.js", |
738 | - "// [missing]", |
739 | - "// event-custom/event-custom-min.js", |
740 | - "** event-custom-min **")) |
741 | - self.assertEqual( |
742 | - "".join(combine_files(["yui/yui-min.js", |
743 | - "oop/oop-min.js", |
744 | - "event-custom/event-custom-min.js"], |
745 | - root=test_dir)).strip(), |
746 | - expected) |
747 | - |
748 | - def test_no_parent_hack(self): |
749 | - """If someone tries to hack going up the root, they'll get a miss.""" |
750 | - test_dir = self.makeDir() |
751 | - self.makeSampleFile( |
752 | - test_dir, |
753 | - os.path.join("oop", "oop-min.js"), |
754 | - "** oop-min **") |
755 | - |
756 | - root = os.path.join(test_dir, "root", "lazr") |
757 | - os.makedirs(root) |
758 | - |
759 | - hack = "../../oop/oop-min.js" |
760 | - self.assertTrue(os.path.exists(os.path.join(root, hack))) |
761 | - |
762 | - expected = "\n".join(("// ../../oop/oop-min.js", |
763 | - "// [missing]")) |
764 | - self.assertEqual( |
765 | - "".join(combine_files([hack], root=root)).strip(), |
766 | - expected) |
767 | - |
768 | - def test_rewrite_url_normalizes_parent_references(self): |
769 | - """URL references in CSS files get normalized for parent dirs.""" |
770 | - test_dir = self.makeDir() |
771 | - files = [ |
772 | - self.makeSampleFile( |
773 | - test_dir, |
774 | - os.path.join("yui", "base", "base.css"), |
775 | - ".foo{background-image:url(../../img.png)}"), |
776 | - ] |
777 | - |
778 | - expected = "\n".join(("/* yui/base/base.css */", |
779 | - ".foo{background-image:url(img.png)}")) |
780 | - self.assertEqual( |
781 | - "".join(combine_files(files, root=test_dir)).strip(), |
782 | - expected) |
783 | - |
784 | - def test_no_absolute_path_hack(self): |
785 | - """If someone tries to fetch an absolute file, they'll get nothing.""" |
786 | - test_dir = self.makeDir() |
787 | - |
788 | - hack = "/etc/passwd" |
789 | - self.assertTrue(os.path.exists("/etc/passwd")) |
790 | - |
791 | - expected = "" |
792 | - self.assertEqual( |
793 | - "".join(combine_files([hack], root=test_dir)).strip(), |
794 | - expected) |
795 | diff --git a/setup.py b/setup.py |
796 | index acb19b7..9a126c9 100644 |
797 | --- a/setup.py |
798 | +++ b/setup.py |
799 | @@ -306,7 +306,6 @@ setup( |
800 | 'lp.services.sitesearch.bingtestservice:main', |
801 | 'build-twisted-plugin-cache = ' |
802 | 'lp.services.twistedsupport.plugincache:main', |
803 | - 'combine-css = lp.scripts.utilities.js.combinecss:main', |
804 | 'generate-key-pair = ' |
805 | 'lp.services.crypto.scripts.generatekeypair:main', |
806 | 'harness = lp.scripts.harness:python', |
I'm not an expert but the code looks good to me and it matches the goal in the Description of the MP.