OLD | NEW |
| (Empty) |
1 #!/usr/bin/env python | |
2 # Copyright (c) 2012 The Chromium Authors. All rights reserved. | |
3 # Use of this source code is governed by a BSD-style license that can be | |
4 # found in the LICENSE file. | |
5 | |
6 '''Support for formatting an RC file for compilation. | |
7 ''' | |
8 | |
9 import os | |
10 import types | |
11 import re | |
12 from functools import partial | |
13 | |
14 from grit import util | |
15 from grit.node import misc | |
16 | |
17 | |
18 def Format(root, lang='en', output_dir='.'): | |
19 from grit.node import empty, include, message, structure | |
20 | |
21 yield _FormatHeader(root, lang, output_dir) | |
22 | |
23 for item in root.ActiveDescendants(): | |
24 if isinstance(item, empty.MessagesNode): | |
25 # Write one STRINGTABLE per <messages> container. | |
26 # This is hacky: it iterates over the children twice. | |
27 yield 'STRINGTABLE\nBEGIN\n' | |
28 for subitem in item.ActiveDescendants(): | |
29 if isinstance(subitem, message.MessageNode): | |
30 with subitem: | |
31 yield FormatMessage(subitem, lang) | |
32 yield 'END\n\n' | |
33 elif isinstance(item, include.IncludeNode): | |
34 with item: | |
35 yield FormatInclude(item, lang, output_dir) | |
36 elif isinstance(item, structure.StructureNode): | |
37 with item: | |
38 yield FormatStructure(item, lang, output_dir) | |
39 | |
40 | |
41 ''' | |
42 This dictionary defines the language charset pair lookup table, which is used | |
43 for replacing the GRIT expand variables for language info in Product Version | |
44 resource. The key is the language ISO country code, and the value | |
45 is the language and character-set pair, which is a hexadecimal string | |
46 consisting of the concatenation of the language and character-set identifiers. | |
47 The first 4 digit of the value is the hex value of LCID, the remaining | |
48 4 digits is the hex value of character-set id(code page)of the language. | |
49 | |
50 LCID resource: http://msdn.microsoft.com/en-us/library/ms776294.aspx | |
51 Codepage resource: http://www.science.co.il/language/locale-codes.asp | |
52 | |
53 We have defined three GRIT expand_variables to be used in the version resource | |
54 file to set the language info. Here is an example how they should be used in | |
55 the VS_VERSION_INFO section of the resource file to allow GRIT to localize | |
56 the language info correctly according to product locale. | |
57 | |
58 VS_VERSION_INFO VERSIONINFO | |
59 ... | |
60 BEGIN | |
61 BLOCK "StringFileInfo" | |
62 BEGIN | |
63 BLOCK "[GRITVERLANGCHARSETHEX]" | |
64 BEGIN | |
65 ... | |
66 END | |
67 END | |
68 BLOCK "VarFileInfo" | |
69 BEGIN | |
70 VALUE "Translation", [GRITVERLANGID], [GRITVERCHARSETID] | |
71 END | |
72 END | |
73 | |
74 ''' | |
75 | |
76 _LANGUAGE_CHARSET_PAIR = { | |
77 # Language neutral LCID, unicode(1200) code page. | |
78 'neutral' : '000004b0', | |
79 # LANG_USER_DEFAULT LCID, unicode(1200) code page. | |
80 'userdefault' : '040004b0', | |
81 'ar' : '040104e8', | |
82 'fi' : '040b04e4', | |
83 'ko' : '041203b5', | |
84 'es' : '0c0a04e4', | |
85 'bg' : '040204e3', | |
86 # No codepage for filipino, use unicode(1200). | |
87 'fil' : '046404e4', | |
88 'fr' : '040c04e4', | |
89 'lv' : '042604e9', | |
90 'sv' : '041d04e4', | |
91 'ca' : '040304e4', | |
92 'de' : '040704e4', | |
93 'lt' : '042704e9', | |
94 # Do not use! This is only around for backwards | |
95 # compatibility and will be removed - use fil instead | |
96 'tl' : '0c0004b0', | |
97 'zh-CN' : '080403a8', | |
98 'zh-TW' : '040403b6', | |
99 'zh-HK' : '0c0403b6', | |
100 'el' : '040804e5', | |
101 'no' : '001404e4', | |
102 'nb' : '041404e4', | |
103 'nn' : '081404e4', | |
104 'th' : '041e036a', | |
105 'he' : '040d04e7', | |
106 'iw' : '040d04e7', | |
107 'pl' : '041504e2', | |
108 'tr' : '041f04e6', | |
109 'hr' : '041a04e4', | |
110 # No codepage for Hindi, use unicode(1200). | |
111 'hi' : '043904b0', | |
112 'pt-PT' : '081604e4', | |
113 'pt-BR' : '041604e4', | |
114 'uk' : '042204e3', | |
115 'cs' : '040504e2', | |
116 'hu' : '040e04e2', | |
117 'ro' : '041804e2', | |
118 # No codepage for Urdu, use unicode(1200). | |
119 'ur' : '042004b0', | |
120 'da' : '040604e4', | |
121 'is' : '040f04e4', | |
122 'ru' : '041904e3', | |
123 'vi' : '042a04ea', | |
124 'nl' : '041304e4', | |
125 'id' : '042104e4', | |
126 'sr' : '081a04e2', | |
127 'en-GB' : '0809040e', | |
128 'it' : '041004e4', | |
129 'sk' : '041b04e2', | |
130 'et' : '042504e9', | |
131 'ja' : '041103a4', | |
132 'sl' : '042404e2', | |
133 'en' : '040904b0', | |
134 # LCID for Mexico; Windows does not support L.A. LCID. | |
135 'es-419' : '080a04e4', | |
136 # No codepage for Bengali, use unicode(1200). | |
137 'bn' : '044504b0', | |
138 'fa' : '042904e8', | |
139 # No codepage for Gujarati, use unicode(1200). | |
140 'gu' : '044704b0', | |
141 # No codepage for Kannada, use unicode(1200). | |
142 'kn' : '044b04b0', | |
143 # Malay (Malaysia) [ms-MY] | |
144 'ms' : '043e04e4', | |
145 # No codepage for Malayalam, use unicode(1200). | |
146 'ml' : '044c04b0', | |
147 # No codepage for Marathi, use unicode(1200). | |
148 'mr' : '044e04b0', | |
149 # No codepage for Oriya , use unicode(1200). | |
150 'or' : '044804b0', | |
151 # No codepage for Tamil, use unicode(1200). | |
152 'ta' : '044904b0', | |
153 # No codepage for Telugu, use unicode(1200). | |
154 'te' : '044a04b0', | |
155 # No codepage for Amharic, use unicode(1200). >= Vista. | |
156 'am' : '045e04b0', | |
157 'sw' : '044104e4', | |
158 'af' : '043604e4', | |
159 'eu' : '042d04e4', | |
160 'fr-CA' : '0c0c04e4', | |
161 'gl' : '045604e4', | |
162 # No codepage for Zulu, use unicode(1200). | |
163 'zu' : '043504b0', | |
164 'fake-bidi' : '040d04e7', | |
165 } | |
166 | |
167 # Language ID resource: http://msdn.microsoft.com/en-us/library/ms776294.aspx | |
168 # | |
169 # There is no appropriate sublang for Spanish (Latin America) [es-419], so we | |
170 # use Mexico. SUBLANG_DEFAULT would incorrectly map to Spain. Unlike other | |
171 # Latin American countries, Mexican Spanish is supported by VERSIONINFO: | |
172 # http://msdn.microsoft.com/en-us/library/aa381058.aspx | |
173 | |
174 _LANGUAGE_DIRECTIVE_PAIR = { | |
175 'neutral' : 'LANG_NEUTRAL, SUBLANG_NEUTRAL', | |
176 'userdefault' : 'LANG_NEUTRAL, SUBLANG_DEFAULT', | |
177 'ar' : 'LANG_ARABIC, SUBLANG_DEFAULT', | |
178 'fi' : 'LANG_FINNISH, SUBLANG_DEFAULT', | |
179 'ko' : 'LANG_KOREAN, SUBLANG_KOREAN', | |
180 'es' : 'LANG_SPANISH, SUBLANG_SPANISH_MODERN', | |
181 'bg' : 'LANG_BULGARIAN, SUBLANG_DEFAULT', | |
182 # LANG_FILIPINO (100) not in VC 7 winnt.h. | |
183 'fil' : '100, SUBLANG_DEFAULT', | |
184 'fr' : 'LANG_FRENCH, SUBLANG_FRENCH', | |
185 'lv' : 'LANG_LATVIAN, SUBLANG_DEFAULT', | |
186 'sv' : 'LANG_SWEDISH, SUBLANG_SWEDISH', | |
187 'ca' : 'LANG_CATALAN, SUBLANG_DEFAULT', | |
188 'de' : 'LANG_GERMAN, SUBLANG_GERMAN', | |
189 'lt' : 'LANG_LITHUANIAN, SUBLANG_LITHUANIAN', | |
190 # Do not use! See above. | |
191 'tl' : 'LANG_NEUTRAL, SUBLANG_DEFAULT', | |
192 'zh-CN' : 'LANG_CHINESE, SUBLANG_CHINESE_SIMPLIFIED', | |
193 'zh-TW' : 'LANG_CHINESE, SUBLANG_CHINESE_TRADITIONAL', | |
194 'zh-HK' : 'LANG_CHINESE, SUBLANG_CHINESE_HONGKONG', | |
195 'el' : 'LANG_GREEK, SUBLANG_DEFAULT', | |
196 'no' : 'LANG_NORWEGIAN, SUBLANG_DEFAULT', | |
197 'nb' : 'LANG_NORWEGIAN, SUBLANG_NORWEGIAN_BOKMAL', | |
198 'nn' : 'LANG_NORWEGIAN, SUBLANG_NORWEGIAN_NYNORSK', | |
199 'th' : 'LANG_THAI, SUBLANG_DEFAULT', | |
200 'he' : 'LANG_HEBREW, SUBLANG_DEFAULT', | |
201 'iw' : 'LANG_HEBREW, SUBLANG_DEFAULT', | |
202 'pl' : 'LANG_POLISH, SUBLANG_DEFAULT', | |
203 'tr' : 'LANG_TURKISH, SUBLANG_DEFAULT', | |
204 'hr' : 'LANG_CROATIAN, SUBLANG_DEFAULT', | |
205 'hi' : 'LANG_HINDI, SUBLANG_DEFAULT', | |
206 'pt-PT' : 'LANG_PORTUGUESE, SUBLANG_PORTUGUESE', | |
207 'pt-BR' : 'LANG_PORTUGUESE, SUBLANG_DEFAULT', | |
208 'uk' : 'LANG_UKRAINIAN, SUBLANG_DEFAULT', | |
209 'cs' : 'LANG_CZECH, SUBLANG_DEFAULT', | |
210 'hu' : 'LANG_HUNGARIAN, SUBLANG_DEFAULT', | |
211 'ro' : 'LANG_ROMANIAN, SUBLANG_DEFAULT', | |
212 'ur' : 'LANG_URDU, SUBLANG_DEFAULT', | |
213 'da' : 'LANG_DANISH, SUBLANG_DEFAULT', | |
214 'is' : 'LANG_ICELANDIC, SUBLANG_DEFAULT', | |
215 'ru' : 'LANG_RUSSIAN, SUBLANG_DEFAULT', | |
216 'vi' : 'LANG_VIETNAMESE, SUBLANG_DEFAULT', | |
217 'nl' : 'LANG_DUTCH, SUBLANG_DEFAULT', | |
218 'id' : 'LANG_INDONESIAN, SUBLANG_DEFAULT', | |
219 'sr' : 'LANG_SERBIAN, SUBLANG_SERBIAN_LATIN', | |
220 'en-GB' : 'LANG_ENGLISH, SUBLANG_ENGLISH_UK', | |
221 'it' : 'LANG_ITALIAN, SUBLANG_DEFAULT', | |
222 'sk' : 'LANG_SLOVAK, SUBLANG_DEFAULT', | |
223 'et' : 'LANG_ESTONIAN, SUBLANG_DEFAULT', | |
224 'ja' : 'LANG_JAPANESE, SUBLANG_DEFAULT', | |
225 'sl' : 'LANG_SLOVENIAN, SUBLANG_DEFAULT', | |
226 'en' : 'LANG_ENGLISH, SUBLANG_ENGLISH_US', | |
227 # No L.A. sublang exists. | |
228 'es-419' : 'LANG_SPANISH, SUBLANG_SPANISH_MEXICAN', | |
229 'bn' : 'LANG_BENGALI, SUBLANG_DEFAULT', | |
230 'fa' : 'LANG_PERSIAN, SUBLANG_DEFAULT', | |
231 'gu' : 'LANG_GUJARATI, SUBLANG_DEFAULT', | |
232 'kn' : 'LANG_KANNADA, SUBLANG_DEFAULT', | |
233 'ms' : 'LANG_MALAY, SUBLANG_DEFAULT', | |
234 'ml' : 'LANG_MALAYALAM, SUBLANG_DEFAULT', | |
235 'mr' : 'LANG_MARATHI, SUBLANG_DEFAULT', | |
236 'or' : 'LANG_ORIYA, SUBLANG_DEFAULT', | |
237 'ta' : 'LANG_TAMIL, SUBLANG_DEFAULT', | |
238 'te' : 'LANG_TELUGU, SUBLANG_DEFAULT', | |
239 'am' : 'LANG_AMHARIC, SUBLANG_DEFAULT', | |
240 'sw' : 'LANG_SWAHILI, SUBLANG_DEFAULT', | |
241 'af' : 'LANG_AFRIKAANS, SUBLANG_DEFAULT', | |
242 'eu' : 'LANG_BASQUE, SUBLANG_DEFAULT', | |
243 'fr-CA' : 'LANG_FRENCH, SUBLANG_FRENCH_CANADIAN', | |
244 'gl' : 'LANG_GALICIAN, SUBLANG_DEFAULT', | |
245 'zu' : 'LANG_ZULU, SUBLANG_DEFAULT', | |
246 'pa' : 'LANG_PUNJABI, SUBLANG_PUNJABI_INDIA', | |
247 'sa' : 'LANG_SANSKRIT, SUBLANG_SANSKRIT_INDIA', | |
248 'si' : 'LANG_SINHALESE, SUBLANG_SINHALESE_SRI_LANKA', | |
249 'ne' : 'LANG_NEPALI, SUBLANG_NEPALI_NEPAL', | |
250 'ti' : 'LANG_TIGRIGNA, SUBLANG_TIGRIGNA_ERITREA', | |
251 'fake-bidi' : 'LANG_HEBREW, SUBLANG_DEFAULT', | |
252 } | |
253 | |
254 # A note on 'no-specific-language' in the following few functions: | |
255 # Some build systems may wish to call GRIT to scan for dependencies in | |
256 # a language-agnostic way, and can then specify this fake language as | |
257 # the output context. It should never be used when output is actually | |
258 # being generated. | |
259 | |
260 def GetLangCharsetPair(language): | |
261 if _LANGUAGE_CHARSET_PAIR.has_key(language): | |
262 return _LANGUAGE_CHARSET_PAIR[language] | |
263 elif language == 'no-specific-language': | |
264 return '' | |
265 else: | |
266 print 'Warning:GetLangCharsetPair() found undefined language %s' %(language) | |
267 return '' | |
268 | |
269 def GetLangDirectivePair(language): | |
270 if _LANGUAGE_DIRECTIVE_PAIR.has_key(language): | |
271 return _LANGUAGE_DIRECTIVE_PAIR[language] | |
272 else: | |
273 # We don't check for 'no-specific-language' here because this | |
274 # function should only get called when output is being formatted, | |
275 # and at that point we would not want to get | |
276 # 'no-specific-language' passed as the language. | |
277 print ('Warning:GetLangDirectivePair() found undefined language %s' % | |
278 language) | |
279 return 'unknown language: see tools/grit/format/rc.py' | |
280 | |
281 def GetLangIdHex(language): | |
282 if _LANGUAGE_CHARSET_PAIR.has_key(language): | |
283 langcharset = _LANGUAGE_CHARSET_PAIR[language] | |
284 lang_id = '0x' + langcharset[0:4] | |
285 return lang_id | |
286 elif language == 'no-specific-language': | |
287 return '' | |
288 else: | |
289 print 'Warning:GetLangIdHex() found undefined language %s' %(language) | |
290 return '' | |
291 | |
292 | |
293 def GetCharsetIdDecimal(language): | |
294 if _LANGUAGE_CHARSET_PAIR.has_key(language): | |
295 langcharset = _LANGUAGE_CHARSET_PAIR[language] | |
296 charset_decimal = int(langcharset[4:], 16) | |
297 return str(charset_decimal) | |
298 elif language == 'no-specific-language': | |
299 return '' | |
300 else: | |
301 print 'Warning:GetCharsetIdDecimal() found undefined language %s' % language | |
302 return '' | |
303 | |
304 | |
305 def GetUnifiedLangCode(language) : | |
306 r = re.compile('([a-z]{1,2})_([a-z]{1,2})') | |
307 if r.match(language) : | |
308 underscore = language.find('_') | |
309 return language[0:underscore] + '-' + language[underscore + 1:].upper() | |
310 else : | |
311 return language | |
312 | |
313 | |
314 def RcSubstitutions(substituter, lang): | |
315 '''Add language-based substitutions for Rc files to the substitutor.''' | |
316 unified_lang_code = GetUnifiedLangCode(lang) | |
317 substituter.AddSubstitutions({ | |
318 'GRITVERLANGCHARSETHEX': GetLangCharsetPair(unified_lang_code), | |
319 'GRITVERLANGID': GetLangIdHex(unified_lang_code), | |
320 'GRITVERCHARSETID': GetCharsetIdDecimal(unified_lang_code)}) | |
321 | |
322 | |
323 def _FormatHeader(root, lang, output_dir): | |
324 '''Returns the required preamble for RC files.''' | |
325 assert isinstance(lang, types.StringTypes) | |
326 assert isinstance(root, misc.GritNode) | |
327 # Find the location of the resource header file, so that we can include | |
328 # it. | |
329 resource_header = 'resource.h' # fall back to this | |
330 language_directive = '' | |
331 for output in root.GetOutputFiles(): | |
332 if output.attrs['type'] == 'rc_header': | |
333 resource_header = os.path.abspath(output.GetOutputFilename()) | |
334 resource_header = util.MakeRelativePath(output_dir, resource_header) | |
335 if output.attrs['lang'] != lang: | |
336 continue | |
337 if output.attrs['language_section'] == '': | |
338 # If no language_section is requested, no directive is added | |
339 # (Used when the generated rc will be included from another rc | |
340 # file that will have the appropriate language directive) | |
341 language_directive = '' | |
342 elif output.attrs['language_section'] == 'neutral': | |
343 # If a neutral language section is requested (default), add a | |
344 # neutral language directive | |
345 language_directive = 'LANGUAGE LANG_NEUTRAL, SUBLANG_NEUTRAL' | |
346 elif output.attrs['language_section'] == 'lang': | |
347 language_directive = 'LANGUAGE %s' % GetLangDirectivePair(lang) | |
348 resource_header = resource_header.replace('\\', '\\\\') | |
349 return '''// This file is automatically generated by GRIT. Do not edit. | |
350 | |
351 #include "%s" | |
352 #include <winresrc.h> | |
353 #ifdef IDC_STATIC | |
354 #undef IDC_STATIC | |
355 #endif | |
356 #define IDC_STATIC (-1) | |
357 | |
358 %s | |
359 | |
360 | |
361 ''' % (resource_header, language_directive) | |
362 # end _FormatHeader() function | |
363 | |
364 | |
365 def FormatMessage(item, lang): | |
366 '''Returns a single message of a string table.''' | |
367 message = item.ws_at_start + item.Translate(lang) + item.ws_at_end | |
368 # Escape quotation marks (RC format uses doubling-up | |
369 message = message.replace('"', '""') | |
370 # Replace linebreaks with a \n escape | |
371 message = util.LINEBREAKS.sub(r'\\n', message) | |
372 if hasattr(item.GetRoot(), 'GetSubstituter'): | |
373 substituter = item.GetRoot().GetSubstituter() | |
374 message = substituter.Substitute(message) | |
375 | |
376 name_attr = item.GetTextualIds()[0] | |
377 | |
378 return ' %-15s "%s"\n' % (name_attr, message) | |
379 | |
380 | |
381 def _FormatSection(item, lang, output_dir): | |
382 '''Writes out an .rc file section.''' | |
383 assert isinstance(lang, types.StringTypes) | |
384 from grit.node import structure | |
385 assert isinstance(item, structure.StructureNode) | |
386 | |
387 if item.IsExcludedFromRc(): | |
388 return '' | |
389 else: | |
390 text = item.gatherer.Translate( | |
391 lang, skeleton_gatherer=item.GetSkeletonGatherer(), | |
392 pseudo_if_not_available=item.PseudoIsAllowed(), | |
393 fallback_to_english=item.ShouldFallbackToEnglish()) + '\n\n' | |
394 | |
395 # Replace the language expand_variables in version rc info. | |
396 if item.ExpandVariables() and hasattr(item.GetRoot(), 'GetSubstituter'): | |
397 substituter = item.GetRoot().GetSubstituter() | |
398 text = substituter.Substitute(text) | |
399 | |
400 return text | |
401 | |
402 | |
403 def FormatInclude(item, lang, output_dir, type=None, process_html=False): | |
404 '''Formats an item that is included in an .rc file (e.g. an ICON). | |
405 | |
406 Args: | |
407 item: an IncludeNode or StructureNode | |
408 lang, output_dir: standard formatter parameters | |
409 type: .rc file resource type, e.g. 'ICON' (ignored unless item is a | |
410 StructureNode) | |
411 process_html: False/True (ignored unless item is a StructureNode) | |
412 ''' | |
413 assert isinstance(lang, types.StringTypes) | |
414 from grit.node import structure | |
415 from grit.node import include | |
416 assert isinstance(item, (structure.StructureNode, include.IncludeNode)) | |
417 | |
418 if isinstance(item, include.IncludeNode): | |
419 type = item.attrs['type'].upper() | |
420 process_html = item.attrs['flattenhtml'] == 'true' | |
421 filename_only = item.attrs['filenameonly'] == 'true' | |
422 relative_path = item.attrs['relativepath'] == 'true' | |
423 else: | |
424 assert (isinstance(item, structure.StructureNode) and item.attrs['type'] in | |
425 ['admin_template', 'chrome_html', 'chrome_scaled_image', 'igoogle', | |
426 'muppet', 'tr_html', 'txt']) | |
427 filename_only = False | |
428 relative_path = False | |
429 | |
430 # By default, we use relative pathnames to included resources so that | |
431 # sharing the resulting .rc files is possible. | |
432 # | |
433 # The FileForLanguage() Function has the side effect of generating the file | |
434 # if needed (e.g. if it is an HTML file include). | |
435 file_for_lang = item.FileForLanguage(lang, output_dir) | |
436 if file_for_lang is None: | |
437 return '' | |
438 | |
439 filename = os.path.abspath(file_for_lang) | |
440 if process_html: | |
441 filename = item.Process(output_dir) | |
442 elif filename_only: | |
443 filename = os.path.basename(filename) | |
444 elif relative_path: | |
445 filename = util.MakeRelativePath(output_dir, filename) | |
446 | |
447 filename = filename.replace('\\', '\\\\') # escape for the RC format | |
448 | |
449 if isinstance(item, structure.StructureNode) and item.IsExcludedFromRc(): | |
450 return '' | |
451 else: | |
452 return '%-18s %-18s "%s"\n' % (item.attrs['name'], type, filename) | |
453 | |
454 | |
455 def _DoNotFormat(item, lang, output_dir): | |
456 return '' | |
457 | |
458 | |
459 # Formatter instance to use for each type attribute | |
460 # when formatting Structure nodes. | |
461 _STRUCTURE_FORMATTERS = { | |
462 'accelerators' : _FormatSection, | |
463 'dialog' : _FormatSection, | |
464 'menu' : _FormatSection, | |
465 'rcdata' : _FormatSection, | |
466 'version' : _FormatSection, | |
467 'admin_template' : partial(FormatInclude, type='ADM'), | |
468 'chrome_html' : partial(FormatInclude, type='BINDATA', | |
469 process_html=True), | |
470 'chrome_scaled_image' : partial(FormatInclude, type='BINDATA'), | |
471 'igoogle' : partial(FormatInclude, type='XML'), | |
472 'muppet' : partial(FormatInclude, type='XML'), | |
473 'tr_html' : partial(FormatInclude, type='HTML'), | |
474 'txt' : partial(FormatInclude, type='TXT'), | |
475 'policy_template_metafile': _DoNotFormat, | |
476 } | |
477 | |
478 | |
479 def FormatStructure(item, lang, output_dir): | |
480 formatter = _STRUCTURE_FORMATTERS[item.attrs['type']] | |
481 return formatter(item, lang, output_dir) | |
OLD | NEW |