| // Copyright 2022 The Chromium Authors |
| // Use of this source code is governed by a BSD-style license that can be |
| // found in the LICENSE file. |
| |
| #import <Foundation/Foundation.h> |
| |
| #import <stdio.h> |
| #import <unistd.h> |
| |
| #import <string> |
| |
| #import "base/apple/foundation_util.h" |
| #import "base/files/file.h" |
| #import "base/files/file_path.h" |
| #import "base/files/file_util.h" |
| #import "base/strings/string_piece.h" |
| #import "base/strings/string_util.h" |
| #import "base/strings/sys_string_conversions.h" |
| #import "ios/chrome/tools/strings/grit_header_parsing.h" |
| |
| namespace { |
| |
| using PList = NSDictionary<NSString*, NSObject*>; |
| |
| const char kUsageString[] = R"( |
| usage: substitute_strings_identifier -I header_path -i source_path -i output_path |
| |
| Loads the plist at `source_path` and replace all string values corresponding |
| to string identifiers to their numerical values (as found in `header_path`) |
| and write the resulting plist to `output_path`. |
| )"; |
| |
| NSObject* ConvertValue(NSObject* value, const ResourceMap& resource_map); |
| |
| PList* ConvertPlist(PList* plist, const ResourceMap& resource_map) { |
| NSMutableDictionary* converted = [[NSMutableDictionary alloc] init]; |
| for (NSString* key in plist) { |
| NSObject* object = ConvertValue(plist[key], resource_map); |
| if (!object) { |
| return nil; |
| } |
| |
| converted[key] = object; |
| } |
| return [converted copy]; |
| } |
| |
| NSObject* ConvertString(NSString* string, const ResourceMap& resource_map) { |
| const std::string key = base::SysNSStringToUTF8(string); |
| auto iter = resource_map.find(key); |
| if (iter == resource_map.end()) { |
| if (base::StartsWith(key, "IDS_") || base::StartsWith(key, "IDR_")) { |
| fprintf(stderr, "ERROR: no value found for string: %s\n", key.c_str()); |
| return nil; |
| } |
| |
| return string; |
| } |
| |
| return [NSNumber numberWithInt:iter->second]; |
| } |
| |
| NSArray* ConvertArray(NSArray* array, const ResourceMap& resource_map) { |
| NSMutableArray* converted = [[NSMutableArray alloc] init]; |
| for (NSObject* value in array) { |
| NSObject* object = ConvertValue(value, resource_map); |
| if (!object) { |
| return nil; |
| } |
| |
| [converted addObject:object]; |
| } |
| return [converted copy]; |
| } |
| |
| NSObject* ConvertValue(NSObject* value, const ResourceMap& resource_map) { |
| if ([value isKindOfClass:[NSString class]]) { |
| NSString* string = base::apple::ObjCCastStrict<NSString>(value); |
| return ConvertString(string, resource_map); |
| } |
| |
| if ([value isKindOfClass:[NSArray class]]) { |
| NSArray<NSObject*>* array = base::apple::ObjCCastStrict<NSArray>(value); |
| return ConvertArray(array, resource_map); |
| } |
| |
| if ([value isKindOfClass:[NSDictionary class]]) { |
| PList* plist = base::apple::ObjCCastStrict<NSDictionary>(value); |
| return ConvertPlist(plist, resource_map); |
| } |
| |
| return value; |
| } |
| |
| bool ConvertFile(const base::FilePath& source_path, |
| const base::FilePath& output_path, |
| const ResourceMap& resource_map) { |
| NSURL* source_url = base::apple::FilePathToNSURL(source_path); |
| NSURL* output_url = base::apple::FilePathToNSURL(output_path); |
| |
| NSError* error = nil; |
| PList* source_plist = [NSDictionary dictionaryWithContentsOfURL:source_url |
| error:&error]; |
| if (error) { |
| fprintf(stderr, "ERROR: loading %s failed: %s\n", |
| source_path.AsUTF8Unsafe().c_str(), |
| base::SysNSStringToUTF8(error.localizedDescription).c_str()); |
| return false; |
| } |
| |
| PList* output_plist = ConvertPlist(source_plist, resource_map); |
| if (!output_plist) { |
| return false; |
| } |
| |
| base::File::Error file_error; |
| const base::FilePath output_dir = output_path.DirName(); |
| if (!base::CreateDirectoryAndGetError(output_dir, &file_error)) { |
| fprintf(stderr, "ERROR: creating %s failed: %s\n", |
| output_dir.AsUTF8Unsafe().c_str(), |
| base::File::ErrorToString(file_error).c_str()); |
| return false; |
| } |
| |
| if (![output_plist writeToURL:output_url error:&error]) { |
| fprintf(stderr, "ERROR: writing %s failed: %s\n", |
| output_path.AsUTF8Unsafe().c_str(), |
| base::SysNSStringToUTF8(error.localizedDescription).c_str()); |
| return false; |
| } |
| |
| return true; |
| } |
| |
| int RealMain(int argc, char* const argv[]) { |
| base::FilePath source_path; |
| base::FilePath output_path; |
| std::vector<base::FilePath> headers; |
| |
| int ch = 0; |
| while ((ch = getopt(argc, argv, "I:i:o:h")) != -1) { |
| switch (ch) { |
| case 'I': |
| headers.push_back(base::FilePath(optarg)); |
| break; |
| |
| case 'i': |
| if (!source_path.empty()) { |
| fprintf(stderr, "ERROR: cannot pass -i multiple times\n"); |
| return 1; |
| } |
| |
| source_path = base::FilePath(optarg); |
| break; |
| |
| case 'o': |
| if (!output_path.empty()) { |
| fprintf(stderr, "ERROR: cannot pass -o multiple times\n"); |
| return 1; |
| } |
| |
| output_path = base::FilePath(optarg); |
| break; |
| |
| case 'h': |
| fprintf(stdout, "%s", kUsageString + 1); |
| return 0; |
| |
| default: |
| fprintf(stderr, "ERROR: unknown argument: -%c\n", ch); |
| return 1; |
| } |
| } |
| |
| if (headers.empty()) { |
| fprintf(stderr, "ERROR: header_path is required.\n"); |
| return 1; |
| } |
| |
| if (source_path.empty()) { |
| fprintf(stderr, "ERROR: source_path is required.\n"); |
| return 1; |
| } |
| |
| if (output_path.empty()) { |
| fprintf(stderr, "ERROR: output_path is required.\n"); |
| return 1; |
| } |
| |
| absl::optional<ResourceMap> resource_map = |
| LoadResourcesFromGritHeaders(headers); |
| |
| if (!resource_map) { |
| return 1; |
| } |
| |
| if (!ConvertFile(source_path, output_path, *resource_map)) { |
| return 1; |
| } |
| |
| return 0; |
| } |
| |
| } // anonymous namespace |
| |
| int main(int argc, char* const argv[]) { |
| @autoreleasepool { |
| return RealMain(argc, argv); |
| } |
| } |