81.5.13
by Sidnei da Silva
- Sample metadata |
1 |
import os |
81.5.10
by Sidnei da Silva
- Add a few tests for parsing meta |
2 |
import re |
81.5.14
by Sidnei da Silva
- Make requirements parseable. |
3 |
import sys |
4 |
import glob |
|
170.3.1
by Sidnei da Silva
- Implement minification optimization by declaring variables for string literals used more than once. |
5 |
import itertools |
81.5.14
by Sidnei da Silva
- Make requirements parseable. |
6 |
import optparse |
81.5.10
by Sidnei da Silva
- Add a few tests for parsing meta |
7 |
|
8 |
import simplejson |
|
9 |
||
81.5.14
by Sidnei da Silva
- Make requirements parseable. |
10 |
from lazr.js.build import SRC_DIR |
11 |
||
12 |
||
81.5.10
by Sidnei da Silva
- Add a few tests for parsing meta |
13 |
DETAILS_RE = re.compile( |
14 |
"YUI\.add\([\'\"\s]*([^\'\"]*)[\'\"\s]*,.*?function.*?" |
|
15 |
"[\'\"\s]*[0-9\.]*[\'\"\s]*" |
|
156.1.1
by Guilherme Salgado
Fix DETAILS_RE in meta.py so that it doesn't do substring matching on the keywords used to find the dependencies of a module. |
16 |
"({[^{}a-zA-Z]*(use|requires|optional|after|" |
156.1.3
by Guilherme Salgado
Minor change to my previous fix to DETAILS_RE |
17 |
"supersedes|omit|skinnable)[\s\'\":]*[^{}]*})\s*\);", re.M | re.S) |
81.5.10
by Sidnei da Silva
- Add a few tests for parsing meta |
18 |
|
170.3.1
by Sidnei da Silva
- Implement minification optimization by declaring variables for string literals used more than once. |
19 |
LITERAL_RE = re.compile("([\[ ]+)\"([\w\.\+-]+)\"([^:])") |
20 |
NAME_RE = re.compile("[\.\+-]") |
|
21 |
||
81.5.10
by Sidnei da Silva
- Add a few tests for parsing meta |
22 |
|
23 |
def extract_metadata(src): |
|
24 |
"""Extract metadata about an YUI module, given it's source."""
|
|
25 |
metadata = [] |
|
26 |
for entry in DETAILS_RE.finditer(src): |
|
27 |
name, details, ignore = entry.groups() |
|
28 |
details = simplejson.loads(details) |
|
29 |
details["name"] = name |
|
30 |
metadata.append(details) |
|
31 |
return metadata |
|
81.5.11
by Sidnei da Silva
- Cleanup |
32 |
|
81.5.14
by Sidnei da Silva
- Make requirements parseable. |
33 |
|
34 |
class Builder: |
|
35 |
||
81.5.19
by Sidnei da Silva
- Add a prefix |
36 |
def __init__(self, name, src_dir=SRC_DIR, output=None, |
161.1.5
by Guilherme Salgado
Couple minor changes as suggested by Maris |
37 |
prefix="", exclude_regex=None, ext=True, include_skin=True): |
81.5.14
by Sidnei da Silva
- Make requirements parseable. |
38 |
"""Create a new Builder.
|
39 |
||
40 |
:param name: The name of the global variable name that will
|
|
41 |
contain the modules configuration.
|
|
42 |
:param src_dir: The directory containing the source files.
|
|
43 |
:param output: The output filename for the module metadata.
|
|
81.5.19
by Sidnei da Silva
- Add a prefix |
44 |
:param prefix: A prefix to be added to the relative path.
|
124.5.5
by Sidnei da Silva
- Back to excludes-regex. Add type. |
45 |
:param exclude_regex: A regex that will exclude file
|
124.5.4
by Sidnei da Silva
- Default value for excludes and multiple extras |
46 |
paths from the final rollup. -min and -debug versions
|
47 |
will still be built.
|
|
48 |
:param ext: Default value for the 'ext' setting. Set to
|
|
49 |
'False' to use modules with a local combo loader.
|
|
161.1.5
by Guilherme Salgado
Couple minor changes as suggested by Maris |
50 |
:param include_skin: If False, the generated metadata won't include
|
51 |
skin modules and all javascript modules will have skinnable=False.
|
|
52 |
Defaults to True.
|
|
81.5.14
by Sidnei da Silva
- Make requirements parseable. |
53 |
"""
|
54 |
self.name = name |
|
55 |
self.output = output |
|
81.5.19
by Sidnei da Silva
- Add a prefix |
56 |
self.prefix = prefix |
81.5.14
by Sidnei da Silva
- Make requirements parseable. |
57 |
self.src_dir = src_dir |
124.5.5
by Sidnei da Silva
- Back to excludes-regex. Add type. |
58 |
self.exclude_regex = exclude_regex |
161.1.5
by Guilherme Salgado
Couple minor changes as suggested by Maris |
59 |
self.include_skin = include_skin |
124.5.4
by Sidnei da Silva
- Default value for excludes and multiple extras |
60 |
self.ext = ext |
81.5.14
by Sidnei da Silva
- Make requirements parseable. |
61 |
|
62 |
def log(self, msg): |
|
63 |
sys.stdout.write(msg + '\n') |
|
64 |
||
65 |
def fail(self, msg): |
|
66 |
"""An error was encountered, abort build."""
|
|
67 |
sys.stderr.write(msg + '\n') |
|
68 |
sys.exit(1) |
|
69 |
||
70 |
def file_is_excluded(self, filepath): |
|
71 |
"""Is the given file path excluded from the rollup process?"""
|
|
124.5.5
by Sidnei da Silva
- Back to excludes-regex. Add type. |
72 |
if not self.exclude_regex: |
81.5.14
by Sidnei da Silva
- Make requirements parseable. |
73 |
# Include everything.
|
74 |
return False |
|
124.5.4
by Sidnei da Silva
- Default value for excludes and multiple extras |
75 |
|
124.5.5
by Sidnei da Silva
- Back to excludes-regex. Add type. |
76 |
return re.search(self.exclude_regex, filepath) |
81.5.14
by Sidnei da Silva
- Make requirements parseable. |
77 |
|
78 |
def generate_metadata(self, fnames, root, var_name, out): |
|
79 |
"""Extract metadata from a group of files and write it out."""
|
|
80 |
metadata = [] |
|
81 |
||
82 |
for fname in fnames: |
|
83 |
self.log("Extracting metadata from '%s'" % fname) |
|
84 |
data = open(fname, "r").read() |
|
85 |
meta = extract_metadata(data) |
|
81.5.23
by Sidnei da Silva
- Fixed the tests |
86 |
prefix = "" |
87 |
if self.prefix and not prefix.endswith("/"): |
|
81.6.1
by Sidnei da Silva
- Move yui around |
88 |
prefix = self.prefix + "/" |
81.5.14
by Sidnei da Silva
- Make requirements parseable. |
89 |
for entry in meta: |
166.2.1
by Sidnei da Silva
- Generated module info should default to minified path. |
90 |
# According to the source of the YUI loader module:
|
91 |
#
|
|
92 |
# The default path for the YUI library is the
|
|
93 |
# minified version of the files (e.g., event-min.js).
|
|
94 |
#
|
|
95 |
# To make it easier for everyone, let's use the same
|
|
96 |
# convention here, and use the minified path.
|
|
97 |
relpath = ( |
|
81.6.4
by Sidnei da Silva
- Improved generation of skin modules and revamped combo service to make it more twisty. |
98 |
prefix + fname.replace(root + os.path.sep, "") |
99 |
).replace(os.path.sep, "/") |
|
166.2.1
by Sidnei da Silva
- Generated module info should default to minified path. |
100 |
dirname, basename = relpath.rsplit("/", 1) |
166.2.2
by Sidnei da Silva
- Make tuple start on the Right Line. |
101 |
entry["path"] = "%s/%s-min%s" % ( |
102 |
(dirname,) + os.path.splitext(basename)) |
|
81.6.4
by Sidnei da Silva
- Improved generation of skin modules and revamped combo service to make it more twisty. |
103 |
|
81.5.23
by Sidnei da Silva
- Fixed the tests |
104 |
entry["type"] = "js" |
124.5.4
by Sidnei da Silva
- Default value for excludes and multiple extras |
105 |
entry["ext"] = self.ext |
161.1.5
by Guilherme Salgado
Couple minor changes as suggested by Maris |
106 |
if self.include_skin and entry.get("skinnable"): |
81.6.4
by Sidnei da Silva
- Improved generation of skin modules and revamped combo service to make it more twisty. |
107 |
self.generate_skin_modules(entry, metadata, root) |
81.5.14
by Sidnei da Silva
- Make requirements parseable. |
108 |
metadata.extend(meta) |
109 |
||
110 |
modules = {} |
|
170.3.1
by Sidnei da Silva
- Implement minification optimization by declaring variables for string literals used more than once. |
111 |
all_literals = [] |
81.5.14
by Sidnei da Silva
- Make requirements parseable. |
112 |
for meta in metadata: |
113 |
name = meta.pop("name") |
|
114 |
modules[name] = meta |
|
170.3.1
by Sidnei da Silva
- Implement minification optimization by declaring variables for string literals used more than once. |
115 |
all_literals.append(name) |
116 |
all_literals.extend(meta.get("use", ())) |
|
117 |
all_literals.extend(meta.get("requires", ())) |
|
118 |
all_literals.extend(meta.get("after", ())) |
|
170.3.5
by Sidnei da Silva
- Add some more comments as recommended by Maris, simplify part of the code. |
119 |
all_literals.append(meta["type"]) |
170.3.1
by Sidnei da Silva
- Implement minification optimization by declaring variables for string literals used more than once. |
120 |
|
121 |
# Only optimize string literals if they are used more than
|
|
170.3.5
by Sidnei da Silva
- Add some more comments as recommended by Maris, simplify part of the code. |
122 |
# once, since otherwise the optimization is pointless. This
|
123 |
# loop here is basically filtering out those that only occur
|
|
124 |
# once.
|
|
170.3.1
by Sidnei da Silva
- Implement minification optimization by declaring variables for string literals used more than once. |
125 |
literals = [k for k, g in itertools.groupby(sorted(all_literals)) |
126 |
if len(list(g)) > 1] |
|
127 |
||
128 |
# For each string literal we are interested in, generate a
|
|
129 |
# variable declaration for the string literal, to improve
|
|
130 |
# minification.
|
|
170.3.5
by Sidnei da Silva
- Add some more comments as recommended by Maris, simplify part of the code. |
131 |
#
|
132 |
# The variable name is generated by replacing [".", "+", "-"]
|
|
133 |
# with an underscore and then make that the variable name,
|
|
134 |
# uppercase.
|
|
135 |
#
|
|
136 |
# We'll save a mapping of literal -> variable name here for
|
|
137 |
# reuse below on the re.sub() helper function.
|
|
138 |
literals_map = dict([(literal, NAME_RE.sub("_", literal).upper()) |
|
139 |
for literal in literals]) |
|
170.3.1
by Sidnei da Silva
- Implement minification optimization by declaring variables for string literals used more than once. |
140 |
variables_decl = "var %s" % ",\n ".join( |
170.3.5
by Sidnei da Silva
- Add some more comments as recommended by Maris, simplify part of the code. |
141 |
["%s = \"%s\"" % (variable, literal) |
142 |
for literal, variable in sorted(literals_map.iteritems())]) |
|
170.3.1
by Sidnei da Silva
- Implement minification optimization by declaring variables for string literals used more than once. |
143 |
|
170.3.5
by Sidnei da Silva
- Add some more comments as recommended by Maris, simplify part of the code. |
144 |
# This re.sub() helper function operates on the JSON-ified
|
145 |
# representation of the modules, by looking for string
|
|
146 |
# literals that occur over the JSON structure but *not* as
|
|
147 |
# attribute names.
|
|
148 |
#
|
|
149 |
# If a string literal is found that matches the list of
|
|
150 |
# literals we have declared as variables above, then replace
|
|
151 |
# the it by the equivalent variable, otherwise return the
|
|
152 |
# original string.
|
|
170.3.1
by Sidnei da Silva
- Implement minification optimization by declaring variables for string literals used more than once. |
153 |
def literal_sub(match): |
170.3.5
by Sidnei da Silva
- Add some more comments as recommended by Maris, simplify part of the code. |
154 |
literal = match.group(2) |
155 |
if literal in literals_map: |
|
156 |
return match.group(1) + literals_map[literal] + match.group(3) |
|
170.3.1
by Sidnei da Silva
- Implement minification optimization by declaring variables for string literals used more than once. |
157 |
return match.group(0) |
158 |
||
159 |
modules_decl = LITERAL_RE.sub(literal_sub, simplejson.dumps(modules)) |
|
160 |
||
161 |
module_config = open(out, "w") |
|
162 |
try: |
|
163 |
module_config.write("""var %s = (function(){ |
|
164 |
%s; |
|
165 |
return %s; |
|
170.3.2
by Sidnei da Silva
- Missing trailing semicolon |
166 |
})();""" % (var_name, variables_decl, modules_decl)) |
170.3.1
by Sidnei da Silva
- Implement minification optimization by declaring variables for string literals used more than once. |
167 |
finally: |
168 |
module_config.close() |
|
81.5.14
by Sidnei da Silva
- Make requirements parseable. |
169 |
|
81.6.4
by Sidnei da Silva
- Improved generation of skin modules and revamped combo service to make it more twisty. |
170 |
def generate_skin_modules(self, entry, metadata, root): |
171 |
# Generate a skin module definition, since YUI assumes that
|
|
172 |
# the path starts with the module name, and that breaks with
|
|
173 |
# our structure.
|
|
174 |
#
|
|
175 |
# Follow lazr-js conventions and look for any file in the skin
|
|
176 |
# assets directory.
|
|
177 |
module_names = [] |
|
178 |
by_name = {} |
|
179 |
||
180 |
prefix = "" |
|
181 |
if self.prefix and not prefix.endswith("/"): |
|
182 |
prefix = self.prefix + "/" |
|
183 |
||
184 |
# Default 'after' modules from YUI Loader. Might have to
|
|
185 |
# be changed in the future, if YUI itself changes.
|
|
186 |
after = ["cssreset", "cssfonts", |
|
187 |
"cssgrids", "cssreset-context", |
|
188 |
"cssfonts-context", |
|
189 |
"cssgrids-context"] |
|
190 |
||
191 |
if entry.get("requires"): |
|
192 |
# If the base module requires other modules, extend
|
|
193 |
# the after entry with the (expected) skins for those
|
|
194 |
# modules to force our skin to be loaded after those.
|
|
195 |
after.extend(["skin-sam-%s" % s |
|
196 |
for s in entry["requires"]]) |
|
197 |
||
198 |
assets = os.path.join( |
|
199 |
os.path.dirname(entry["path"][len(prefix):]), "assets") |
|
200 |
sam = os.path.join(assets, "skins", "sam") |
|
201 |
css_assets = glob.glob(os.path.join(root, sam, "*-skin.css")) |
|
202 |
||
203 |
for fname in css_assets: |
|
204 |
if not os.path.exists(fname): |
|
205 |
# If the file doesn't exist, don't create a module to
|
|
206 |
# load it.
|
|
207 |
continue
|
|
208 |
||
209 |
# Compute a module name for this asset.
|
|
210 |
module_name = os.path.basename(fname)[:-len("-skin.css")] |
|
211 |
skin_module_name = "skin-sam-%s" % entry["name"] |
|
212 |
# If the computed module_name does not match the
|
|
213 |
# Javascript module name without the namespace, then use
|
|
214 |
# it as a postfix to disambiguate possibly multiple
|
|
215 |
# modules.
|
|
216 |
package = entry["name"].split(".")[-1] |
|
217 |
if module_name != package and len(css_assets) > 1: |
|
218 |
skin_module_name = "%s+%s" % (skin_module_name, module_name) |
|
219 |
||
220 |
css = (fname.replace(root + os.path.sep, "") |
|
221 |
).replace(os.path.sep, "/") |
|
222 |
module = {"name": skin_module_name, |
|
223 |
"after": after[:], |
|
224 |
"type": "css", |
|
225 |
"ext": self.ext, |
|
226 |
"path": prefix + css} |
|
227 |
by_name[skin_module_name] = module |
|
228 |
module_names.append(skin_module_name) |
|
229 |
metadata.append(module) |
|
230 |
||
231 |
# All assets under the skin have been looked at. Now look for
|
|
232 |
# a "-core.css" asset, following lazr-js conventions and add
|
|
233 |
# it as a requirement for the previously-found assets.
|
|
234 |
for module_name in module_names: |
|
235 |
name = os.path.basename( |
|
236 |
by_name[module_name]["path"])[:-len("-skin.css")] |
|
237 |
fname = os.path.join(root, assets, "%s-core.css" % name) |
|
238 |
if not os.path.exists(fname): |
|
239 |
# No core CSS asset exists for this skin, skip
|
|
240 |
# generating a module for it.
|
|
241 |
continue
|
|
242 |
||
243 |
skin_module_name = "%s+core" % module_name |
|
244 |
css = (fname.replace(root + os.path.sep, "") |
|
245 |
).replace(os.path.sep, "/") |
|
246 |
module = {"name": skin_module_name, |
|
247 |
"after": after[:], |
|
248 |
"type": "css", |
|
249 |
"ext": self.ext, |
|
250 |
"path": prefix + css} |
|
251 |
||
252 |
requires = by_name[module_name].setdefault("requires", []) |
|
253 |
requires.append(skin_module_name) |
|
254 |
after = by_name[module_name].setdefault("after", []) |
|
255 |
after.append(skin_module_name) |
|
256 |
metadata.append(module) |
|
257 |
||
81.5.14
by Sidnei da Silva
- Make requirements parseable. |
258 |
def do_build(self): |
259 |
included_files = [] |
|
260 |
||
81.5.16
by Sidnei da Silva
- Use os.walk |
261 |
for root, dirnames, fnames in os.walk(self.src_dir): |
262 |
for fname in glob.glob(os.path.join(root, '*.js')): |
|
81.5.14
by Sidnei da Silva
- Make requirements parseable. |
263 |
if not self.file_is_excluded(fname): |
264 |
included_files.append(fname) |
|
265 |
||
266 |
self.generate_metadata(included_files, self.src_dir, |
|
267 |
self.name, self.output) |
|
268 |
||
269 |
||
270 |
def get_options(): |
|
271 |
"""Parse the command line options."""
|
|
272 |
parser = optparse.OptionParser( |
|
273 |
usage="%prog [options]", |
|
274 |
description=( |
|
275 |
"Create YUI module metadata from extension modules. "
|
|
276 |
))
|
|
277 |
parser.add_option( |
|
278 |
'-n', '--name', dest='name', default='LAZR_MODULES', |
|
279 |
help=('The global variable name used to hold the modules config.')) |
|
280 |
parser.add_option( |
|
281 |
'-o', '--output', dest='output', |
|
282 |
help=('The output filename for the module metadata.')) |
|
283 |
parser.add_option( |
|
81.5.19
by Sidnei da Silva
- Add a prefix |
284 |
'-p', '--prefix', dest='prefix', |
285 |
help=('A prefix to be added to the relative path.')) |
|
286 |
parser.add_option( |
|
81.5.14
by Sidnei da Silva
- Make requirements parseable. |
287 |
'-s', '--srcdir', dest='src_dir', default=SRC_DIR, |
288 |
help=('The directory containing the src files.')) |
|
289 |
parser.add_option( |
|
124.5.5
by Sidnei da Silva
- Back to excludes-regex. Add type. |
290 |
'-e', '--ext', dest='ext', default=False, |
291 |
action="store_true", |
|
124.5.4
by Sidnei da Silva
- Default value for excludes and multiple extras |
292 |
help=('Default value for the "ext" configuration setting.')) |
293 |
parser.add_option( |
|
124.5.5
by Sidnei da Silva
- Back to excludes-regex. Add type. |
294 |
'-x', '--exclude', dest='exclude_regex', |
295 |
default=None, metavar='REGEX', |
|
124.5.4
by Sidnei da Silva
- Default value for excludes and multiple extras |
296 |
help=('Exclude any files that match the given regular expressions.')) |
161.1.3
by Guilherme Salgado
Make it possible to pass an extra --no-skin arg to meta.py:main |
297 |
parser.add_option( |
298 |
'-k', '--no-skin', dest='no_skin', default=False, action="store_true", |
|
161.1.4
by Guilherme Salgado
Add a couple comments and clean some things up |
299 |
help=('Do not include skin files in the list of modules and set ' |
300 |
'skinnable=False for all modules.')) |
|
81.5.14
by Sidnei da Silva
- Make requirements parseable. |
301 |
return parser.parse_args() |
302 |
||
303 |
||
304 |
def main(): |
|
305 |
options, args = get_options() |
|
306 |
Builder( |
|
307 |
name=options.name, |
|
308 |
src_dir=os.path.abspath(options.src_dir), |
|
309 |
output=options.output, |
|
81.5.19
by Sidnei da Silva
- Add a prefix |
310 |
prefix=options.prefix, |
124.5.5
by Sidnei da Silva
- Back to excludes-regex. Add type. |
311 |
exclude_regex=options.exclude_regex, |
124.5.4
by Sidnei da Silva
- Default value for excludes and multiple extras |
312 |
ext=options.ext, |
161.1.5
by Guilherme Salgado
Couple minor changes as suggested by Maris |
313 |
include_skin=not options.no_skin, |
81.5.14
by Sidnei da Silva
- Make requirements parseable. |
314 |
).do_build() |