1212#import < objc/runtime.h>
1313#import " XCDUndocumentedChecker.h"
1414#import " XMLPlistDecoder.h"
15+ #import " Xcproj+FileUtils.h"
1516
1617@implementation Xcproj
1718{
@@ -203,25 +204,28 @@ - (void) application:(DDCliApplication *)app willParseOptions:(DDGetoptLongParse
203204 [optionsParser addOptionsFromTable: optionTable];
204205}
205206
206- - (void ) setProject : (NSString *)projectName
207+ - (void ) setProject : (NSString *)projectPath
207208{
208209 [self .class initializeXcproj ];
210+
211+ if (![PBXProject isProjectWrapperExtension: [projectPath pathExtension ]])
212+ @throw [DDCliParseException parseExceptionWithReason: [NSString stringWithFormat: @" The project name %@ does not have a valid extension." , projectPath] exitCode: EX_USAGE];
209213
210- if (![PBXProject isProjectWrapperExtension: [projectName pathExtension ]])
211- @throw [DDCliParseException parseExceptionWithReason: [ NSString stringWithFormat: @" The project name %@ does not have a valid extension. " , projectName] exitCode: EX_USAGE ];
212-
213- NSString *projectPath = projectName;
214- if (![projectName isAbsolutePath ])
215- projectPath = [[[ NSFileManager defaultManager ] currentDirectoryPath ] stringByAppendingPathComponent: projectName];
216-
214+ if (![projectPath isAbsolutePath ]){
215+ projectPath = [[[ NSFileManager defaultManager ] currentDirectoryPath ] stringByAppendingPathComponent: projectPath ];
216+ } else {
217+ // change current path
218+ [[ NSFileManager defaultManager ] changeCurrentDirectoryPath: [[projectPath stringByStandardizingPath ] stringByDeletingLastPathComponent ]];
219+ }
220+
217221 if (![[NSFileManager defaultManager ] fileExistsAtPath: projectPath])
218- @throw [DDCliParseException parseExceptionWithReason: [NSString stringWithFormat: @" The project %@ does not exist in this directory." , projectName ] exitCode: EX_NOINPUT];
219-
222+ @throw [DDCliParseException parseExceptionWithReason: [NSString stringWithFormat: @" The project %@ does not exist in this directory." , projectPath ] exitCode: EX_NOINPUT];
223+
220224 [_project release ];
221225 _project = [[PBXProject projectWithFile: projectPath] retain ];
222226
223227 if (!_project)
224- @throw [DDCliParseException parseExceptionWithReason: [NSString stringWithFormat: @" The '%@ ' project is corrupted." , projectName ] exitCode: EX_DATAERR];
228+ @throw [DDCliParseException parseExceptionWithReason: [NSString stringWithFormat: @" The '%@ ' project is corrupted." , projectPath ] exitCode: EX_DATAERR];
225229}
226230
227231- (void ) setTarget : (NSString *)targetName
@@ -319,7 +323,7 @@ - (int) application:(DDCliApplication *)app runWithArguments:(NSArray *)argument
319323
320324- (NSArray *) allowedActions
321325{
322- return [NSArray arrayWithObjects: @" list-targets" , @" list-headers" , @" read-build-setting" , @" write-build-setting" , @" add-xcconfig" , @" add-resources-bundle" , @" touch" , nil ];
326+ return [NSArray arrayWithObjects: @" list-targets" , @" list-headers" , @" read-build-setting" , @" write-build-setting" , @" add-xcconfig" , @" add-resources-bundle" , @" put-resource-bundle " , @" touch" , nil ];
323327}
324328
325329- (void ) printUsage : (int )exitCode
@@ -341,8 +345,10 @@ - (void) printUsage:(int)exitCode
341345 @" Assign a value to a build setting. If the build setting does not exist, it is added to the target\n\n "
342346 @" * add-xcconfig <xcconfig_path>\n "
343347 @" Add an xcconfig file to the project and base all configurations on it\n\n "
344- @" * add-resources-bundle <bundle_path>\n "
345- @" Add a bundle to the project and in the `Copy Bundle Resources` build phase\n\n "
348+ @" * add-resources-bundle <bundle_paths> ...\n "
349+ @" Add multiple bundles to default group in the project and in the `Copy Bundle Resources` build phase\n\n "
350+ @" * put-resource-bundle <source_file_absolute_path_to_read> [<dest_dir_path> (default=Resources, relative or absolute)>]\n "
351+ @" Copy or rewrite a file, and then Add a bundle to target path in the project and in the `Copy Bundle Resources` build phase\n\n "
346352 @" * touch\n "
347353 @" Rewrite the project file\n " );
348354 exit (exitCode);
@@ -461,6 +467,83 @@ - (int) addResourcesBundle:(NSArray *)arguments
461467 return [self writeProject ];
462468}
463469
470+ - (int ) putResourceBundle : (NSArray *)arguments
471+ {
472+ if (arguments.count <1 || arguments.count >2 ){
473+ ddprintf (@" Wrong arguments input. See usage\n " );
474+ [self printUsage: EX_USAGE];
475+ }
476+
477+ // check source file valid
478+ NSString * sourceFilePath = [[arguments firstObject ] stringByStandardizingPath ];
479+
480+ if (![[NSFileManager defaultManager ] fileExistsAtPath: sourceFilePath]){
481+ @throw [DDCliParseException parseExceptionWithReason: [NSString stringWithFormat: @" A source file %@ does not exists." , sourceFilePath] exitCode: EX_OSFILE];
482+ }
483+ if (![[NSFileManager defaultManager ] isReadableFileAtPath: sourceFilePath]){
484+ @throw [DDCliParseException parseExceptionWithReason: [NSString stringWithFormat: @" A source file %@ can't read." , sourceFilePath] exitCode: EX_IOERR];
485+ }
486+ if (![sourceFilePath isAbsolutePath ]){
487+ @throw [DDCliParseException parseExceptionWithReason: [NSString stringWithFormat: @" A path of source file %@ must be absolute path." , sourceFilePath] exitCode: EX_IOERR];
488+ }
489+
490+ NSString * projectTargetGroupName = _target.name ;
491+ NSString * projectRootPath = [[NSFileManager defaultManager ] currentDirectoryPath ];
492+ NSString * targetPath = [projectRootPath stringByAppendingPathComponent: _target.name];
493+ NSString * destFilePath = [targetPath stringByAppendingPathComponent: @" Resources" ];
494+ // set dest path
495+ if (arguments.count ==2 ){
496+ destFilePath = [[arguments lastObject ] stringByStandardizingPath ];
497+ if (![destFilePath isAbsolutePath ]){
498+ destFilePath = [projectRootPath stringByAppendingPathComponent: destFilePath];
499+ }
500+
501+ if (![[destFilePath stringByDeletingLastPathComponent ] hasPrefix: projectRootPath]){
502+ @throw [DDCliParseException parseExceptionWithReason: [NSString stringWithFormat: @" A path of destination file '%@ ' must be locate inside of base path of project '%@ '." , destFilePath, projectRootPath] exitCode: EX_IOERR];
503+ }
504+ }
505+ destFilePath = [destFilePath stringByAppendingPathComponent: [sourceFilePath lastPathComponent ]];
506+
507+ // resolving relative path
508+ NSArray const * relativeDirs = [destFilePath pathComponents ];
509+ relativeDirs = [relativeDirs subarrayWithRange: NSMakeRange (projectRootPath.pathComponents.count, relativeDirs.count-projectRootPath.pathComponents.count-1 )];
510+
511+ if ([[relativeDirs firstObject ] isNotEqualTo: projectTargetGroupName]){
512+ ddprintf (@" [!] WARNING : A relative path of destination file '%@ ' is located outside of current TARGET path '%@ '.\n " , destFilePath, targetPath);
513+ }
514+
515+ @try {
516+ [[NSFileManager defaultManager ] createDirectoryAtPath: destFilePath withIntermediateDirectories: YES attributes: nil error: NULL ];
517+
518+ NSError *error = nil ;
519+ if ([self atomicCopyItemAtURL: [NSURL fileURLWithPath: sourceFilePath] toURL: [NSURL fileURLWithPath: destFilePath] error: &error]){
520+ [relativeDirs enumerateObjectsUsingBlock: ^(id obj, NSUInteger idx, BOOL *stop) {
521+ if (idx==0 ){
522+ [self addGroupNamed: obj beforeGroupNamed: _project.rootGroup.name];
523+ }else {
524+ [self addGroupNamed: obj inGroupNamed: relativeDirs[idx-1 ]];
525+ }
526+
527+ if ([[relativeDirs lastObject ] isEqualTo: obj]){
528+ id <PBXFileReference> bundleReference = [self addFileAtPath: destFilePath];
529+ [self addFileReference: bundleReference inGroupNamed: obj];
530+ [self addFileReference: bundleReference toBuildPhase: @" Resources" ];
531+ }
532+ }];
533+
534+ }else {
535+ ddprintf (@" [!] Resource file copy failed.%@ \n " );
536+ }
537+
538+ }@catch (NSException * exc) {
539+ ddprintf (@" File I/O Exception : %@ \n " , exc.reason );
540+ return EX_IOERR;
541+ }
542+
543+ return [self writeProject ];
544+ }
545+
546+
464547- (int ) touch : (NSArray *)arguments
465548{
466549 return [self writeProject ];
0 commit comments