Make hitting "Enter" submit the add/change profile dialog.
[chromium-blink-merge.git] / chrome / tools / mac_helpers / infoplist_strings_util.mm
blob508c5b754a73e9d7a76b4d76e7b1e6e6cfe32c07
1 // Copyright (c) 2012 The Chromium Authors. All rights reserved.
2 // Use of this source code is governed by a BSD-style license that can be
3 // found in the LICENSE file.
5 // Helper tool that is built and run during a build to pull strings from
6 // the GRD files and generate the InfoPlist.strings files needed for
7 // Mac OS X app bundles.
9 #import <Foundation/Foundation.h>
11 #include <stdio.h>
12 #include <unistd.h>
14 #include "base/file_path.h"
15 #include "base/mac/scoped_nsautorelease_pool.h"
16 #include "base/memory/scoped_ptr.h"
17 #include "base/string_piece.h"
18 #include "base/string_util.h"
19 #include "grit/chromium_strings.h"
20 #include "ui/base/resource/data_pack.h"
22 namespace {
24 NSString* ApplicationVersionString(const char* version_file_path) {
25   NSError* error = nil;
26   NSString* path_string = [NSString stringWithUTF8String:version_file_path];
27   NSString* version_file =
28       [NSString stringWithContentsOfFile:path_string
29                                 encoding:NSUTF8StringEncoding
30                                    error:&error];
31   if (!version_file || error) {
32     fprintf(stderr, "Failed to load version file: %s\n",
33             [[error description] UTF8String]);
34     return nil;
35   }
37   int major = 0, minor = 0, build = 0, patch = 0;
38   NSScanner* scanner = [NSScanner scannerWithString:version_file];
39   if ([scanner scanString:@"MAJOR=" intoString:nil] &&
40       [scanner scanInt:&major] &&
41       [scanner scanString:@"MINOR=" intoString:nil] &&
42       [scanner scanInt:&minor] &&
43       [scanner scanString:@"BUILD=" intoString:nil] &&
44       [scanner scanInt:&build] &&
45       [scanner scanString:@"PATCH=" intoString:nil] &&
46       [scanner scanInt:&patch]) {
47     return [NSString stringWithFormat:@"%d.%d.%d.%d",
48             major, minor, build, patch];
49   }
50   fprintf(stderr, "Failed to parse version file\n");
51   return nil;
54 ui::DataPack* LoadResourceDataPack(const char* dir_path,
55                                    const char* branding_strings_name,
56                                    const char* locale_name) {
57   ui::DataPack* resource_pack = NULL;
59   NSString* resource_path = [NSString stringWithFormat:@"%s/%s_%s.pak",
60                              dir_path, branding_strings_name, locale_name];
61   if (resource_path) {
62     FilePath resources_pak_path([resource_path fileSystemRepresentation]);
63     resource_pack = new ui::DataPack(ui::SCALE_FACTOR_100P);
64     bool success = resource_pack->LoadFromPath(resources_pak_path);
65     if (!success) {
66       delete resource_pack;
67       resource_pack = NULL;
68     }
69   }
71   return resource_pack;
74 NSString* LoadStringFromDataPack(ui::DataPack* data_pack,
75                                  const char* data_pack_lang,
76                                  uint32_t resource_id,
77                                  const char* resource_id_str) {
78   NSString* result = nil;
79   base::StringPiece data;
80   if (data_pack->GetStringPiece(resource_id, &data)) {
81     // Data pack encodes strings as either UTF8 or UTF16.
82     if (data_pack->GetTextEncodingType() == ui::DataPack::UTF8) {
83       result =
84           [[[NSString alloc] initWithBytes:data.data()
85                                     length:data.length()
86                                   encoding:NSUTF8StringEncoding]
87            autorelease];
88     } else if (data_pack->GetTextEncodingType() == ui::DataPack::UTF16) {
89       result =
90           [[[NSString alloc] initWithBytes:data.data()
91                                     length:data.length()
92                                   encoding:NSUTF16LittleEndianStringEncoding]
93            autorelease];
94     } else {
95       fprintf(stderr, "ERROR: requested string %s from binary data pack\n",
96               resource_id_str);
97       exit(1);
98     }
99   }
100   if (!result) {
101     fprintf(stderr, "ERROR: failed to load string %s for lang %s\n",
102             resource_id_str, data_pack_lang);
103     exit(1);
104   }
105   return result;
108 // Escape quotes, newlines, etc so there are no errors when the strings file
109 // is parsed.
110 NSString* EscapeForStringsFileValue(NSString* str) {
111   NSMutableString* worker = [NSMutableString stringWithString:str];
113   // Since this is a build tool, we don't really worry about making this
114   // the most efficient code.
116   // Backslash first since we need to do it before we put in all the others
117   [worker replaceOccurrencesOfString:@"\\"
118                           withString:@"\\\\"
119                              options:NSLiteralSearch
120                                range:NSMakeRange(0, [worker length])];
121   // Now the rest of them.
122   [worker replaceOccurrencesOfString:@"\n"
123                           withString:@"\\n"
124                              options:NSLiteralSearch
125                                range:NSMakeRange(0, [worker length])];
126   [worker replaceOccurrencesOfString:@"\r"
127                           withString:@"\\r"
128                              options:NSLiteralSearch
129                                range:NSMakeRange(0, [worker length])];
130   [worker replaceOccurrencesOfString:@"\t"
131                           withString:@"\\t"
132                              options:NSLiteralSearch
133                                range:NSMakeRange(0, [worker length])];
134   [worker replaceOccurrencesOfString:@"\""
135                           withString:@"\\\""
136                              options:NSLiteralSearch
137                                range:NSMakeRange(0, [worker length])];
139   return [[worker copy] autorelease];
142 // The valid types for the -t arg
143 const char* kAppType_Main = "main";  // Main app
144 const char* kAppType_Helper = "helper";  // Helper app
146 }  // namespace
148 int main(int argc, char* const argv[]) {
149   base::mac::ScopedNSAutoreleasePool autorelease_pool;
151   const char* version_file_path = NULL;
152   const char* grit_output_dir = NULL;
153   const char* branding_strings_name = NULL;
154   const char* output_dir = NULL;
155   const char* app_type = kAppType_Main;
157   // Process the args
158   int ch;
159   while ((ch = getopt(argc, argv, "t:v:g:b:o:")) != -1) {
160     switch (ch) {
161       case 't':
162         app_type = optarg;
163         break;
164       case 'v':
165         version_file_path = optarg;
166         break;
167       case 'g':
168         grit_output_dir = optarg;
169         break;
170       case 'b':
171         branding_strings_name = optarg;
172         break;
173       case 'o':
174         output_dir = optarg;
175         break;
176       default:
177         fprintf(stderr, "ERROR: bad command line arg\n");
178         exit(1);
179         break;
180     }
181   }
182   argc -= optind;
183   argv += optind;
185 #define CHECK_ARG(a, b) \
186   do { \
187     if ((a)) { \
188       fprintf(stderr, "ERROR: " b "\n"); \
189       exit(1); \
190     } \
191   } while (false)
193   // Check our args
194   CHECK_ARG(!version_file_path, "Missing VERSION file path");
195   CHECK_ARG(!grit_output_dir, "Missing grit output dir path");
196   CHECK_ARG(!output_dir, "Missing path to write InfoPlist.strings files");
197   CHECK_ARG(!branding_strings_name, "Missing branding strings file name");
198   CHECK_ARG(argc == 0, "Missing language list");
199   CHECK_ARG((strcmp(app_type, kAppType_Main) != 0 &&
200              strcmp(app_type, kAppType_Helper) != 0),
201             "Unknown app type");
203   char* const* lang_list = argv;
204   int lang_list_count = argc;
206   // Parse the version file and build our string
207   NSString* version_string = ApplicationVersionString(version_file_path);
208   if (!version_string) {
209     fprintf(stderr, "ERROR: failed to get a version string");
210     exit(1);
211   }
213   NSFileManager* fm = [NSFileManager defaultManager];
215   for (int loop = 0; loop < lang_list_count; ++loop) {
216     const char* cur_lang = lang_list[loop];
218     // Open the branded string pak file
219     scoped_ptr<ui::DataPack> branded_data_pack(
220         LoadResourceDataPack(grit_output_dir,
221                              branding_strings_name,
222                              cur_lang));
223     if (branded_data_pack.get() == NULL) {
224       fprintf(stderr, "ERROR: Failed to load branded pak for language: %s\n",
225               cur_lang);
226       exit(1);
227     }
229     uint32_t name_id = IDS_PRODUCT_NAME;
230     const char* name_id_str = "IDS_PRODUCT_NAME";
231     uint32_t short_name_id = IDS_APP_MENU_PRODUCT_NAME;
232     const char* short_name_id_str = "IDS_APP_MENU_PRODUCT_NAME";
233     if (strcmp(app_type, kAppType_Helper) == 0) {
234       name_id = IDS_HELPER_NAME;
235       name_id_str = "IDS_HELPER_NAME";
236       short_name_id = IDS_SHORT_HELPER_NAME;
237       short_name_id_str = "IDS_SHORT_HELPER_NAME";
238     }
240     // Fetch the strings
241     NSString* name =
242           LoadStringFromDataPack(branded_data_pack.get(), cur_lang,
243                                  name_id, name_id_str);
244     NSString* short_name =
245           LoadStringFromDataPack(branded_data_pack.get(), cur_lang,
246                                  short_name_id, short_name_id_str);
247     NSString* copyright =
248         LoadStringFromDataPack(branded_data_pack.get(), cur_lang,
249                                IDS_ABOUT_VERSION_COPYRIGHT,
250                                "IDS_ABOUT_VERSION_COPYRIGHT");
252     // For now, assume this is ok for all languages. If we need to, this could
253     // be moved into generated_resources.grd and fetched.
254     NSString *get_info = [NSString stringWithFormat:@"%@ %@, %@",
255                           name, version_string, copyright];
257     // Generate the InfoPlist.strings file contents
258     NSString* strings_file_contents_string =
259         [NSString stringWithFormat:
260           @"CFBundleDisplayName = \"%@\";\n"
261           @"CFBundleGetInfoString = \"%@\";\n"
262           @"CFBundleName = \"%@\";\n"
263           @"NSHumanReadableCopyright = \"%@\";\n",
264           EscapeForStringsFileValue(name),
265           EscapeForStringsFileValue(get_info),
266           EscapeForStringsFileValue(short_name),
267           EscapeForStringsFileValue(copyright)];
269     // We set up Xcode projects expecting strings files to be UTF8, so make
270     // sure we write the data in that form.  When Xcode copies them it will
271     // put them final runtime encoding.
272     NSData* strings_file_contents_utf8 =
273         [strings_file_contents_string dataUsingEncoding:NSUTF8StringEncoding];
275     if ([strings_file_contents_utf8 length] == 0) {
276       fprintf(stderr, "ERROR: failed to get the utf8 encoding of the strings "
277               "file for language: %s\n", cur_lang);
278       exit(1);
279     }
281     // For Cocoa to find the locale at runtime, it needs to use '_' instead of
282     // '-' (http://crbug.com/20441).  Also, 'en-US' should be represented
283     // simply as 'en' (http://crbug.com/19165, http://crbug.com/25578).
284     NSString* cur_lang_ns = [NSString stringWithUTF8String:cur_lang];
285     if ([cur_lang_ns isEqualToString:@"en-US"]) {
286       cur_lang_ns = @"en";
287     }
288     cur_lang_ns = [cur_lang_ns stringByReplacingOccurrencesOfString:@"-"
289                                                          withString:@"_"];
290     // Make sure the lproj we write to exists
291     NSString *lproj_name = [NSString stringWithFormat:@"%@.lproj", cur_lang_ns];
292     NSString *output_path =
293         [[NSString stringWithUTF8String:output_dir]
294          stringByAppendingPathComponent:lproj_name];
295     NSError* error = nil;
296     if (![fm fileExistsAtPath:output_path] &&
297         ![fm createDirectoryAtPath:output_path
298         withIntermediateDirectories:YES
299                         attributes:nil
300                              error:&error]) {
301       fprintf(stderr, "ERROR: '%s' didn't exist or we failed to create it\n",
302               [output_path UTF8String]);
303       exit(1);
304     }
306     // Write out the file
307     output_path =
308         [output_path stringByAppendingPathComponent:@"InfoPlist.strings"];
309     if (![strings_file_contents_utf8 writeToFile:output_path
310                                       atomically:YES]) {
311       fprintf(stderr, "ERROR: Failed to write out '%s'\n",
312               [output_path UTF8String]);
313       exit(1);
314     }
315   }
316   return 0;