Merge ~twom/launchpad:sassy-css-no-op-css-compilation into launchpad: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)
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

Revision history for this message
Ioana Lasc (ilasc) wrote :

I'm not an expert but the code looks good to me and it matches the goal in the Description of the MP.

review: Approve
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
1diff --git a/Makefile b/Makefile
2index 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 \
29diff --git a/lib/canonical/launchpad/icing/combo.scss b/lib/canonical/launchpad/icing/combo.scss
30new file mode 100644
31index 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';
63diff --git a/lib/canonical/launchpad/icing/css/components/_index.scss b/lib/canonical/launchpad/icing/css/components/_index.scss
64new file mode 100644
65index 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';
77diff --git a/lib/lp/app/templates/base-layout-macros.pt b/lib/lp/app/templates/base-layout-macros.pt
78index 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
90diff --git a/lib/lp/scripts/utilities/js/combinecss.py b/lib/lp/scripts/utilities/js/combinecss.py
91deleted file mode 100755
92index 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)
179diff --git a/lib/lp/scripts/utilities/js/combo.py b/lib/lp/scripts/utilities/js/combo.py
180deleted file mode 100644
181index 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)
254diff --git a/lib/lp/scripts/utilities/js/jsbuild.py b/lib/lp/scripts/utilities/js/jsbuild.py
255index 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()
270diff --git a/lib/lp/scripts/utilities/js/tests/__init__.py b/lib/lp/scripts/utilities/js/tests/__init__.py
271deleted file mode 100644
272index e69de29..0000000
273--- a/lib/lp/scripts/utilities/js/tests/__init__.py
274+++ /dev/null
275diff --git a/lib/lp/scripts/utilities/js/tests/test_combo.py b/lib/lp/scripts/utilities/js/tests/test_combo.py
276deleted file mode 100644
277index 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)
795diff --git a/setup.py b/setup.py
796index 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',

Subscribers

People subscribed via source and target branches

to status/vote changes: