├── Pods ├── Pods-GBCli.xcconfig ├── Headers │ └── GBCli │ │ ├── GBCli.h │ │ ├── GBPrint.h │ │ ├── GBSettings.h │ │ ├── GBOptionsHelper.h │ │ └── GBCommandLineParser.h ├── BuildHeaders │ └── GBCli │ │ ├── GBCli.h │ │ ├── GBPrint.h │ │ ├── GBSettings.h │ │ ├── GBOptionsHelper.h │ │ └── GBCommandLineParser.h ├── Pods-GBCli-prefix.pch ├── Pods-dummy.m ├── Pods-GBCli-dummy.m ├── Manifest.lock ├── Pods-GBCli-Private.xcconfig ├── Pods.xcconfig ├── GBCli │ ├── GBCli │ │ └── src │ │ │ ├── GBCli.h │ │ │ ├── GBPrint.h │ │ │ ├── GBPrint.m │ │ │ ├── GBCommandLineParser.h │ │ │ ├── GBSettings.h │ │ │ ├── GBSettings.m │ │ │ ├── GBOptionsHelper.h │ │ │ ├── GBCommandLineParser.m │ │ │ └── GBOptionsHelper.m │ ├── LICENSE │ └── Readme.markdown ├── Pods-environment.h ├── Local Podspecs │ └── GBCli.podspec ├── Pods-acknowledgements.markdown ├── Pods-acknowledgements.plist ├── Pods-resources.sh └── Pods.xcodeproj │ └── project.pbxproj ├── .gitignore ├── Podfile ├── symbolicator.xcworkspace └── contents.xcworkspacedata ├── symbolicator ├── symbolicator-Bridging-Header.h ├── Options.swift ├── main.swift ├── Settings.swift ├── Symbolicator.swift ├── ArchiveHandler.swift ├── ObjCRegex │ ├── RegExCategories.m │ └── RegExCategories.h └── FileSymbolicator.swift ├── symbolicator.xcodeproj ├── project.xcworkspace │ └── contents.xcworkspacedata └── project.pbxproj ├── Podfile.lock ├── LICENSE └── Readme.md /Pods/Pods-GBCli.xcconfig: -------------------------------------------------------------------------------- 1 | -------------------------------------------------------------------------------- /Pods/Headers/GBCli/GBCli.h: -------------------------------------------------------------------------------- 1 | ../../GBCli/GBCli/src/GBCli.h -------------------------------------------------------------------------------- /Pods/BuildHeaders/GBCli/GBCli.h: -------------------------------------------------------------------------------- 1 | ../../GBCli/GBCli/src/GBCli.h -------------------------------------------------------------------------------- /Pods/Headers/GBCli/GBPrint.h: -------------------------------------------------------------------------------- 1 | ../../GBCli/GBCli/src/GBPrint.h -------------------------------------------------------------------------------- /.gitignore: -------------------------------------------------------------------------------- 1 | *-AutoGenerated!.* 2 | 3 | # Xcode 4 | xcuserdata/ 5 | -------------------------------------------------------------------------------- /Pods/BuildHeaders/GBCli/GBPrint.h: -------------------------------------------------------------------------------- 1 | ../../GBCli/GBCli/src/GBPrint.h -------------------------------------------------------------------------------- /Pods/Headers/GBCli/GBSettings.h: -------------------------------------------------------------------------------- 1 | ../../GBCli/GBCli/src/GBSettings.h -------------------------------------------------------------------------------- /Pods/BuildHeaders/GBCli/GBSettings.h: -------------------------------------------------------------------------------- 1 | ../../GBCli/GBCli/src/GBSettings.h -------------------------------------------------------------------------------- /Podfile: -------------------------------------------------------------------------------- 1 | platform :osx, "10.9" 2 | 3 | pod "GBCli", :git => "../GBCli" 4 | -------------------------------------------------------------------------------- /Pods/Headers/GBCli/GBOptionsHelper.h: -------------------------------------------------------------------------------- 1 | ../../GBCli/GBCli/src/GBOptionsHelper.h -------------------------------------------------------------------------------- /Pods/BuildHeaders/GBCli/GBOptionsHelper.h: -------------------------------------------------------------------------------- 1 | ../../GBCli/GBCli/src/GBOptionsHelper.h -------------------------------------------------------------------------------- /Pods/Headers/GBCli/GBCommandLineParser.h: -------------------------------------------------------------------------------- 1 | ../../GBCli/GBCli/src/GBCommandLineParser.h -------------------------------------------------------------------------------- /Pods/BuildHeaders/GBCli/GBCommandLineParser.h: -------------------------------------------------------------------------------- 1 | ../../GBCli/GBCli/src/GBCommandLineParser.h -------------------------------------------------------------------------------- /Pods/Pods-GBCli-prefix.pch: -------------------------------------------------------------------------------- 1 | #ifdef __OBJC__ 2 | #import 3 | #endif 4 | 5 | #import "Pods-environment.h" 6 | -------------------------------------------------------------------------------- /Pods/Pods-dummy.m: -------------------------------------------------------------------------------- 1 | #import 2 | @interface PodsDummy_Pods : NSObject 3 | @end 4 | @implementation PodsDummy_Pods 5 | @end 6 | -------------------------------------------------------------------------------- /Pods/Pods-GBCli-dummy.m: -------------------------------------------------------------------------------- 1 | #import 2 | @interface PodsDummy_Pods_GBCli : NSObject 3 | @end 4 | @implementation PodsDummy_Pods_GBCli 5 | @end 6 | -------------------------------------------------------------------------------- /symbolicator.xcworkspace/contents.xcworkspacedata: -------------------------------------------------------------------------------- 1 | -------------------------------------------------------------------------------- /symbolicator/symbolicator-Bridging-Header.h: -------------------------------------------------------------------------------- 1 | /* 2 | 3 | Created by Tomaz Kragelj on 9.06.2014. 4 | Copyright (c) 2014 Gentle Bytes. All rights reserved. 5 | 6 | */ 7 | 8 | #import 9 | #import "RegExCategories.h" 10 | -------------------------------------------------------------------------------- /symbolicator.xcodeproj/project.xcworkspace/contents.xcworkspacedata: -------------------------------------------------------------------------------- 1 | 2 | 4 | 6 | 7 | 8 | -------------------------------------------------------------------------------- /Podfile.lock: -------------------------------------------------------------------------------- 1 | PODS: 2 | - GBCli (1.1) 3 | 4 | DEPENDENCIES: 5 | - GBCli (from `../GBCli`) 6 | 7 | EXTERNAL SOURCES: 8 | GBCli: 9 | :git: ../GBCli 10 | 11 | SPEC CHECKSUMS: 12 | GBCli: 3527037ed1a797dbcc7ea32a86287abae0eb614f 13 | 14 | COCOAPODS: 0.33.1 15 | -------------------------------------------------------------------------------- /Pods/Manifest.lock: -------------------------------------------------------------------------------- 1 | PODS: 2 | - GBCli (1.1) 3 | 4 | DEPENDENCIES: 5 | - GBCli (from `../GBCli`) 6 | 7 | EXTERNAL SOURCES: 8 | GBCli: 9 | :git: ../GBCli 10 | 11 | SPEC CHECKSUMS: 12 | GBCli: 3527037ed1a797dbcc7ea32a86287abae0eb614f 13 | 14 | COCOAPODS: 0.33.1 15 | -------------------------------------------------------------------------------- /Pods/Pods-GBCli-Private.xcconfig: -------------------------------------------------------------------------------- 1 | #include "Pods-GBCli.xcconfig" 2 | GCC_PREPROCESSOR_DEFINITIONS = COCOAPODS=1 3 | HEADER_SEARCH_PATHS = "${PODS_ROOT}/BuildHeaders" "${PODS_ROOT}/BuildHeaders/GBCli" "${PODS_ROOT}/Headers" "${PODS_ROOT}/Headers/GBCli" 4 | OTHER_LDFLAGS = -ObjC 5 | PODS_ROOT = ${SRCROOT} -------------------------------------------------------------------------------- /Pods/Pods.xcconfig: -------------------------------------------------------------------------------- 1 | GCC_PREPROCESSOR_DEFINITIONS = $(inherited) COCOAPODS=1 2 | HEADER_SEARCH_PATHS = "${PODS_ROOT}/Headers" "${PODS_ROOT}/Headers/GBCli" 3 | OTHER_CFLAGS = $(inherited) -isystem "${PODS_ROOT}/Headers" -isystem "${PODS_ROOT}/Headers/GBCli" 4 | OTHER_LDFLAGS = -ObjC 5 | PODS_ROOT = ${SRCROOT}/Pods -------------------------------------------------------------------------------- /Pods/GBCli/GBCli/src/GBCli.h: -------------------------------------------------------------------------------- 1 | // 2 | // GBCli.h 3 | // GBCli 4 | // 5 | // Created by Tomaz Kragelj on 23.05.14. 6 | // 7 | // 8 | 9 | // Imports all source files for GBCli 10 | 11 | #import "GBCommandLineParser.h" 12 | #import "GBSettings.h" 13 | #import "GBOptionsHelper.h" 14 | #import "GBPrint.h" 15 | -------------------------------------------------------------------------------- /Pods/GBCli/GBCli/src/GBPrint.h: -------------------------------------------------------------------------------- 1 | // 2 | // GBPrint.h 3 | // GBCli 4 | // 5 | // Created by Tomaz Kragelj on 23.05.14. 6 | // 7 | // 8 | 9 | #import 10 | 11 | extern void gbprint(NSString *format, ...); 12 | extern void gbprintln(NSString *format, ...); 13 | extern void gbfprint(FILE *file, NSString *format, ...); 14 | extern void gbfprintln(FILE *file, NSString *format, ...); 15 | -------------------------------------------------------------------------------- /Pods/Pods-environment.h: -------------------------------------------------------------------------------- 1 | 2 | // To check if a library is compiled with CocoaPods you 3 | // can use the `COCOAPODS` macro definition which is 4 | // defined in the xcconfigs so it is available in 5 | // headers also when they are imported in the client 6 | // project. 7 | 8 | 9 | // GBCli 10 | #define COCOAPODS_POD_AVAILABLE_GBCli 11 | #define COCOAPODS_VERSION_MAJOR_GBCli 1 12 | #define COCOAPODS_VERSION_MINOR_GBCli 1 13 | #define COCOAPODS_VERSION_PATCH_GBCli 0 14 | 15 | -------------------------------------------------------------------------------- /Pods/Local Podspecs/GBCli.podspec: -------------------------------------------------------------------------------- 1 | Pod::Spec.new do |s| 2 | s.name = 'GBCli' 3 | s.version = '1.1' 4 | s.summary = 'Objective C foundation tool command line interface library.' 5 | s.homepage = 'http://github/tomaz/GBCli' 6 | s.license = 'MIT' 7 | s.author = { 'Tomaz Kragelj' => 'tkragelj@gmail.com' } 8 | s.source = { :git => 'https://github.com/tomaz/GBCli.git', :tag => '1.1' } 9 | s.platform = :osx, '10.8' 10 | s.source_files = 'GBCli/src' 11 | s.requires_arc = true 12 | end 13 | -------------------------------------------------------------------------------- /LICENSE: -------------------------------------------------------------------------------- 1 | Copyright (C) 2012 by Tomaz Kragelj 2 | 3 | Permission is hereby granted, free of charge, to any person obtaining a copy 4 | of this software and associated documentation files (the "Software"), to deal 5 | in the Software without restriction, including without limitation the rights 6 | to use, copy, modify, merge, publish, distribute, sublicense, and/or sell 7 | copies of the Software, and to permit persons to whom the Software is 8 | furnished to do so, subject to the following conditions: 9 | 10 | The above copyright notice and this permission notice shall be included in 11 | all copies or substantial portions of the Software. 12 | 13 | THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR 14 | IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, 15 | FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE 16 | AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER 17 | LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, 18 | OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN 19 | THE SOFTWARE. -------------------------------------------------------------------------------- /Pods/GBCli/LICENSE: -------------------------------------------------------------------------------- 1 | Copyright (C) 2012 by Tomaz Kragelj 2 | 3 | Permission is hereby granted, free of charge, to any person obtaining a copy 4 | of this software and associated documentation files (the "Software"), to deal 5 | in the Software without restriction, including without limitation the rights 6 | to use, copy, modify, merge, publish, distribute, sublicense, and/or sell 7 | copies of the Software, and to permit persons to whom the Software is 8 | furnished to do so, subject to the following conditions: 9 | 10 | The above copyright notice and this permission notice shall be included in 11 | all copies or substantial portions of the Software. 12 | 13 | THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR 14 | IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, 15 | FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE 16 | AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER 17 | LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, 18 | OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN 19 | THE SOFTWARE. -------------------------------------------------------------------------------- /symbolicator/Options.swift: -------------------------------------------------------------------------------- 1 | /* 2 | 3 | Created by Tomaz Kragelj on 11.06.2014. 4 | Copyright (c) 2014 Gentle Bytes. All rights reserved. 5 | 6 | */ 7 | 8 | import Foundation 9 | 10 | class Options: GBOptionsHelper { 11 | override init() { 12 | super.init() 13 | 14 | applicationVersion = { GAppVersion } 15 | applicationBuild = { "\(GAppBuildNumber)" } 16 | printHelpHeader = { "Usage: %APPNAME [OPTIONS] \nExample: %APPNAME crashlog1.crash \"~/Downloads/some other crash.txt\"" } 17 | 18 | registerSeparator("OPTIONS") 19 | registerOption(0, long: settingXcodeArchivesKey, description: "Xcode archives location", flags: GBOptionFlags()) 20 | registerOption(0, long: settingsDryRunKey, description: "Dry run, do not overwrite input", flags: .noValue) 21 | registerOption(0, long: settingsFuzzySearchKey, description: "Use fuzzy search for base address lookup", flags: .noValue) 22 | registerOption(0, long: settingsPrintVerboseKey, description: "Print verbose output", flags: .noValue) 23 | registerOption(0, long: settingsPrintHelpKey, description: "Print this help and exit", flags: .noValue) 24 | } 25 | } 26 | -------------------------------------------------------------------------------- /symbolicator/main.swift: -------------------------------------------------------------------------------- 1 | // 2 | // main.swift 3 | // symbolicator 4 | // 5 | // Created by Tomaz Kragelj on 9.06.14. 6 | // Copyright (c) 2014 Gentle Bytes. All rights reserved. 7 | // 8 | 9 | import Foundation 10 | 11 | let settings = Settings() 12 | let options = Options() 13 | let commandLineParser = GBCommandLineParser() 14 | 15 | func setup() { 16 | commandLineParser.registerOptions(options) 17 | commandLineParser.register(settings) 18 | } 19 | 20 | func run() { 21 | options.printVersion() 22 | print("") 23 | 24 | if !commandLineParser.parseOptionsUsingDefaultArguments() { 25 | print("") 26 | options.printHelp() 27 | return 28 | } 29 | 30 | if settings.printHelp { 31 | options.printHelp() 32 | return 33 | } 34 | 35 | if commandLineParser.arguments.count == 0 { 36 | print("At least one crashlog path is required!") 37 | print("") 38 | options.printHelp() 39 | return 40 | } 41 | 42 | let symbolicator = Symbolicator() 43 | let crashLogs = settings.arguments as! Array; 44 | let archivesPath = settings.xcodeArchivesFolder; 45 | symbolicator.symbolicate(crashLogs, archivesPath: archivesPath) 46 | } 47 | 48 | setup() 49 | run() 50 | -------------------------------------------------------------------------------- /Pods/GBCli/GBCli/src/GBPrint.m: -------------------------------------------------------------------------------- 1 | // 2 | // GBPrint.m 3 | // GBCli 4 | // 5 | // Created by Tomaz Kragelj on 23.05.14. 6 | // 7 | // 8 | 9 | #import "GBPrint.h" 10 | 11 | static void gb_printf_worker(FILE *file, NSString *format, va_list arguments) { 12 | NSString *msg = [[NSString alloc] initWithFormat:format arguments:arguments]; 13 | fprintf(file, "%s", [msg UTF8String]); 14 | } 15 | 16 | void gbprint(NSString *format, ...) { 17 | va_list arguments; 18 | va_start(arguments, format); 19 | gb_printf_worker(stdout, format, arguments); 20 | va_end(arguments); 21 | } 22 | 23 | void gbprintln(NSString *format, ...) { 24 | va_list arguments; 25 | va_start(arguments, format); 26 | format = [format stringByAppendingString:@"\n"]; 27 | gb_printf_worker(stdout, format, arguments); 28 | va_end(arguments); 29 | } 30 | 31 | void gbfprint(FILE *file, NSString *format, ...) { 32 | va_list arguments; 33 | va_start(arguments, format); 34 | gb_printf_worker(file, format, arguments); 35 | va_end(arguments); 36 | } 37 | 38 | void gbfprintln(FILE *file, NSString *format, ...) { 39 | va_list arguments; 40 | va_start(arguments, format); 41 | format = [format stringByAppendingString:@"\n"]; 42 | gb_printf_worker(file, format, arguments); 43 | va_end(arguments); 44 | } 45 | -------------------------------------------------------------------------------- /Pods/Pods-acknowledgements.markdown: -------------------------------------------------------------------------------- 1 | # Acknowledgements 2 | This application makes use of the following third party libraries: 3 | 4 | ## GBCli 5 | 6 | Copyright (C) 2012 by Tomaz Kragelj 7 | 8 | Permission is hereby granted, free of charge, to any person obtaining a copy 9 | of this software and associated documentation files (the "Software"), to deal 10 | in the Software without restriction, including without limitation the rights 11 | to use, copy, modify, merge, publish, distribute, sublicense, and/or sell 12 | copies of the Software, and to permit persons to whom the Software is 13 | furnished to do so, subject to the following conditions: 14 | 15 | The above copyright notice and this permission notice shall be included in 16 | all copies or substantial portions of the Software. 17 | 18 | THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR 19 | IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, 20 | FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE 21 | AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER 22 | LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, 23 | OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN 24 | THE SOFTWARE. 25 | Generated by CocoaPods - http://cocoapods.org 26 | -------------------------------------------------------------------------------- /symbolicator/Settings.swift: -------------------------------------------------------------------------------- 1 | /* 2 | 3 | Created by Tomaz Kragelj on 9.06.2014. 4 | Copyright (c) 2014 Gentle Bytes. All rights reserved. 5 | 6 | */ 7 | 8 | class Settings: GBSettings { 9 | override init() { 10 | // First setup factory defaults. 11 | let defaults = Settings(name: "Factory Defaults") 12 | defaults.xcodeArchivesFolder = "~/Library/Developer/Xcode/Archives" 13 | 14 | // Now return the settings using factory defaults as their parent 15 | super.init(name: "FactoryDefaults", parent: defaults) 16 | } 17 | 18 | fileprivate init(name: String) { 19 | super.init(name: name, parent: nil) 20 | } 21 | 22 | var xcodeArchivesFolder: String { 23 | get { return object(forKey: settingXcodeArchivesKey) as! String } 24 | set { setObject(newValue, forKey: settingXcodeArchivesKey) } 25 | } 26 | 27 | var dryRun: Bool { 28 | get { return bool(forKey: settingsDryRunKey) } 29 | set { setBool(newValue, forKey: settingsDryRunKey) } 30 | } 31 | 32 | var fuzzySearch: Bool { 33 | get { return bool(forKey: settingsFuzzySearchKey) } 34 | set { setBool(newValue, forKey: settingsFuzzySearchKey) } 35 | } 36 | 37 | var printVerbose: Bool { 38 | get { return bool(forKey: settingsPrintVerboseKey) } 39 | set { setBool(newValue, forKey: settingsPrintVerboseKey) } 40 | } 41 | 42 | var printHelp: Bool { 43 | get { return bool(forKey: settingsPrintHelpKey) } 44 | set { setBool(newValue, forKey: settingsPrintHelpKey) } 45 | } 46 | } 47 | 48 | let settingXcodeArchivesKey = "archives" 49 | let settingsDryRunKey = "dryrun" 50 | let settingsFuzzySearchKey = "fuzzy" 51 | let settingsPrintVerboseKey = "verbose" 52 | let settingsPrintHelpKey = "help" 53 | -------------------------------------------------------------------------------- /symbolicator/Symbolicator.swift: -------------------------------------------------------------------------------- 1 | /* 2 | 3 | Created by Tomaz Kragelj on 9.06.2014. 4 | Copyright (c) 2014 Gentle Bytes. All rights reserved. 5 | 6 | */ 7 | 8 | import Foundation 9 | 10 | class Symbolicator { 11 | 12 | func symbolicate(_ files: Array, archivesPath: String) { 13 | print("Symbolizing \(files.count) crash logs...") 14 | 15 | let archiveHandler = ArchiveHandler(path: archivesPath) 16 | let symbolicator = FileSymbolicator() 17 | 18 | for filename in files { 19 | // Prepare full path to crash log and bail out if it doesn't exist. 20 | print("") 21 | print("Symbolizing \(filename)...") 22 | let path: String = (filename as NSString).standardizingPath 23 | if !FileManager.default.fileExists(atPath: path) { 24 | print("ERROR: file doesn't exist!") 25 | continue 26 | } 27 | 28 | // Load contents of the file into string and bail out if it doesn't work. 29 | do { 30 | let original = try String(contentsOfFile:path, encoding: String.Encoding.utf8) 31 | 32 | // Symbolicate the crash log. 33 | if let symbolized = symbolicator.symbolicate(filename, contents: original, archiveHandler: archiveHandler) { 34 | if settings.dryRun { 35 | continue 36 | } 37 | 38 | if symbolized == original { 39 | continue 40 | } 41 | 42 | do { 43 | try symbolized.write(toFile: path, atomically: true, encoding: String.Encoding.utf8) 44 | print("File overwritted with symbolized data") 45 | } catch { 46 | print("ERROR: failed saving symbolized contents: \(error)") 47 | } 48 | } 49 | } catch { 50 | print("ERROR: Failed reading contents of \((path as NSString).lastPathComponent): \(error)") 51 | continue 52 | } 53 | } 54 | } 55 | } 56 | -------------------------------------------------------------------------------- /Pods/Pods-acknowledgements.plist: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | PreferenceSpecifiers 6 | 7 | 8 | FooterText 9 | This application makes use of the following third party libraries: 10 | Title 11 | Acknowledgements 12 | Type 13 | PSGroupSpecifier 14 | 15 | 16 | FooterText 17 | Copyright (C) 2012 by Tomaz Kragelj 18 | 19 | Permission is hereby granted, free of charge, to any person obtaining a copy 20 | of this software and associated documentation files (the "Software"), to deal 21 | in the Software without restriction, including without limitation the rights 22 | to use, copy, modify, merge, publish, distribute, sublicense, and/or sell 23 | copies of the Software, and to permit persons to whom the Software is 24 | furnished to do so, subject to the following conditions: 25 | 26 | The above copyright notice and this permission notice shall be included in 27 | all copies or substantial portions of the Software. 28 | 29 | THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR 30 | IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, 31 | FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE 32 | AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER 33 | LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, 34 | OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN 35 | THE SOFTWARE. 36 | Title 37 | GBCli 38 | Type 39 | PSGroupSpecifier 40 | 41 | 42 | FooterText 43 | Generated by CocoaPods - http://cocoapods.org 44 | Title 45 | 46 | Type 47 | PSGroupSpecifier 48 | 49 | 50 | StringsTable 51 | Acknowledgements 52 | Title 53 | Acknowledgements 54 | 55 | 56 | -------------------------------------------------------------------------------- /Pods/Pods-resources.sh: -------------------------------------------------------------------------------- 1 | #!/bin/sh 2 | set -e 3 | 4 | RESOURCES_TO_COPY=${PODS_ROOT}/resources-to-copy-${TARGETNAME}.txt 5 | > "$RESOURCES_TO_COPY" 6 | 7 | install_resource() 8 | { 9 | case $1 in 10 | *.storyboard) 11 | echo "ibtool --reference-external-strings-file --errors --warnings --notices --output-format human-readable-text --compile ${CONFIGURATION_BUILD_DIR}/${UNLOCALIZED_RESOURCES_FOLDER_PATH}/`basename \"$1\" .storyboard`.storyboardc ${PODS_ROOT}/$1 --sdk ${SDKROOT}" 12 | ibtool --reference-external-strings-file --errors --warnings --notices --output-format human-readable-text --compile "${CONFIGURATION_BUILD_DIR}/${UNLOCALIZED_RESOURCES_FOLDER_PATH}/`basename \"$1\" .storyboard`.storyboardc" "${PODS_ROOT}/$1" --sdk "${SDKROOT}" 13 | ;; 14 | *.xib) 15 | echo "ibtool --reference-external-strings-file --errors --warnings --notices --output-format human-readable-text --compile ${CONFIGURATION_BUILD_DIR}/${UNLOCALIZED_RESOURCES_FOLDER_PATH}/`basename \"$1\" .xib`.nib ${PODS_ROOT}/$1 --sdk ${SDKROOT}" 16 | ibtool --reference-external-strings-file --errors --warnings --notices --output-format human-readable-text --compile "${CONFIGURATION_BUILD_DIR}/${UNLOCALIZED_RESOURCES_FOLDER_PATH}/`basename \"$1\" .xib`.nib" "${PODS_ROOT}/$1" --sdk "${SDKROOT}" 17 | ;; 18 | *.framework) 19 | echo "mkdir -p ${CONFIGURATION_BUILD_DIR}/${FRAMEWORKS_FOLDER_PATH}" 20 | mkdir -p "${CONFIGURATION_BUILD_DIR}/${FRAMEWORKS_FOLDER_PATH}" 21 | echo "rsync -av ${PODS_ROOT}/$1 ${CONFIGURATION_BUILD_DIR}/${FRAMEWORKS_FOLDER_PATH}" 22 | rsync -av "${PODS_ROOT}/$1" "${CONFIGURATION_BUILD_DIR}/${FRAMEWORKS_FOLDER_PATH}" 23 | ;; 24 | *.xcdatamodel) 25 | echo "xcrun momc \"${PODS_ROOT}/$1\" \"${CONFIGURATION_BUILD_DIR}/${UNLOCALIZED_RESOURCES_FOLDER_PATH}/`basename "$1"`.mom\"" 26 | xcrun momc "${PODS_ROOT}/$1" "${CONFIGURATION_BUILD_DIR}/${UNLOCALIZED_RESOURCES_FOLDER_PATH}/`basename "$1" .xcdatamodel`.mom" 27 | ;; 28 | *.xcdatamodeld) 29 | echo "xcrun momc \"${PODS_ROOT}/$1\" \"${CONFIGURATION_BUILD_DIR}/${UNLOCALIZED_RESOURCES_FOLDER_PATH}/`basename "$1" .xcdatamodeld`.momd\"" 30 | xcrun momc "${PODS_ROOT}/$1" "${CONFIGURATION_BUILD_DIR}/${UNLOCALIZED_RESOURCES_FOLDER_PATH}/`basename "$1" .xcdatamodeld`.momd" 31 | ;; 32 | *.xcassets) 33 | ;; 34 | /*) 35 | echo "$1" 36 | echo "$1" >> "$RESOURCES_TO_COPY" 37 | ;; 38 | *) 39 | echo "${PODS_ROOT}/$1" 40 | echo "${PODS_ROOT}/$1" >> "$RESOURCES_TO_COPY" 41 | ;; 42 | esac 43 | } 44 | 45 | rsync -avr --copy-links --no-relative --exclude '*/.svn/*' --files-from="$RESOURCES_TO_COPY" / "${CONFIGURATION_BUILD_DIR}/${UNLOCALIZED_RESOURCES_FOLDER_PATH}" 46 | if [[ "${ACTION}" == "install" ]]; then 47 | rsync -avr --copy-links --no-relative --exclude '*/.svn/*' --files-from="$RESOURCES_TO_COPY" / "${INSTALL_DIR}/${UNLOCALIZED_RESOURCES_FOLDER_PATH}" 48 | fi 49 | rm -f "$RESOURCES_TO_COPY" 50 | 51 | if [[ -n "${WRAPPER_EXTENSION}" ]] && [ `xcrun --find actool` ] && [ `find . -name '*.xcassets' | wc -l` -ne 0 ] 52 | then 53 | case "${TARGETED_DEVICE_FAMILY}" in 54 | 1,2) 55 | TARGET_DEVICE_ARGS="--target-device ipad --target-device iphone" 56 | ;; 57 | 1) 58 | TARGET_DEVICE_ARGS="--target-device iphone" 59 | ;; 60 | 2) 61 | TARGET_DEVICE_ARGS="--target-device ipad" 62 | ;; 63 | *) 64 | TARGET_DEVICE_ARGS="--target-device mac" 65 | ;; 66 | esac 67 | find "${PWD}" -name "*.xcassets" -print0 | xargs -0 actool --output-format human-readable-text --notices --warnings --platform "${PLATFORM_NAME}" --minimum-deployment-target "${IPHONEOS_DEPLOYMENT_TARGET}" ${TARGET_DEVICE_ARGS} --compress-pngs --compile "${BUILT_PRODUCTS_DIR}/${UNLOCALIZED_RESOURCES_FOLDER_PATH}" 68 | fi 69 | -------------------------------------------------------------------------------- /Readme.md: -------------------------------------------------------------------------------- 1 | Symbolicator 2 | ------------ 3 | 4 | Symbolicator is command line utility for symbolizings crash logs for OS X and iOS applications. 5 | 6 | In it's essence symbolicator is a wrapper over `atos` command line tool. It's primary focus is making symbolication as simple as possible. As minimum, it's as simple as specifying the list of crash log paths you want to symbolicate: 7 | 8 | ``` 9 | symbolicator crash1.crash ~/Downloads/crash2.crash ~/Documents/crash3.crash 10 | 11 | ``` 12 | 13 | The tool will deduce the application information from each crash log, fetch appropriate DWARF file from Xcode archives path and use it to symbolize the crash log. By default it searches for DWARF files on *~/Library/Developer/Xcode/Archives* but you can specify different path with `--archives` command line switch). 14 | 15 | Note: you must run symbolicator on Mac which contains all archived projects for which crash logs belong. If you have multiple crash logs it's faster to provide all of them as multiple arguments on command line than each one separately. 16 | 17 | 18 | How to compile? 19 | --------------- 20 | 21 | 1. Open `symbolicator.xcworkspace` file in Xcode and compile. 22 | 2. You can find compiled binary on your Xcode derived data path, by default `~/Library/Developer/Xcode/DerivedData/symbolicator-xxxxxxxxxxxxxxxx/build/Products/Debug` (or Release). 23 | 3. For simplest usage, copy generated `symbolicator` binary to some location in your PATH (`/usr/bin` for example, use `echo $PATH` in Terminal to see the list of all possible locations). 24 | 25 | Note: Symbolicator is implemented in Swift, so it requires Xcode 6+ to compile. It also uses cocoapods for dependencies, for convenience they are included in this repository so you don't need to do `pod install`. Just take care to open `xcworkspace` not `xcodeproj` file! 26 | 27 | 28 | How it works? 29 | ------------- 30 | 31 | Each crash log contains information about the application that crashes - this includes name, bundle identifier, version and build number etc. Each Xcode archive is simply a bundle containing both, compiled binary (that's usually stripped of all debugging symbols) and DWARF file which contains all symbol mappings. Additionally, it contains a plist file that describes the application information - version etc. for which it was created. Symbolicator matches information from crash log with corresponding archive bundle and uses it to symbolicate symbols. 32 | 33 | 34 | Thanks 35 | ------ 36 | 37 | Symbolicator uses the following open source libraries: 38 | 39 | - [GBCli](http://github.com/tomaz/GBCli) 40 | - [RX+](http://github.com/bendytree/Objective-C-RegEx-Categories) 41 | 42 | 43 | License 44 | ------- 45 | 46 | The code is provided under MIT license as stated below: 47 | 48 | Copyright (C) 2012 by Tomaz Kragelj 49 | 50 | Permission is hereby granted, free of charge, to any person obtaining a copy 51 | of this software and associated documentation files (the "Software"), to deal 52 | in the Software without restriction, including without limitation the rights 53 | to use, copy, modify, merge, publish, distribute, sublicense, and/or sell 54 | copies of the Software, and to permit persons to whom the Software is 55 | furnished to do so, subject to the following conditions: 56 | 57 | The above copyright notice and this permission notice shall be included in 58 | all copies or substantial portions of the Software. 59 | 60 | THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR 61 | IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, 62 | FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE 63 | AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER 64 | LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, 65 | OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN 66 | THE SOFTWARE. -------------------------------------------------------------------------------- /Pods/GBCli/GBCli/src/GBCommandLineParser.h: -------------------------------------------------------------------------------- 1 | // 2 | // GBCommandLineParser.h 3 | // GBCli 4 | // 5 | // Created by Tomaž Kragelj on 3/12/12. 6 | // Copyright (c) 2012 Tomaz Kragelj. All rights reserved. 7 | // 8 | 9 | #import 10 | 11 | @class GBSettings; 12 | 13 | typedef NSUInteger GBValueRequirements; 14 | typedef NSUInteger GBParseFlags; 15 | typedef void(^GBCommandLineParseBlock)(GBParseFlags flags, NSString *argument, id value, BOOL *stop); 16 | 17 | /** Handles command line arguments parsing. 18 | 19 | To use the class, instantiate it, register all options, ask it to parse command line arguments and finally use accessors to read the data. Here's one possible way: 20 | 21 | ``` 22 | int main(int argv, char **argv) { 23 | GBCommandLineParser *parser = [[GBCommandLineParser alloc] init]; 24 | [parser registerOption:@"verbose" shortcut:'v' requirement:GBValueRequired]; 25 | [parser registerSwitch:@"help" shortcut:'h']; 26 | ... 27 | [parser parseOptionsWithArguments:argv count:argc block:^(NSString *argument, id value, BOOL *stop) { 28 | if (value == GBCommandLineArgumentResults.unknownArgument) { 29 | *stop = YES; 30 | return; 31 | } else if (value == GBCommandLineArgumentResults.missingValue) { 32 | *stop = YES; 33 | return; 34 | } 35 | ... you can do somethig with valid argument here if needed 36 | }]; 37 | ... you can access all parsed options and arguments here... 38 | id value1 = [parser valueForOption:@"verbose"]; 39 | NSArray *arguments = parser.arguments; 40 | return 0; 41 | } 42 | ``` 43 | 44 | @warning **Important:** This class is similar to `DDCli` from Dave Dribin, but works under arc! It also uses a different approach to command line parsing - instead of using "push" model like `DDCli` (i.e. sending KVO notifications to pass arguments to a delegate), it uses "pull" model: you let it parse the values and then ask the class for specific argument values. With this approach, it centralizes arguments handling - instead of splitting it over various delegate and KVO mutator methods, you can do it in a single place. 45 | */ 46 | @interface GBCommandLineParser : NSObject 47 | 48 | #pragma mark - Options registration 49 | 50 | - (void)beginRegisterOptionGroup:(NSString *)name; 51 | - (void)endRegisterOptionGroup; // optional; if another beginRegisterOptionGroup: is enountered, a new group is started. Use it if you want to register "standalone" options after group. 52 | - (void)registerOption:(NSString *)longOption shortcut:(char)shortOption requirement:(GBValueRequirements)requirement; 53 | - (void)registerOption:(NSString *)longOption requirement:(GBValueRequirements)requirement; 54 | - (void)registerSwitch:(NSString *)longOption shortcut:(char)shortOption; 55 | - (void)registerSwitch:(NSString *)longOption; 56 | 57 | #pragma mark - Options parsing 58 | 59 | - (void)registerSettings:(GBSettings *)settings; 60 | - (BOOL)parseOptionsUsingDefaultArguments; 61 | - (BOOL)parseOptionsWithArguments:(char **)argv count:(int)argc; 62 | - (BOOL)parseOptionsWithArguments:(NSArray *)arguments commandLine:(NSString *)cmd; 63 | 64 | - (BOOL)parseOptionsUsingDefaultArgumentsWithBlock:(GBCommandLineParseBlock)handler; 65 | - (BOOL)parseOptionsWithArguments:(char **)argv count:(int)argc block:(GBCommandLineParseBlock)handler; 66 | - (BOOL)parseOptionsWithArguments:(NSArray *)arguments commandLine:(NSString *)cmd block:(GBCommandLineParseBlock)handler; 67 | 68 | #pragma mark - Getting parsed results 69 | 70 | - (id)valueForOption:(NSString *)longOption; 71 | 72 | @property (nonatomic, readonly) NSArray *arguments; 73 | 74 | @end 75 | 76 | #pragma mark - 77 | 78 | /** Various command line argument value requirements. */ 79 | typedef NS_ENUM(NSUInteger, GBValueFlags) { 80 | GBValueRequired, ///< Command line argument requires a value. 81 | GBValueOptional, ///< Command line argument can optionally have a value, but is not required. 82 | GBValueNone ///< Command line argument is on/off switch. 83 | }; 84 | 85 | /** Various parsing flags. */ 86 | typedef NS_ENUM(NSUInteger, GBParseFlag) { 87 | GBParseFlagOption, 88 | GBParseFlagArgument, 89 | GBParseFlagWrongGroup, 90 | GBParseFlagMissingValue, 91 | GBParseFlagUnknownOption, 92 | }; 93 | -------------------------------------------------------------------------------- /Pods/GBCli/GBCli/src/GBSettings.h: -------------------------------------------------------------------------------- 1 | // 2 | // GBSettings.h 3 | // GBCli 4 | // 5 | // Created by Tomaž Kragelj on 3/13/12. 6 | // Copyright (c) 2012 Tomaz Kragelj. All rights reserved. 7 | // 8 | 9 | #import 10 | 11 | /** The main application settings. 12 | 13 | This class declares all possible settings for the rest of the application. It supports building a hierarchy of settings levels, for example: factory defaults, settings file and command line arguments. It provides methods for accessing any given setting, which will automatically descend to parent if current level doesn't provide a value. If no level provides a value, methods will fail! Example of usage: 14 | 15 | ``` 16 | // Initialize settings hierarchy 17 | GBSettings *factoryDefaults = [GBSettings settingsWithName:@"FactoryDefaults" parent:nil]; 18 | GBSettings *fileSettings = [GBSettings settingsWithName:@"File" parent:factoryDefaults]; 19 | GBSettings *settings = [GBSettings settingsWithName:@"CommandLine" parent:fileSettings]; 20 | 21 | // Setup default values 22 | [factoryDefaults setObject:@"Some value" forKey:@"MyString"]; 23 | [factoryDefaults setInteger:50 forKey:@"MyInteger"]; 24 | [factoryDefaults setBool:YES forKey:@"MyBool"]; 25 | [fileSettings setInteger:12 forKey:@"MyInteger"]; 26 | [settings setInteger:20 forKey:@"MyInteger"]; 27 | [settings setBool:NO forKey:@"MyBool"]; 28 | ... from here on, just use settings... 29 | 30 | // Access values 31 | NSString *s = [settings objectForKey:@"MyString"]; // @"Some value" 32 | NSInteger i = [settings integerForKey:@"MyInteger"]; // 20 33 | BOOL b = [settings boolForKey:@"MyBool"]; // NO 34 | 35 | // Determine which level certain setting comes from 36 | GBSettings *s = [settings settingsForKey:@"MyString"]; // factoryDefaults 37 | ``` 38 | */ 39 | @interface GBSettings : NSObject 40 | 41 | #pragma mark - Initialization & disposal 42 | 43 | + (instancetype)settingsWithName:(NSString *)name parent:(GBSettings *)parent; 44 | - (instancetype)initWithName:(NSString *)name parent:(GBSettings *)parent; 45 | 46 | #pragma mark - Settings serialization support 47 | 48 | - (BOOL)loadSettingsFromPlist:(NSString *)path error:(NSError **)error; 49 | - (BOOL)saveSettingsToPlist:(NSString *)path error:(NSError **)error; 50 | 51 | #pragma mark - Values handling 52 | 53 | - (id)objectForKey:(NSString *)key; 54 | - (void)setObject:(id)value forKey:(NSString *)key; 55 | 56 | - (BOOL)boolForKey:(NSString *)key; 57 | - (void)setBool:(BOOL)value forKey:(NSString *)key; 58 | 59 | - (NSInteger)integerForKey:(NSString *)key; 60 | - (void)setInteger:(NSInteger)value forKey:(NSString *)key; 61 | 62 | - (NSUInteger)unsignedIntegerForKey:(NSString *)key; 63 | - (void)setUnsignedInteger:(NSUInteger)value forKey:(NSString *)key; 64 | 65 | - (CGFloat)floatForKey:(NSString *)key; 66 | - (void)setFloat:(CGFloat)value forKey:(NSString *)key; 67 | 68 | #pragma mark - Arguments handling 69 | 70 | - (void)addArgument:(NSString *)argument; 71 | - (GBSettings *)settingsForArgument:(NSString *)argument; 72 | @property (nonatomic, strong) NSArray *arguments; 73 | 74 | #pragma mark - Registration & low level handling 75 | 76 | - (void)registerArrayForKey:(NSString *)key; 77 | - (id)objectForLocalKey:(NSString *)key; 78 | - (void)setObject:(id)value forLocalKey:(NSString *)key; 79 | 80 | #pragma mark - Introspection 81 | 82 | - (void)enumerateSettings:(void(^)(GBSettings *settings, BOOL *stop))handler; 83 | - (GBSettings *)settingsForArrayValue:(NSString *)value key:(NSString *)key; 84 | - (GBSettings *)settingsForKey:(NSString *)key; 85 | - (BOOL)isKeyPresentAtThisLevel:(NSString *)key; 86 | - (BOOL)isKeyArray:(NSString *)key; 87 | 88 | #pragma mark - Properties 89 | 90 | @property (nonatomic, readonly, copy) NSString *name; 91 | @property (nonatomic, readonly, strong) GBSettings *parent; 92 | 93 | @end 94 | 95 | #pragma mark - Convenience one-line synthesize macros for concrete properties 96 | 97 | #define GB_SYNTHESIZE_PROPERTY(type, accessorSel, mutatorSel, valueAccessor, valueMutator, key, val) \ 98 | - (type)accessorSel { return [self valueAccessor:key]; } \ 99 | - (void)mutatorSel:(type)value { [self valueMutator:val forKey:key]; } 100 | #define GB_SYNTHESIZE_OBJECT(type, accessorSel, mutatorSel, key) GB_SYNTHESIZE_PROPERTY(type, accessorSel, mutatorSel, objectForKey, setObject, key, value) 101 | #define GB_SYNTHESIZE_COPY(type, accessorSel, mutatorSel, key) GB_SYNTHESIZE_PROPERTY(type, accessorSel, mutatorSel, objectForKey, setObject, key, [value copy]) 102 | #define GB_SYNTHESIZE_BOOL(accessorSel, mutatorSel, key) GB_SYNTHESIZE_PROPERTY(BOOL, accessorSel, mutatorSel, boolForKey, setBool, key, value) 103 | #define GB_SYNTHESIZE_INT(accessorSel, mutatorSel, key) GB_SYNTHESIZE_PROPERTY(NSInteger, accessorSel, mutatorSel, integerForKey, setInteger, key, value) 104 | #define GB_SYNTHESIZE_UINT(accessorSel, mutatorSel, key) GB_SYNTHESIZE_PROPERTY(NSUInteger, accessorSel, mutatorSel, unsignedIntegerForKey, setUnsignedInteger, key, value) 105 | #define GB_SYNTHESIZE_FLOAT(accessorSel, mutatorSel, key) GB_SYNTHESIZE_PROPERTY(CGFloat, accessorSel, mutatorSel, floatForKey, setFloat, key, value) 106 | -------------------------------------------------------------------------------- /symbolicator/ArchiveHandler.swift: -------------------------------------------------------------------------------- 1 | /* 2 | 3 | Created by Tomaz Kragelj on 10.06.2014. 4 | Copyright (c) 2014 Gentle Bytes. All rights reserved. 5 | 6 | */ 7 | 8 | import Foundation 9 | 10 | class ArchiveHandler { 11 | init(path: String) { 12 | self.dwarfPathsByIdentifiers = Dictionary() 13 | self.basePath = path 14 | } 15 | 16 | /// Returns map where keys are names of binaries with corresponding full path to DWARF file. 17 | func dwarfPathWithIdentifier(_ identifier: String, version: String, build: String) -> String? { 18 | // If we don't have any dwarf files scanned, do so now. 19 | if self.dwarfPathsByIdentifiers.count == 0 { 20 | let manager = FileManager.default 21 | let fullBasePath = (self.basePath as NSString).standardizingPath 22 | manager.enumerateDirectoriesAtPath(fullBasePath) { dateFolder in 23 | manager.enumerateDirectoriesAtPath(dateFolder) { buildFolder in 24 | // If there's no plist file at the given path, ignore it. 25 | let plistPath = (buildFolder as NSString).appendingPathComponent("Info.plist") 26 | if !manager.fileExists(atPath: plistPath) { return } 27 | 28 | // Load plist into dictionary. 29 | let plistData = try! Data(contentsOf: URL(fileURLWithPath: plistPath), options: .uncached) 30 | let plistContents = try! PropertyListSerialization.propertyList(from: plistData, options: PropertyListSerialization.ReadOptions(rawValue: 0), format: nil) 31 | 32 | // Read application properties. 33 | let applicationInfo = self.applicationInformationWithInfoPlist(plistContents as! NSDictionary) 34 | 35 | // Scan for all subfolders of dSYMs folder. 36 | manager.enumerateDirectoriesAtPath("\(buildFolder)/dSYMs") { subpath in 37 | // Delete .app.dSYM or .framework.dSYM and prepare path to contained DWARF file. 38 | let binaryNameWithExtension = ((subpath as NSString).lastPathComponent as NSString).deletingPathExtension 39 | let binaryName = (binaryNameWithExtension as NSString).deletingPathExtension 40 | let dwarfPath = "\(subpath)/Contents/Resources/DWARF/\(binaryName)" 41 | 42 | // Add the key to DWARF file for this binary. 43 | let dwarfKey = self.dwarfKeyWithIdentifier(binaryName, version: applicationInfo.version, build: applicationInfo.build) 44 | self.dwarfPathsByIdentifiers[dwarfKey] = dwarfPath 45 | 46 | // If this is the main application binary, also create the key with bundle identifier. 47 | if binaryNameWithExtension == applicationInfo.name { 48 | let identifierKey = self.dwarfKeyWithIdentifier(applicationInfo.identifier, version: applicationInfo.version, build: applicationInfo.build) 49 | self.dwarfPathsByIdentifiers[identifierKey] = dwarfPath 50 | } 51 | } 52 | } 53 | } 54 | } 55 | 56 | // Try to get dwarf path using build number first. If found, use it. 57 | let archiveKey = self.dwarfKeyWithIdentifier(identifier, version: version, build: build) 58 | if let result = self.dwarfPathsByIdentifiers[archiveKey] { 59 | return result 60 | } 61 | 62 | // Try to use generic "any build" for given version (older versions of Xcode didn't save build number to archive plist). If found, use it. 63 | let genericArchiveKey = self.dwarfKeyWithIdentifier(identifier, version: version, build: "") 64 | if let result = self.dwarfPathsByIdentifiers[genericArchiveKey] { 65 | return result 66 | } 67 | 68 | // If there's no archive match, return nil 69 | return nil 70 | } 71 | 72 | fileprivate func applicationInformationWithInfoPlist(_ plistContents: NSDictionary) -> (name: String, identifier: String, version: String, build: String) { 73 | var applicationName = "" 74 | var applicationIdentifier = "" 75 | var applicationVersion = "" 76 | var applicationBuild = "" 77 | 78 | if let applicationProperties = plistContents.object(forKey: "ApplicationProperties") as? NSDictionary { 79 | if let path = applicationProperties.object(forKey: "ApplicationPath") as? String { 80 | applicationName = (path as NSString).lastPathComponent 81 | } 82 | if let identifier = applicationProperties.object(forKey: "CFBundleIdentifier") as? String { 83 | applicationIdentifier = identifier 84 | } 85 | if let version = applicationProperties.object(forKey: "CFBundleShortVersionString") as? String { 86 | applicationVersion = version 87 | } 88 | if let build = applicationProperties.object(forKey: "CFBundleVersion") as? String { 89 | applicationBuild = build 90 | } 91 | } 92 | 93 | return (applicationName, applicationIdentifier, applicationVersion, applicationBuild) 94 | } 95 | 96 | fileprivate func dwarfKeyWithIdentifier(_ identifier: String, version: String, build: String) -> String { 97 | if build.characters.count == 0 { 98 | return "\(identifier) \(version) ANYBUILD" 99 | } 100 | return "\(identifier) \(version) \(build)" 101 | } 102 | 103 | fileprivate let basePath: String 104 | fileprivate var dwarfPathsByIdentifiers: Dictionary 105 | } 106 | 107 | extension FileManager { 108 | func enumerateDirectoriesAtPath(_ path: String, block: (_ path: String) -> Void) { 109 | let subpaths = try! self.contentsOfDirectory(atPath: path) as [String] 110 | for subpath in subpaths { 111 | let fullPath = (path as NSString).appendingPathComponent(subpath) 112 | if !self.isDirectoryAtPath(fullPath as NSString) { continue } 113 | block(fullPath) 114 | } 115 | } 116 | 117 | func isDirectoryAtPath(_ path: NSString) -> Bool { 118 | let attributes = try! self.attributesOfItem(atPath: path as String) 119 | if attributes[FileAttributeKey.type] as! FileAttributeType == FileAttributeType.typeDirectory { 120 | return true 121 | } 122 | return false 123 | } 124 | } 125 | -------------------------------------------------------------------------------- /Pods/GBCli/GBCli/src/GBSettings.m: -------------------------------------------------------------------------------- 1 | 2 | // GBSettings.m 3 | // GBCli 4 | // 5 | // Created by Tomaž Kragelj on 3/13/12. 6 | // Copyright (c) 2012 Tomaz Kragelj. All rights reserved. 7 | // 8 | 9 | #import "GBSettings.h" 10 | 11 | static NSString * const GBSettingsArgumentsKey = @"B450A340-EC4F-40EC-B18D-B52DB881A16A"; 12 | 13 | #pragma mark - 14 | 15 | @interface GBSettings () 16 | @property (nonatomic, readwrite, copy) NSString *name; 17 | @property (nonatomic, readwrite, strong) GBSettings *parent; 18 | @property (nonatomic, strong) NSMutableSet *arrayKeys; 19 | @property (nonatomic, strong) NSMutableDictionary *storage; 20 | @end 21 | 22 | #pragma mark - 23 | 24 | @implementation GBSettings 25 | 26 | #pragma mark - Initialization & disposal 27 | 28 | + (instancetype)settingsWithName:(NSString *)name parent:(GBSettings *)parent { 29 | return [[self alloc] initWithName:name parent:parent]; 30 | } 31 | 32 | - (instancetype)initWithName:(NSString *)name parent:(GBSettings *)parent { 33 | self = [super init]; 34 | if (self) { 35 | self.name = name; 36 | self.parent = parent; 37 | self.arrayKeys = [NSMutableSet set]; 38 | self.storage = [NSMutableDictionary dictionary]; 39 | [self registerArrayForKey:GBSettingsArgumentsKey]; 40 | } 41 | return self; 42 | } 43 | 44 | #pragma mark - Settings serialization support 45 | 46 | - (BOOL)loadSettingsFromPlist:(NSString *)path error:(NSError **)error { 47 | NSFileManager *manager = [NSFileManager defaultManager]; 48 | if (![manager fileExistsAtPath:path]) return NO; 49 | 50 | // Load data into dictionary. 51 | NSData* data = [NSData dataWithContentsOfFile:path options:0 error:error]; 52 | if (!data) return NO; 53 | NSDictionary *values = [NSPropertyListSerialization propertyListWithData:data options:NSPropertyListImmutable format:NULL error:error]; 54 | if (!values) return NO; 55 | 56 | // Prepare block that will handle individual key. 57 | void(^handleKey)(NSString *, id) = ^(NSString *key, id value) { 58 | while ([key hasPrefix:@"-"]) key = [key substringFromIndex:1]; 59 | [self setObject:value forKey:key]; 60 | }; 61 | 62 | // Copy all values to ourself. Remove - or -- prefix which can optionally be used in the file! 63 | [self.storage removeAllObjects]; 64 | [values enumerateKeysAndObjectsUsingBlock:^(NSString *key, id value, BOOL *stop) { 65 | if ([value isKindOfClass:[NSDictionary class]]) { 66 | [value enumerateKeysAndObjectsUsingBlock:^(NSString *valueKey, id valueValue, BOOL *stop) { 67 | handleKey(valueKey, valueValue); 68 | }]; 69 | return; 70 | } 71 | handleKey(key, value); 72 | }]; 73 | return YES; 74 | } 75 | 76 | - (BOOL)saveSettingsToPlist:(NSString *)path error:(NSError **)error { 77 | // Note that we only save settings from current level! 78 | NSData *data = [NSPropertyListSerialization dataWithPropertyList:self.storage format:NSPropertyListXMLFormat_v1_0 options:0 error:error]; 79 | if (!data) return NO; 80 | return [data writeToFile:path options:NSDataWritingAtomic error:error]; 81 | } 82 | 83 | #pragma mark - Values handling 84 | 85 | - (id)objectForKey:(NSString *)key { 86 | if ([self isKeyArray:key]) { 87 | NSMutableArray *allValues = [NSMutableArray array]; 88 | GBSettings *settings = self; 89 | while (settings) { 90 | NSArray *currentLevelValues = [settings objectForLocalKey:key]; 91 | [allValues addObjectsFromArray:currentLevelValues]; 92 | settings = settings.parent; 93 | } 94 | return allValues; 95 | } 96 | GBSettings *level = [self settingsForKey:key]; 97 | return [level objectForLocalKey:key]; 98 | } 99 | - (void)setObject:(id)value forKey:(NSString *)key { 100 | if ([self isKeyArray:key] && ![key isKindOfClass:[NSArray class]]) { 101 | NSMutableArray *array = [self.storage objectForKey:key]; 102 | if (![array isKindOfClass:[NSMutableArray class]]) { 103 | id existing = array; 104 | array = [NSMutableArray array]; 105 | if (existing) [array addObject:existing]; 106 | [self setObject:array forLocalKey:key]; 107 | } 108 | if ([value isKindOfClass:[NSArray class]]) 109 | [array addObjectsFromArray:value]; 110 | else 111 | [array addObject:value]; 112 | return; 113 | } 114 | [self setObject:value forLocalKey:key]; 115 | } 116 | 117 | - (BOOL)boolForKey:(NSString *)key { 118 | NSNumber *number = [self objectForKey:key]; 119 | return [number boolValue]; 120 | } 121 | - (void)setBool:(BOOL)value forKey:(NSString *)key { 122 | [self setObject:@(value) forKey:key]; 123 | } 124 | 125 | - (NSInteger)integerForKey:(NSString *)key { 126 | NSNumber *number = [self objectForKey:key]; 127 | return [number integerValue]; 128 | } 129 | - (void)setInteger:(NSInteger)value forKey:(NSString *)key { 130 | [self setObject:@(value) forKey:key]; 131 | } 132 | 133 | - (NSUInteger)unsignedIntegerForKey:(NSString *)key { 134 | NSNumber *number = [self objectForKey:key]; 135 | return (NSUInteger)[number integerValue]; 136 | } 137 | - (void)setUnsignedInteger:(NSUInteger)value forKey:(NSString *)key { 138 | [self setObject:@(value) forKey:key]; 139 | } 140 | 141 | - (CGFloat)floatForKey:(NSString *)key { 142 | NSNumber *number = [self objectForKey:key]; 143 | return [number doubleValue]; 144 | } 145 | - (void)setFloat:(CGFloat)value forKey:(NSString *)key { 146 | [self setObject:@(value) forKey:key]; 147 | } 148 | 149 | #pragma mark - Arguments handling 150 | 151 | - (void)addArgument:(NSString *)argument { 152 | [self setObject:argument forKey:GBSettingsArgumentsKey]; 153 | } 154 | 155 | - (GBSettings *)settingsForArgument:(NSString *)argument { 156 | return [self settingsForArrayValue:argument key:GBSettingsArgumentsKey]; 157 | } 158 | 159 | GB_SYNTHESIZE_OBJECT(NSArray *, arguments, setArguments, GBSettingsArgumentsKey) 160 | 161 | #pragma mark - Registration & low level handling 162 | 163 | - (void)registerArrayForKey:(NSString *)key { 164 | [self.arrayKeys addObject:key]; 165 | } 166 | 167 | - (id)objectForLocalKey:(NSString *)key { 168 | return [self.storage objectForKey:key]; 169 | } 170 | - (void)setObject:(id)value forLocalKey:(NSString *)key { 171 | [self.storage setObject:value forKey:key]; 172 | } 173 | 174 | #pragma mark - Introspection 175 | 176 | - (void)enumerateSettings:(void(^)(GBSettings *settings, BOOL *stop))handler { 177 | GBSettings *settings = self; 178 | BOOL stop = NO; 179 | while (settings) { 180 | handler(settings, &stop); 181 | if (stop) break; 182 | settings = settings.parent; 183 | } 184 | } 185 | 186 | - (GBSettings *)settingsForArrayValue:(NSString *)value key:(NSString *)key { 187 | __block GBSettings *result = nil; 188 | [self enumerateSettings:^(GBSettings *settings, BOOL *stop) { 189 | NSArray *arguments = [settings objectForLocalKey:key]; 190 | if ([arguments containsObject:value]) { 191 | result = settings; 192 | *stop = YES; 193 | } 194 | }]; 195 | return result; 196 | } 197 | 198 | - (GBSettings *)settingsForKey:(NSString *)key { 199 | __block GBSettings *result = nil; 200 | [self enumerateSettings:^(GBSettings *settings, BOOL *stop) { 201 | if ([settings isKeyPresentAtThisLevel:key]) { 202 | result = settings; 203 | *stop = YES; 204 | } 205 | }]; 206 | return result; 207 | } 208 | 209 | - (BOOL)isKeyPresentAtThisLevel:(NSString *)key { 210 | if ([self.storage objectForKey:key]) return YES; 211 | return NO; 212 | } 213 | 214 | - (BOOL)isKeyArray:(NSString *)key { 215 | return [self.arrayKeys containsObject:key]; 216 | } 217 | 218 | @end 219 | -------------------------------------------------------------------------------- /Pods/GBCli/GBCli/src/GBOptionsHelper.h: -------------------------------------------------------------------------------- 1 | // 2 | // GBOptionsHelper.h 3 | // GBCli 4 | // 5 | // Created by Tomaž Kragelj on 3/15/12. 6 | // Copyright (c) 2012 Tomaz Kragelj. All rights reserved. 7 | // 8 | 9 | #import 10 | #import "GBCommandLineParser.h" 11 | 12 | @class GBCommandLineParser; 13 | @class GBSettings; 14 | 15 | /** Various option flags. You can also use GBValueRequirement values here! */ 16 | typedef NS_OPTIONS(NSUInteger, GBOptionFlags) { 17 | GBOptionRequiredValue = 0, ///< Command line argument requires a value. 18 | GBOptionOptionalValue = 1 << 0, ///< Command line argument can optionally have a value, but is not required. 19 | GBOptionNoValue = 1 << 1,///< Command line argument is on/off switch. 20 | GBOptionSeparator = 1 << 3, ///< Option is separator, not real option definition. 21 | GBOptionGroup = 1 << 4, ///< Option is an option gorup, not actual option definition. 22 | GBOptionNoCmdLine = 1 << 5, ///< Option is not used on command line, don't register to parser. 23 | GBOptionNoPrint = 1 << 6, ///< Option should be excluded from print settings display. 24 | GBOptionNoHelp = 1 << 7, ///< Option should be excluded from help display. 25 | GBOptionInvisible = GBOptionNoPrint | GBOptionNoHelp, 26 | }; 27 | 28 | /** Description of a single option or separator. */ 29 | typedef struct { 30 | char shortOption; ///< Short option char or `0` if not used. 31 | __unsafe_unretained NSString *longOption; ///< Long option name - required for options. 32 | __unsafe_unretained NSString *description; ///< Description of the option. 33 | GBOptionFlags flags; ///< Various flags. 34 | } GBOptionDefinition; 35 | 36 | /** Block used to fetch strings from user code. */ 37 | typedef NSString *(^GBOptionStringBlock)(void); 38 | 39 | #pragma mark - 40 | 41 | /** Helper class for nicer integration between GBSettings and GBCommandLineParser. 42 | 43 | Although using this class is optional, it provides several nice features and automations out of the box (although you can subclass if your want to customize): 44 | 45 | - Registration of options to GBCommandLineParser. 46 | - Print version information (subclass to customize). 47 | - Print help (subclass to customize). 48 | - Print values, preserving their GBSettings level hierarchy (subclass to customize). 49 | 50 | One example of usage: 51 | 52 | ``` 53 | int main(int argc, char **argv) { 54 | GBSettings *factory = [GBSettings settingsWithName:@"Factory" parent:nil]; 55 | GBSettings *settings = [GBSettings settingsWithName@"CmdLine" parent:factory]; 56 | 57 | OptionsHelper *options = [[OptionsHelper alloc] init]; 58 | [options registerSeparator:@"PATHS"]; 59 | [options registerOption:'i' long:@"input" description:@"Input path" flags:GBValueRequired]; 60 | [options registerOption:'o' long:@"output" description:@"Output path" flags:GBValueRequired]; 61 | [options registerSeparator:@"MISCELLANEOUS"]; 62 | [options registerOption:0 long:@"version" description:@"Display version and exit" flags:GBValueNone|GBOptionNoPrint]; 63 | [options registerOption:'?' long:@"help" description:@"Display this help and exit" flags:GBValueNone|GBOptionNoPrint]; 64 | 65 | GBCommandLineParser *parser = [[GBCommandLineParser alloc] init]; 66 | [options registerOptionsToCommandLineParser:parser]; 67 | __block BOOL commandLineValid = YES; 68 | __block BOOL finished = NO; 69 | [parser parseOptionsWithArguments:argv count:argc block:^(NSString *argument, id value, BOOL *stop) { 70 | if (value == GBCommandLineArgumentResults.unknownArgument) { 71 | // unknown argument 72 | commandLineValid = NO; 73 | *stop = YES; 74 | } else if (value == GBCommandLineArgumentResults.missingValue) { 75 | // known argument but missing value 76 | commandLineValid = NO; 77 | *stop = YES; 78 | } else if ([argument isEqualToString:@"version"]) { 79 | [options printVersion]; 80 | finished = YES; 81 | *stop = YES; 82 | } else if ([argument isEqualToString:@"help"]) { 83 | [options printHelp]; 84 | finished = YES; 85 | *stop = YES; 86 | } else { 87 | [settings setObject:value forKey:argument]; 88 | } 89 | }]; 90 | 91 | if (finished) return 0; 92 | if (!commandLineValue) return 1; 93 | 94 | [options printValuesFromSettings:settings]; 95 | return 0; 96 | } 97 | ``` 98 | 99 | There are several hooks by which you can inject text into default output. The hooks use block API, for example printValuesHeader, printHelpHeader etc. An example: 100 | 101 | ``` 102 | int main(int argc, char **argv) { 103 | ... 104 | GBOptionsHelper *options = [[OptionsHelper alloc] init]; 105 | options.printHelpHeader = ^{ return @"Usage: MyTool [OPTIONS] "; }; 106 | options.printHelpFooter = ^{ return @"Thanks to everyone for their help..."; }; 107 | ... 108 | } 109 | ``` 110 | 111 | These blocks are automatically invoked when and if necessary. The strings you return from them can contain several placeholders: 112 | 113 | - `%APPNAME` is replaced by the application name, either the value returned from applicationName, or auto-generated by GBOptionsHelper itself. 114 | - `%APPVERSION` is replaced by the application version, if given via applicationVersion block or empty string otherwise. 115 | - `%APPBUILD` is replaced by the application build number, if given via applicationBuild block or empty strings otherwise. 116 | 117 | @warning **Note:** Only values from blocks related to printing text are checked for placeholders, applicationName, applicationVersion and applicationBuild aren't! 118 | */ 119 | @interface GBOptionsHelper : NSObject 120 | 121 | #pragma mark - Options registration 122 | 123 | - (void)registerOptionsFromDefinitions:(GBOptionDefinition *)definitions; // this mode doesn't support option groups at this moment! 124 | - (void)registerSeparator:(NSString *)description; 125 | - (void)registerGroup:(NSString *)name description:(NSString *)description optionsBlock:(void(^)(GBOptionsHelper *options))block; 126 | - (void)registerGroup:(NSString *)name description:(NSString *)description flags:(GBOptionFlags)flags optionsBlock:(void(^)(GBOptionsHelper *options))block; 127 | - (void)registerOption:(char)shortName long:(NSString *)longName description:(NSString *)description flags:(GBOptionFlags)flags; 128 | 129 | #pragma mark - Integration with other components 130 | 131 | - (void)registerOptionsToCommandLineParser:(GBCommandLineParser *)parser; 132 | 133 | #pragma mark - Diagnostic info 134 | 135 | - (void)printValuesFromSettings:(GBSettings *)settings; 136 | - (void)printVersion; 137 | - (void)printHelp; 138 | 139 | #pragma mark - Getting information from user 140 | 141 | @property (nonatomic, copy) GBOptionStringBlock applicationName; 142 | @property (nonatomic, copy) GBOptionStringBlock applicationVersion; 143 | @property (nonatomic, copy) GBOptionStringBlock applicationBuild; 144 | 145 | #pragma mark - Hooks for injecting text to output 146 | 147 | @property (nonatomic, copy) GBOptionStringBlock printValuesHeader; 148 | @property (nonatomic, copy) GBOptionStringBlock printValuesArgumentsHeader; 149 | @property (nonatomic, copy) GBOptionStringBlock printValuesOptionsHeader; 150 | @property (nonatomic, copy) GBOptionStringBlock printValuesFooter; 151 | 152 | @property (nonatomic, copy) GBOptionStringBlock printHelpHeader; 153 | @property (nonatomic, copy) GBOptionStringBlock printHelpFooter; 154 | 155 | @end 156 | 157 | #pragma mark - 158 | 159 | @interface GBCommandLineParser (GBOptionsHelper) 160 | - (void)registerOptions:(GBOptionsHelper *)options; 161 | @end 162 | -------------------------------------------------------------------------------- /symbolicator/ObjCRegex/RegExCategories.m: -------------------------------------------------------------------------------- 1 | // 2 | // RegExCategories.m 3 | // 4 | // https://github.com/bendytree/Objective-C-RegEx-Categories 5 | // 6 | // 7 | // The MIT License (MIT) 8 | // 9 | // Copyright (c) 2013 Josh Wright <@BendyTree> 10 | // 11 | // Permission is hereby granted, free of charge, to any person obtaining a copy 12 | // of this software and associated documentation files (the "Software"), to deal 13 | // in the Software without restriction, including without limitation the rights 14 | // to use, copy, modify, merge, publish, distribute, sublicense, and/or sell 15 | // copies of the Software, and to permit persons to whom the Software is 16 | // furnished to do so, subject to the following conditions: 17 | // 18 | // The above copyright notice and this permission notice shall be included in all 19 | // copies or substantial portions of the Software. 20 | // 21 | // THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR 22 | // IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, 23 | // FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE 24 | // AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER 25 | // LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, 26 | // OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE 27 | // SOFTWARE. 28 | // 29 | 30 | #import "RegExCategories.h" 31 | 32 | @implementation NSRegularExpression (ObjectiveCRegexCategories) 33 | 34 | - (id) initWithPattern:(NSString*)pattern 35 | { 36 | return [self initWithPattern:pattern options:0 error:nil]; 37 | } 38 | 39 | + (NSRegularExpression*) rx:(NSString*)pattern 40 | { 41 | return [[self alloc] initWithPattern:pattern]; 42 | } 43 | 44 | + (NSRegularExpression*) rx:(NSString*)pattern ignoreCase:(BOOL)ignoreCase 45 | { 46 | return [[self alloc] initWithPattern:pattern options:ignoreCase?NSRegularExpressionCaseInsensitive:0 error:nil]; 47 | } 48 | 49 | + (NSRegularExpression*) rx:(NSString*)pattern options:(NSRegularExpressionOptions)options 50 | { 51 | return [[self alloc] initWithPattern:pattern options:options error:nil]; 52 | } 53 | 54 | - (BOOL) isMatch:(NSString*)matchee 55 | { 56 | return [self numberOfMatchesInString:matchee options:0 range:NSMakeRange(0, matchee.length)] > 0; 57 | } 58 | 59 | - (int) indexOf:(NSString*)matchee 60 | { 61 | NSRange range = [self rangeOfFirstMatchInString:matchee options:0 range:NSMakeRange(0, matchee.length)]; 62 | return range.location == NSNotFound ? -1 : (int)range.location; 63 | } 64 | 65 | - (NSArray*) split:(NSString *)str 66 | { 67 | NSRange range = NSMakeRange(0, str.length); 68 | 69 | //get locations of matches 70 | NSMutableArray* matchingRanges = [NSMutableArray array]; 71 | NSArray* matches = [self matchesInString:str options:0 range:range]; 72 | for(NSTextCheckingResult* match in matches) { 73 | [matchingRanges addObject:[NSValue valueWithRange:match.range]]; 74 | } 75 | 76 | //invert ranges - get ranges of non-matched pieces 77 | NSMutableArray* pieceRanges = [NSMutableArray array]; 78 | 79 | //add first range 80 | [pieceRanges addObject:[NSValue valueWithRange:NSMakeRange(0, 81 | (matchingRanges.count == 0 ? str.length : [matchingRanges[0] rangeValue].location))]]; 82 | 83 | //add between splits ranges and last range 84 | for(int i=0; i=0; i--) { 120 | NSTextCheckingResult* match = matches[i]; 121 | NSString* matchStr = [string substringWithRange:match.range]; 122 | NSString* replacement = replacer(matchStr); 123 | [result replaceCharactersInRange:match.range withString:replacement]; 124 | } 125 | 126 | return result; 127 | } 128 | 129 | - (NSString*) replace:(NSString *)string withDetailsBlock:(NSString*(^)(RxMatch* match))replacer 130 | { 131 | //no replacer? just return 132 | if (!replacer) return string; 133 | 134 | //copy the string so we can replace subsections 135 | NSMutableString* replaced = [string mutableCopy]; 136 | 137 | //get matches 138 | NSArray* matches = [self matchesInString:string options:0 range:NSMakeRange(0, string.length)]; 139 | 140 | //replace each match (right to left so indexing doesn't get messed up) 141 | for (int i=(int)matches.count-1; i>=0; i--) { 142 | NSTextCheckingResult* result = matches[i]; 143 | RxMatch* match = [self resultToMatch:result original:string]; 144 | NSString* replacement = replacer(match); 145 | [replaced replaceCharactersInRange:result.range withString:replacement]; 146 | } 147 | 148 | return replaced; 149 | } 150 | 151 | - (NSArray*) matches:(NSString*)str 152 | { 153 | NSMutableArray* matches = [NSMutableArray array]; 154 | 155 | NSArray* results = [self matchesInString:str options:0 range:NSMakeRange(0, str.length)]; 156 | for (NSTextCheckingResult* result in results) { 157 | NSString* match = [str substringWithRange:result.range]; 158 | [matches addObject:match]; 159 | } 160 | 161 | return matches; 162 | } 163 | 164 | - (NSString*) firstMatch:(NSString*)str 165 | { 166 | NSTextCheckingResult* match = [self firstMatchInString:str options:0 range:NSMakeRange(0, str.length)]; 167 | 168 | if (!match) return nil; 169 | 170 | return [str substringWithRange:match.range]; 171 | } 172 | 173 | - (RxMatch*) resultToMatch:(NSTextCheckingResult*)result original:(NSString*)original 174 | { 175 | RxMatch* match = [[RxMatch alloc] init]; 176 | match.original = original; 177 | match.range = result.range; 178 | match.value = result.range.length ? [original substringWithRange:result.range] : nil; 179 | 180 | //groups 181 | NSMutableArray* groups = [NSMutableArray array]; 182 | match.groups = groups; 183 | for(int i=0; i String? { 15 | // Extract all information about the process that crashed. Exit if not possible. 16 | guard let information = extractProcessInformation(contents) else { 17 | return nil 18 | } 19 | 20 | // Store parameters for later use. 21 | self.path = path 22 | self.archiveHandler = archiveHandler 23 | 24 | // Prepare array of all lines needed for symbolication. 25 | let matches = linesToSymbolicate(contents as NSString) 26 | print("Found \(matches.count) lines that need symbolication") 27 | 28 | // Symbolicate all matches. 29 | return symbolicateString(contents, information: information, matches: matches) 30 | } 31 | 32 | fileprivate func linesToSymbolicate(_ contents: NSString) -> [RxMatch] { 33 | let pattern = "^[0-9]+?\\s+?([^?]+?)\\s+?(0x[0-9a-fA-F]+?)\\s+?(.+?)$" 34 | let regex = pattern.toRx(options: .anchorsMatchLines) 35 | 36 | // Find all matches. 37 | let matches = contents.matches(withDetails: regex) as! [RxMatch] 38 | let whitespace = CharacterSet.whitespaces 39 | 40 | // Filter just the ones that have a hex number instead of symbol. 41 | return matches.filter { match in 42 | guard let symbolOrAddress = (match.groups[3] as! RxMatchGroup).value else { 43 | return false 44 | } 45 | 46 | // Only contains hexadecimal address. 47 | if symbolOrAddress.hasPrefix("0x") { 48 | return true 49 | } 50 | 51 | // Contains "binary + address" - for example "Startupizer2 + 608348" 52 | let binary = (match.groups[1] as! RxMatchGroup).value.trimmingCharacters(in: whitespace) 53 | if symbolOrAddress.contains(binary) && symbolOrAddress.contains("+") { 54 | return true 55 | } 56 | 57 | return false 58 | } 59 | } 60 | 61 | fileprivate func symbolicateString(_ contents: String, information: CrashlogInformation, matches: [RxMatch]) -> String { 62 | // Symbolicate all matches. Each entry corresponds to the same match in given array. 63 | let whitespace = CharacterSet.whitespacesAndNewlines 64 | var result = contents 65 | numberOfSymbolizedAddresses = 0 66 | for match in matches { 67 | // Add delimiter above each symbolication when verbose mode is on. 68 | if settings.printVerbose { 69 | print("") 70 | } 71 | 72 | // Prepare binary and base address. 73 | let binary = (match.groups[1] as! RxMatchGroup).value!.trimmingCharacters(in: whitespace) 74 | guard let baseAddress = baseAddressForSymbolication(contents, identifier: binary) else { 75 | continue 76 | } 77 | 78 | // Prepare dwarf path for this binary. 79 | guard let dwarfPath = archiveHandler.dwarfPathWithIdentifier(binary, version: information.version, build: information.build) else { 80 | print("> \(binary): missing DWARF file!") 81 | continue 82 | } 83 | 84 | // Symbolicate addresses. 85 | let address = (match.groups[2] as! RxMatchGroup).value! 86 | guard let symbolizedAddress = symbolicateAddresses(baseAddress, architecture: information.architecture, dwarfPath: dwarfPath, addresses: [address]).first else { 87 | print("> \(binary) \(address): no symbol found!") 88 | continue 89 | } 90 | 91 | // If no symbol is available, ignore. 92 | let originalString = match.value! 93 | if (symbolizedAddress.characters.count == 0) { 94 | print("> \(binary) \(address): no symbol found!") 95 | continue 96 | } 97 | 98 | // Replace all occurrences within the file. 99 | let locationInOriginalString = (match.groups[3] as! RxMatchGroup).range.location - match.range.location 100 | let replacementPrefix = originalString.substring(to: originalString.characters.index(originalString.startIndex, offsetBy: locationInOriginalString)) 101 | let replacementString = "\(replacementPrefix)\(symbolizedAddress)" 102 | result = result.replacingOccurrences(of: originalString, with: replacementString) 103 | print("> \(binary) \(address): \(symbolizedAddress)") 104 | numberOfSymbolizedAddresses += 1 105 | } 106 | 107 | if matches.count > 0 { 108 | if settings.printVerbose { 109 | print("") 110 | } 111 | 112 | let filename = (path as NSString).lastPathComponent 113 | if numberOfSymbolizedAddresses == matches.count { 114 | print("All \(matches.count) \(filename) addresses symbolized") 115 | } else { 116 | print("\(numberOfSymbolizedAddresses) of \(matches.count) \(filename) addresses symbolized") 117 | } 118 | } 119 | 120 | return result 121 | } 122 | 123 | fileprivate func baseAddresses(_ contents: String, matches: [RxMatch]) -> [String: (String, [RxMatch])] { 124 | let ignoredChars = CharacterSet.whitespacesAndNewlines 125 | 126 | var result = [String: (String, [RxMatch])]() 127 | 128 | // Prepare an array of base addresses per binary. 129 | for match in matches { 130 | // Prepare binary and address information. 131 | let binary = (match.groups[1] as! RxMatchGroup).value.trimmingCharacters(in: ignoredChars) 132 | if binary.characters.count == 0 { 133 | continue 134 | } 135 | 136 | // If we already matched this pair, reuse it. 137 | if var existingEntry = result[binary] { 138 | var matches = existingEntry.1 139 | matches.append(match) 140 | existingEntry.1 = matches 141 | continue 142 | } 143 | 144 | // Otherwise gather it from crash log. Ignore if no match is found. 145 | guard let baseAddress = baseAddressForSymbolication(contents, identifier: binary) else { 146 | continue 147 | } 148 | 149 | // Add address to previous addresses so we don't have to repeat. 150 | result[binary] = (baseAddress, [match]) 151 | } 152 | 153 | return result 154 | } 155 | 156 | fileprivate func symbolicateAddresses(_ baseAddress: String, architecture: String, dwarfPath: String, addresses: [String]) -> [String] { 157 | let arch = architecture.lowercased().replacingOccurrences(of: "-", with: "_") 158 | let stdOutPipe = Pipe() 159 | let stdErrPipe = Pipe() 160 | let task = Process() 161 | task.launchPath = "/usr/bin/xcrun" 162 | task.arguments = ["atos", "-arch", arch, "-o", dwarfPath, "-l", baseAddress] + addresses 163 | task.standardOutput = stdOutPipe 164 | task.standardError = stdErrPipe 165 | task.launch() 166 | task.waitUntilExit() 167 | 168 | let translatedData = stdOutPipe.fileHandleForReading.readDataToEndOfFile() 169 | let translatedString = NSString(data: translatedData, encoding: String.Encoding.ascii.rawValue)! 170 | 171 | if settings.printVerbose { 172 | // Print command line for simpler replication in 173 | let whitespace = CharacterSet.whitespaces 174 | let arguments = task.arguments! as [String] 175 | let cmdline = arguments.reduce("") { 176 | if let _ = $1.rangeOfCharacter(from: whitespace) { 177 | return "\($0) \"\($1)\"" 178 | } 179 | return "\($0) \($1)" 180 | } 181 | print("\(task.launchPath!) \(cmdline)"); 182 | } 183 | 184 | // If there's some error, print it. 185 | let errorData = stdErrPipe.fileHandleForReading.readDataToEndOfFile() 186 | if let errorString = NSString(data: errorData, encoding: String.Encoding.ascii.rawValue), errorString.length > 0 { 187 | print("\(errorString)") 188 | } 189 | 190 | return translatedString.components(separatedBy: "\n") as [String] 191 | } 192 | 193 | fileprivate func baseAddressForSymbolication(_ contents: String, identifier: String) -> String? { 194 | // First attempt to find the whole identifier. 195 | let pattern = "^\\s+(0x[0-9a-fA-F]+)\\s+-\\s+(0x[0-9a-fA-F]+)\\s+[+]?\(identifier)\\s+" 196 | if let regex = pattern.toRx(options: .anchorsMatchLines), let match = regex.firstMatch(withDetails: contents) { 197 | return (match.groups[1] as! RxMatchGroup).value 198 | } 199 | 200 | // If this fails, fall down to generic search for binaries that include the given identifier. Note we have 2 variants: exact or fuzzy. 201 | let result = settings.fuzzySearch ? 202 | baseAddressFuzzyMatcher(contents, identifier: identifier) : 203 | baseAddressMatcher(contents, identifier: identifier) 204 | if let result = result { 205 | return result 206 | } 207 | 208 | print("WARNING: Didn't find starting address for \(identifier)") 209 | return nil 210 | } 211 | 212 | fileprivate func extractProcessInformation(_ contents: String) -> CrashlogInformation? { 213 | let optionalProcessMatch = "^Process:\\s+([^\\[]+) \\[[^\\]]+\\]".toRx(options: NSRegularExpression.Options.anchorsMatchLines)!.firstMatch(withDetails: contents) 214 | if (optionalProcessMatch == nil) { 215 | print("ERROR: Process name is missing!") 216 | return nil 217 | } 218 | 219 | let optionalIdentifierMatch = "^Identifier:\\s+(.+)$".toRx(options: NSRegularExpression.Options.anchorsMatchLines)!.firstMatch(withDetails: contents) 220 | if (optionalIdentifierMatch == nil) { 221 | print("ERROR: Process identifier is missing!") 222 | return nil 223 | } 224 | 225 | let optionalVersionMatch = "^Version:\\s+([^ ]+) \\(([^)]+)\\)$".toRx(options: NSRegularExpression.Options.anchorsMatchLines)!.firstMatch(withDetails: contents) 226 | if (optionalVersionMatch == nil) { 227 | print("ERROR: Process version and build number is missing!") 228 | return nil 229 | } 230 | 231 | let optionalArchitectureMatch = "^Code Type:\\s+([^ \\r\\n]+)".toRx(options: NSRegularExpression.Options.anchorsMatchLines)!.firstMatch(withDetails: contents); 232 | if (optionalArchitectureMatch == nil) { 233 | print("ERROR: Process architecture value is missing!") 234 | return nil 235 | } 236 | 237 | let processGroup1 = optionalProcessMatch!.groups[1] as! RxMatchGroup 238 | let identifierGroup1 = optionalIdentifierMatch!.groups[1] as! RxMatchGroup 239 | let versionGroup1 = optionalVersionMatch!.groups[1] as! RxMatchGroup 240 | let versionGroup2 = optionalVersionMatch!.groups[2] as! RxMatchGroup 241 | let architectureGroup1 = optionalArchitectureMatch!.groups[1] as! RxMatchGroup 242 | 243 | let name = processGroup1.value as String 244 | let identifier = identifierGroup1.value as String 245 | let version = versionGroup1.value as String 246 | let build = versionGroup2.value as String 247 | let architecture = architectureGroup1.value as String 248 | 249 | print("Detected \(identifier) \(architecture) [\(name) \(version) (\(build))]") 250 | return (name, identifier, version, build, architecture) 251 | } 252 | 253 | fileprivate func baseAddressMatcher(_ contents: String, identifier: String) -> String? { 254 | let pattern = "^\\s+(0x[0-9a-fA-F]+)\\s+-\\s+(0x[0-9a-fA-F]+)\\s+[+]?([^\\s]+)\\s+" 255 | if let regex = pattern.toRx(options: .anchorsMatchLines), let matches = regex.matches(withDetails: contents) as? [RxMatch] { 256 | for match in matches { 257 | let binaryIdentifier = (match.groups[3] as! RxMatchGroup).value 258 | if (binaryIdentifier?.contains(identifier))! { 259 | return (match.groups[1] as! RxMatchGroup).value 260 | } 261 | } 262 | } 263 | return nil 264 | } 265 | 266 | fileprivate func baseAddressFuzzyMatcher(_ contents: String, identifier: String) -> String? { 267 | let pattern = "^\\s+(0x[0-9a-fA-F]+)\\s+-\\s+(0x[0-9a-fA-F]+)\\s+[+]?([^\\s]+)\\s+(.+)" 268 | if let regex = pattern.toRx(options: .anchorsMatchLines), let matches = regex.matches(withDetails: contents) as? [RxMatch] { 269 | for match in matches { 270 | // First try to match unique identifier. 271 | if let binaryIdentifier = (match.groups[3] as! RxMatchGroup).value, binaryIdentifier.contains(identifier) { 272 | return (match.groups[1] as! RxMatchGroup).value 273 | } 274 | 275 | // If this doesn't yield results, perhaps given identifier is not unique bundle identifier (com.gentlebytes.startupizer), but instead application name "Startupizer3". In this case search remaining string which includes full path to the binary. 276 | if let applicationIdentifier = (match.groups[4] as! RxMatchGroup).value, applicationIdentifier.contains(identifier) { 277 | return (match.groups[1] as! RxMatchGroup).value 278 | } 279 | } 280 | } 281 | return nil 282 | } 283 | 284 | fileprivate var archiveHandler: ArchiveHandler! 285 | fileprivate var path: String! 286 | fileprivate var numberOfSymbolizedAddresses = 0 287 | } 288 | -------------------------------------------------------------------------------- /symbolicator/ObjCRegex/RegExCategories.h: -------------------------------------------------------------------------------- 1 | // 2 | // RegExCategories.h 3 | // 4 | // https://github.com/bendytree/Objective-C-RegEx-Categories 5 | // 6 | // 7 | // The MIT License (MIT) 8 | // 9 | // Copyright (c) 2013 Josh Wright <@BendyTree> 10 | // 11 | // Permission is hereby granted, free of charge, to any person obtaining a copy 12 | // of this software and associated documentation files (the "Software"), to deal 13 | // in the Software without restriction, including without limitation the rights 14 | // to use, copy, modify, merge, publish, distribute, sublicense, and/or sell 15 | // copies of the Software, and to permit persons to whom the Software is 16 | // furnished to do so, subject to the following conditions: 17 | // 18 | // The above copyright notice and this permission notice shall be included in all 19 | // copies or substantial portions of the Software. 20 | // 21 | // THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR 22 | // IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, 23 | // FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE 24 | // AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER 25 | // LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, 26 | // OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE 27 | // SOFTWARE. 28 | // 29 | 30 | #import 31 | 32 | 33 | /********************************************************/ 34 | /*********************** MACROS *************************/ 35 | /********************************************************/ 36 | 37 | /* 38 | * By default, we create an alias for NSRegularExpression 39 | * called `Rx` and creates a macro `RX()` for quick regex creation. 40 | * 41 | * If you don't want these macros, add the following statement 42 | * before you include this library: 43 | * 44 | * #define DisableRegExCategoriesMacros 45 | */ 46 | 47 | 48 | /** 49 | * Creates a macro (alias) for NSRegularExpression named `Rx`. 50 | * 51 | * ie. 52 | * NSRegularExpression* rx = [[Rx alloc] initWithPattern:@"\d+" options:0 error:nil]; 53 | */ 54 | 55 | #ifndef DisableRegExCategoriesMacros 56 | #define Rx NSRegularExpression 57 | #endif 58 | 59 | 60 | /** 61 | * Creates a macro (alias) for NSRegularExpression named `Rx`. 62 | * 63 | * ie. 64 | * NSRegularExpression* rx = [[Rx alloc] initWithPattern:@"\d+" options:0 error:nil]; 65 | */ 66 | 67 | #ifndef DisableRegExCategoriesMacros 68 | #define RX(pattern) [[NSRegularExpression alloc] initWithPattern:pattern] 69 | #endif 70 | 71 | 72 | 73 | /********************************************************/ 74 | /******************* MATCH OBJECTS **********************/ 75 | /********************************************************/ 76 | 77 | /** 78 | * RxMatch represents a single match. It contains the 79 | * matched value, range, sub groups, and the original 80 | * string. 81 | */ 82 | 83 | @interface RxMatch : NSObject 84 | @property (retain) NSString* value; /* The substring that matched the expression. */ 85 | @property (assign) NSRange range; /* The range of the original string that was matched. */ 86 | @property (retain) NSArray* groups; /* Each object is an RxMatchGroup. */ 87 | @property (retain) NSString* original; /* The full original string that was matched against. */ 88 | @end 89 | 90 | 91 | @interface RxMatchGroup : NSObject 92 | @property (retain) NSString* value; 93 | @property (assign) NSRange range; 94 | @end 95 | 96 | 97 | 98 | 99 | 100 | /** 101 | * Extend NSRegularExpression. 102 | */ 103 | 104 | @interface NSRegularExpression (ObjectiveCRegexCategories) 105 | 106 | 107 | /*******************************************************/ 108 | /******************* INITIALIZATION ********************/ 109 | /*******************************************************/ 110 | 111 | /** 112 | * Initialize an Rx object from a string. 113 | * 114 | * ie. 115 | * Rx* rx = [[Rx alloc] initWithString:@"\d+"]; 116 | */ 117 | 118 | - (NSRegularExpression*) initWithPattern:(NSString*)pattern; 119 | 120 | 121 | /** 122 | * Initialize an Rx object from a string. 123 | * 124 | * ie. 125 | * Rx* rx = [Rx rx:@"\d+"]; 126 | */ 127 | 128 | + (NSRegularExpression*) rx:(NSString*)pattern; 129 | 130 | 131 | /** 132 | * Initialize an Rx object from a string. By default, NSRegularExpression 133 | * is case sensitive, but this signature allows you to change that. 134 | * 135 | * ie. 136 | * Rx* rx = [Rx rx:@"\d+" ignoreCase:YES]; 137 | */ 138 | 139 | + (NSRegularExpression*) rx:(NSString*)pattern ignoreCase:(BOOL)ignoreCase; 140 | 141 | 142 | /** 143 | * Initialize an Rx object from a string and options. 144 | * 145 | * ie. 146 | * Rx* rx = [Rx rx:@"\d+" options:NSRegularExpressionCaseInsensitive]; 147 | */ 148 | 149 | + (NSRegularExpression*) rx:(NSString*)pattern options:(NSRegularExpressionOptions)options; 150 | 151 | 152 | /*******************************************************/ 153 | /********************** IS MATCH ***********************/ 154 | /*******************************************************/ 155 | 156 | /** 157 | * Returns true if the string matches the regex. May also 158 | * be called on NSString as [@"\d" isMatch:rx]. 159 | * 160 | * ie. 161 | * Rx* rx = RX(@"\d+"); 162 | * BOOL isMatch = [rx isMatch:@"Dog #1"]; // => true 163 | */ 164 | 165 | - (BOOL) isMatch:(NSString*)matchee; 166 | 167 | 168 | /** 169 | * Returns the index of the first match of the passed string. 170 | * 171 | * ie. 172 | * int i = [RX(@"\d+") indexOf:@"Buy 1 dog or buy 2?"]; // => 4 173 | */ 174 | 175 | - (int) indexOf:(NSString*)str; 176 | 177 | 178 | /** 179 | * Splits a string using the regex to identify delimeters. Returns 180 | * an NSArray of NSStrings. 181 | * 182 | * ie. 183 | * NSArray* pieces = [RX(@"[ ,]") split:@"A dog,cat"]; 184 | * => @[@"A", @"dog", @"cat"] 185 | */ 186 | 187 | - (NSArray*) split:(NSString*)str; 188 | 189 | 190 | /** 191 | * Replaces all occurances in a string with a replacement string. 192 | * 193 | * ie. 194 | * NSString* result = [RX(@"ruf+") replace:@"ruf ruff!" with:@"meow"]; 195 | * => @"meow meow!" 196 | */ 197 | 198 | - (NSString*) replace:(NSString*)string with:(NSString*)replacement; 199 | 200 | 201 | /** 202 | * Replaces all occurances of a regex using a block. The block receives the match 203 | * and should return the replacement. 204 | * 205 | * ie. 206 | * NSString* result = [RX(@"[A-Z]+") replace:@"i love COW" withBlock:^(NSString*){ return @"lamp"; }]; 207 | * => @"i love lamp" 208 | */ 209 | 210 | - (NSString*) replace:(NSString*)string withBlock:(NSString*(^)(NSString* match))replacer; 211 | 212 | 213 | /** 214 | * Replaces all occurances of a regex using a block. The block receives a RxMatch object 215 | * that contains all the details of the match and should return a string 216 | * which is what the match is replaced with. 217 | * 218 | * ie. 219 | * NSString* result = [RX(@"\\w+") replace:@"hi bud" withDetailsBlock:^(RxMatch* match){ return [NSString stringWithFormat:@"%i", match.value.length]; }]; 220 | * => @"2 3" 221 | */ 222 | 223 | - (NSString*) replace:(NSString *)string withDetailsBlock:(NSString*(^)(RxMatch* match))replacer; 224 | 225 | 226 | /** 227 | * Returns an array of matched root strings with no other match information. 228 | * 229 | * ie. 230 | * NSString* str = @"My email is me@example.com and yours is you@example.com"; 231 | * NSArray* matches = [RX(@"\\w+[@]\\w+[.](\\w+)") matches:str]; 232 | * => @[ @"me@example.com", @"you@example.com" ] 233 | */ 234 | 235 | - (NSArray*) matches:(NSString*)str; 236 | 237 | 238 | /** 239 | * Returns a string which is the first match of the NSRegularExpression. 240 | * 241 | * ie. 242 | * NSString* str = @"My email is me@example.com and yours is you@example.com"; 243 | * NSString* match = [RX(@"\\w+[@]\\w+[.](\\w+)") firstMatch:str]; 244 | * => @"me@example.com" 245 | */ 246 | 247 | - (NSString*) firstMatch:(NSString*)str; 248 | 249 | 250 | /** 251 | * Returns an NSArray of RxMatch* objects. Each match contains the matched 252 | * value, range, groups, etc. 253 | * 254 | * ie. 255 | * NSString* str = @"My email is me@example.com and yours is you@example.com"; 256 | * NSArray* matches = [str matchesWithDetails:RX(@"\\w+[@]\\w+[.](\\w+)")]; 257 | */ 258 | 259 | - (NSArray*) matchesWithDetails:(NSString*)str; 260 | 261 | 262 | /** 263 | * Returns the first match as an RxMatch* object. 264 | * 265 | * ie. 266 | * NSString* str = @"My email is me@example.com and yours is you@example.com"; 267 | * Rx* rx = RX(@"\\w+[@]\\w+[.](\\w+)"); 268 | * RxMatch* match = [rx firstMatchWithDetails:str]; 269 | */ 270 | 271 | - (RxMatch*) firstMatchWithDetails:(NSString*)str; 272 | 273 | @end 274 | 275 | 276 | 277 | /** 278 | * A category on NSString to make it easy to use 279 | * Rx in simple operations. 280 | */ 281 | 282 | @interface NSString (ObjectiveCRegexCategories) 283 | 284 | 285 | /** 286 | * Initialize an NSRegularExpression object from a string. 287 | * 288 | * ie. 289 | * NSRegularExpression* rx = [@"\d+" toRx]; 290 | */ 291 | 292 | - (NSRegularExpression*) toRx; 293 | 294 | 295 | /** 296 | * Initialize an NSRegularExpression object from a string with 297 | * a flag denoting case-sensitivity. By default, NSRegularExpression 298 | * is case sensitive. 299 | * 300 | * ie. 301 | * NSRegularExpression* rx = [@"\d+" toRxIgnoreCase:YES]; 302 | */ 303 | 304 | - (NSRegularExpression*) toRxIgnoreCase:(BOOL)ignoreCase; 305 | 306 | 307 | /** 308 | * Initialize an NSRegularExpression object from a string with options. 309 | * 310 | * ie. 311 | * NSRegularExpression* rx = [@"\d+" toRxWithOptions:NSRegularExpressionCaseInsensitive]; 312 | */ 313 | 314 | - (NSRegularExpression*) toRxWithOptions:(NSRegularExpressionOptions)options; 315 | 316 | 317 | /** 318 | * Returns true if the string matches the regex. May also 319 | * be called as on Rx as [rx isMatch:@"some string"]. 320 | * 321 | * ie. 322 | * BOOL isMatch = [@"Dog #1" isMatch:RX(@"\d+")]; // => true 323 | */ 324 | 325 | - (BOOL) isMatch:(NSRegularExpression*)rx; 326 | 327 | 328 | /** 329 | * Returns the index of the first match according to 330 | * the regex passed in. 331 | * 332 | * ie. 333 | * int i = [@"Buy 1 dog or buy 2?" indexOf:RX(@"\d+")]; // => 4 334 | */ 335 | 336 | - (int) indexOf:(NSRegularExpression*)rx; 337 | 338 | 339 | /** 340 | * Splits a string using the regex to identify delimeters. Returns 341 | * an NSArray of NSStrings. 342 | * 343 | * ie. 344 | * NSArray* pieces = [@"A dog,cat" split:RX(@"[ ,]")]; 345 | * => @[@"A", @"dog", @"cat"] 346 | */ 347 | 348 | - (NSArray*) split:(NSRegularExpression*)rx; 349 | 350 | 351 | /** 352 | * Replaces all occurances of a regex with a replacement string. 353 | * 354 | * ie. 355 | * NSString* result = [@"ruf ruff!" replace:RX(@"ruf+") with:@"meow"]; 356 | * => @"meow meow!" 357 | */ 358 | 359 | - (NSString*) replace:(NSRegularExpression*)rx with:(NSString*)replacement; 360 | 361 | 362 | /** 363 | * Replaces all occurances of a regex using a block. The block receives the match 364 | * and should return the replacement. 365 | * 366 | * ie. 367 | * NSString* result = [@"i love COW" replace:RX(@"[A-Z]+") withBlock:^(NSString*){ return @"lamp"; }]; 368 | * => @"i love lamp" 369 | */ 370 | 371 | - (NSString*) replace:(NSRegularExpression *)rx withBlock:(NSString*(^)(NSString* match))replacer; 372 | 373 | 374 | /** 375 | * Replaces all occurances of a regex using a block. The block receives an RxMatch 376 | * object which contains all of the details for each match and should return a string 377 | * which is what the match is replaced with. 378 | * 379 | * ie. 380 | * NSString* result = [@"hi bud" replace:RX(@"\\w+") withDetailsBlock:^(RxMatch* match){ return [NSString stringWithFormat:@"%i", match.value.length]; }]; 381 | * => @"2 3" 382 | */ 383 | 384 | - (NSString*) replace:(NSRegularExpression *)rx withDetailsBlock:(NSString*(^)(RxMatch* match))replacer; 385 | 386 | 387 | /** 388 | * Returns an array of matched root strings with no other match information. 389 | * 390 | * ie. 391 | * NSString* str = @"My email is me@example.com and yours is you@example.com"; 392 | * NSArray* matches = [str matches:RX(@"\\w+[@]\\w+[.](\\w+)")]; 393 | * => @[ @"me@example.com", @"you@example.com" ] 394 | */ 395 | 396 | - (NSArray*) matches:(NSRegularExpression*)rx; 397 | 398 | 399 | /** 400 | * Returns a string which is the first match of the NSRegularExpression. 401 | * 402 | * ie. 403 | * NSString* str = @"My email is me@example.com and yours is you@example.com"; 404 | * NSString* match = [str firstMatch:RX(@"\\w+[@]\\w+[.](\\w+)")]; 405 | * => @"me@example.com" 406 | */ 407 | 408 | - (NSString*) firstMatch:(NSRegularExpression*)rx; 409 | 410 | 411 | /** 412 | * Returns an NSArray of RxMatch* objects. Each match contains the matched 413 | * value, range, groups, etc. 414 | * 415 | * ie. 416 | * NSString* str = @"My email is me@example.com and yours is you@example.com"; 417 | * NSArray* matches = [str matchesWithDetails:RX(@"\\w+[@]\\w+[.](\\w+)")]; 418 | */ 419 | 420 | - (NSArray*) matchesWithDetails:(NSRegularExpression*)rx; 421 | 422 | 423 | /** 424 | * Returns an the first match as an RxMatch* object. 425 | * 426 | * ie. 427 | * NSString* str = @"My email is me@example.com and yours is you@example.com"; 428 | * RxMatch* match = [str firstMatchWithDetails:RX(@"\\w+[@]\\w+[.](\\w+)")]; 429 | */ 430 | 431 | - (RxMatch*) firstMatchWithDetails:(NSRegularExpression*)rx; 432 | 433 | @end 434 | 435 | -------------------------------------------------------------------------------- /Pods/GBCli/GBCli/src/GBCommandLineParser.m: -------------------------------------------------------------------------------- 1 | // 2 | // GBCommandLineParser.m 3 | // GBCli 4 | // 5 | // Created by Tomaž Kragelj on 3/12/12. 6 | // Copyright (c) 2012 Tomaz Kragelj. All rights reserved. 7 | // 8 | 9 | #import "GBPrint.h" 10 | #import "GBSettings.h" 11 | #import "GBCommandLineParser.h" 12 | 13 | static NSString * const GBCommandLineLongOptionKey = @"long"; 14 | static NSString * const GBCommandLineShortOptionKey = @"short"; 15 | static NSString * const GBCommandLineRequirementKey = @"requirement"; 16 | static NSString * const GBCommandLineOptionGroupKey = @"group"; // this is returned while parsing to indicate an option group was detected. 17 | static NSString * const GBCommandLineNotAnOptionKey = @"not-an-option"; // this is returned while parsing to indicate an argument was detected. 18 | 19 | #pragma mark - 20 | 21 | @interface GBCommandLineParser () 22 | - (NSDictionary *)optionDataForOption:(NSString *)shortOrLongName value:(NSString **)value; 23 | - (BOOL)isShortOrLongOptionName:(NSString *)value; 24 | @property (nonatomic, strong) GBSettings *settings; // optional (only required by simplified parsing methods) 25 | @property (nonatomic, strong) NSMutableDictionary *parsedOptions; 26 | @property (nonatomic, strong) NSMutableArray *parsedArguments; 27 | @property (nonatomic, strong) NSMutableDictionary *registeredOptionsByLongNames; 28 | @property (nonatomic, strong) NSMutableDictionary *registeredOptionsByShortNames; 29 | @property (nonatomic, strong) NSMutableDictionary *registeredOptionGroupsByNames; 30 | @property (nonatomic, strong) NSMutableSet *currentOptionsGroupOptions; // this is used both while registering and while parsing arguments 31 | @property (nonatomic, copy) NSString *currentOptionsGroupName; // used while parsing 32 | @end 33 | 34 | #pragma mark - 35 | 36 | @implementation GBCommandLineParser 37 | 38 | @synthesize parsedOptions; 39 | @synthesize parsedArguments; 40 | @synthesize registeredOptionsByLongNames; 41 | @synthesize registeredOptionsByShortNames; 42 | 43 | #pragma mark - Initialization & disposal 44 | 45 | - (instancetype)init { 46 | self = [super init]; 47 | if (self) { 48 | self.registeredOptionsByLongNames = [NSMutableDictionary dictionary]; 49 | self.registeredOptionsByShortNames = [NSMutableDictionary dictionary]; 50 | self.registeredOptionGroupsByNames = [NSMutableDictionary dictionary]; 51 | self.parsedOptions = [NSMutableDictionary dictionary]; 52 | self.parsedArguments = [NSMutableArray array]; 53 | } 54 | return self; 55 | } 56 | 57 | #pragma mark - Options registration 58 | 59 | - (void)beginRegisterOptionGroup:(NSString *)name { 60 | self.currentOptionsGroupOptions = self.registeredOptionGroupsByNames[name]; 61 | 62 | // Warn if we already have the given group. 63 | if (self.registeredOptionGroupsByNames[name]) { 64 | fprintf(stderr, "Group %s is already registered!", [name UTF8String]); 65 | return; 66 | } 67 | 68 | // Create options group data into which we'll be registering options from now on. 69 | self.currentOptionsGroupOptions = [NSMutableSet set]; 70 | self.registeredOptionGroupsByNames[name] = self.currentOptionsGroupOptions; 71 | } 72 | 73 | - (void)endRegisterOptionGroup { 74 | self.currentOptionsGroupName = nil; 75 | self.currentOptionsGroupOptions = nil; 76 | } 77 | 78 | - (void)registerOption:(NSString *)longOption shortcut:(char)shortOption requirement:(GBValueRequirements)requirement { 79 | // Register option data. 80 | NSMutableDictionary *data = [NSMutableDictionary dictionary]; 81 | data[GBCommandLineLongOptionKey] = longOption; 82 | data[GBCommandLineRequirementKey] = @(requirement); 83 | self.registeredOptionsByLongNames[longOption] = data; 84 | [self.currentOptionsGroupOptions addObject:longOption]; 85 | 86 | // Register short option data if needed. 87 | if (shortOption > 0) { 88 | NSString *shortOptionKey = [NSString stringWithFormat:@"%c", shortOption]; 89 | data[GBCommandLineShortOptionKey] = @(shortOption); 90 | self.registeredOptionsByShortNames[shortOptionKey] = data; 91 | } 92 | 93 | // If this is a swich, register negative variant (i.e. if the option is named --option, negative form is --no-option). Note that negative variant doesn't support short code! 94 | if (requirement == GBValueNone) { 95 | NSMutableDictionary *negativeVariantData = [NSMutableDictionary dictionary]; 96 | NSString *negativeVariantLongOption = [NSString stringWithFormat:@"no-%@", longOption]; 97 | negativeVariantData[GBCommandLineLongOptionKey] = negativeVariantLongOption; 98 | negativeVariantData[GBCommandLineRequirementKey] = @(requirement); 99 | self.registeredOptionsByLongNames[negativeVariantLongOption] = negativeVariantData; 100 | [self.currentOptionsGroupOptions addObject:negativeVariantData]; 101 | } 102 | } 103 | 104 | - (void)registerOption:(NSString *)longOption requirement:(GBValueRequirements)requirement { 105 | [self registerOption:longOption shortcut:0 requirement:requirement]; 106 | } 107 | 108 | - (void)registerSwitch:(NSString *)longOption shortcut:(char)shortOption { 109 | [self registerOption:longOption shortcut:shortOption requirement:GBValueNone]; 110 | } 111 | 112 | - (void)registerSwitch:(NSString *)longOption { 113 | [self registerSwitch:longOption shortcut:0]; 114 | } 115 | 116 | #pragma mark - Options parsing - Simple methods with default behavior 117 | 118 | - (void)registerSettings:(GBSettings *)settings { 119 | self.settings = settings; 120 | } 121 | 122 | - (BOOL)parseOptionsUsingDefaultArguments { 123 | [self validateSimplifiedOptionsWithSelector:_cmd]; 124 | return [self parseOptionsUsingDefaultArgumentsWithBlock:[self simplifiedOptionsParserBlock]]; 125 | } 126 | 127 | - (BOOL)parseOptionsWithArguments:(char **)argv count:(int)argc { 128 | [self validateSimplifiedOptionsWithSelector:_cmd]; 129 | return [self parseOptionsWithArguments:argv count:argc block:[self simplifiedOptionsParserBlock]]; 130 | } 131 | 132 | - (BOOL)parseOptionsWithArguments:(NSArray *)arguments commandLine:(NSString *)cmd { 133 | [self validateSimplifiedOptionsWithSelector:_cmd]; 134 | return [self parseOptionsWithArguments:arguments commandLine:cmd block:[self simplifiedOptionsParserBlock]]; 135 | } 136 | 137 | - (GBCommandLineParseBlock)simplifiedOptionsParserBlock { 138 | return ^(GBParseFlags flags, NSString *argument, id value, BOOL *stop) { 139 | switch (flags) { 140 | case GBParseFlagUnknownOption: 141 | gbfprintln(stderr, @"Unknown command line option %@, try --help!", argument); 142 | break; 143 | case GBParseFlagMissingValue: 144 | gbfprintln(stderr, @"Missing value for command line option %s, try --help!", argument); 145 | break; 146 | case GBParseFlagWrongGroup: 147 | gbfprintln(stderr, @"Invalid option %@ for group %@!", argument, self.currentOptionsGroupName); 148 | break; 149 | case GBParseFlagArgument: 150 | [self.settings addArgument:value]; 151 | break; 152 | case GBParseFlagOption: 153 | [self.settings setObject:value forKey:argument]; 154 | break; 155 | } 156 | }; 157 | } 158 | 159 | - (void)validateSimplifiedOptionsWithSelector:(SEL)sel { 160 | NSAssert(self.settings != nil, @"%@ requires you to supply GBSettings instance via registerSettings: method!", NSStringFromSelector(sel)); 161 | } 162 | 163 | #pragma mark - Options parsing - Methods with customizations 164 | 165 | - (BOOL)parseOptionsUsingDefaultArgumentsWithBlock:(GBCommandLineParseBlock)handler { 166 | NSProcessInfo *processInfo = [NSProcessInfo processInfo]; 167 | NSString *command = [processInfo processName]; 168 | NSMutableArray *arguments = [[processInfo arguments] mutableCopy]; 169 | [arguments removeObjectAtIndex:0]; 170 | return [self parseOptionsWithArguments:arguments commandLine:command block:handler]; 171 | } 172 | 173 | - (BOOL)parseOptionsWithArguments:(char **)argv count:(int)argc block:(GBCommandLineParseBlock)handler { 174 | if (argc == 0) return YES; 175 | NSString *command = [NSString stringWithUTF8String:argv[0]]; 176 | NSMutableArray *arguments = [NSMutableArray arrayWithCapacity:argc - 1]; 177 | for (int i=1; ilongOption || definition->description) { 52 | [self registerOption:definition->shortOption long:definition->longOption description:definition->description flags:definition->flags]; 53 | definition++; 54 | } 55 | } 56 | 57 | - (void)registerSeparator:(NSString *)description { 58 | [self registerOption:0 long:nil description:description flags:GBOptionSeparator]; 59 | } 60 | 61 | - (void)registerGroup:(NSString *)name description:(NSString *)description optionsBlock:(void(^)(GBOptionsHelper *options))block { 62 | [self registerGroup:name description:description flags:0 optionsBlock:block]; 63 | } 64 | 65 | - (void)registerGroup:(NSString *)name description:(NSString *)description flags:(GBOptionFlags)flags optionsBlock:(void(^)(GBOptionsHelper *options))block { 66 | NSParameterAssert(block != nil); 67 | OptionDefinition *definition = [[OptionDefinition alloc] init]; 68 | definition.shortOption = 0; 69 | definition.longOption = name; 70 | definition.description = description; 71 | definition.flags = GBOptionGroup | flags; 72 | [self.registeredOptions addObject:definition]; 73 | 74 | block(self); 75 | 76 | OptionDefinition *endDefinition = [[OptionDefinition alloc] init]; 77 | endDefinition.flags = GBOptionInternalEndGroup; 78 | [self.registeredOptions addObject:endDefinition]; 79 | } 80 | 81 | - (void)registerOption:(char)shortName long:(NSString *)longName description:(NSString *)description flags:(GBOptionFlags)flags { 82 | OptionDefinition *definition = [[OptionDefinition alloc] init]; 83 | definition.shortOption = shortName; 84 | definition.longOption = longName; 85 | definition.description = description; 86 | definition.flags = flags; 87 | [self.registeredOptions addObject:definition]; 88 | } 89 | 90 | #pragma mark - Integration with other components 91 | 92 | - (void)registerOptionsToCommandLineParser:(GBCommandLineParser *)parser { 93 | [self enumerateOptions:^(OptionDefinition *definition, BOOL *stop) { 94 | if (![self isCmdLine:definition]) return; 95 | if ([self isSeparator:definition]) return; 96 | 97 | if ([self isOptionGroup:definition]) { 98 | [parser beginRegisterOptionGroup:definition.longOption]; 99 | return; 100 | } 101 | 102 | if ([self isOptionGroupEnd:definition]) { 103 | [parser endRegisterOptionGroup]; 104 | return; 105 | } 106 | 107 | NSUInteger requirements = [self requirements:definition]; 108 | [parser registerOption:definition.longOption shortcut:definition.shortOption requirement:requirements]; 109 | }]; 110 | } 111 | 112 | #pragma mark - Diagnostic info 113 | 114 | - (void)printValuesFromSettings:(GBSettings *)settings { 115 | #define GB_UPDATE_MAX_LENGTH(value) \ 116 | NSNumber *length = [lengths objectAtIndex:columns.count]; \ 117 | NSUInteger maxLength = MAX(value.length, length.unsignedIntegerValue); \ 118 | if (maxLength > length.unsignedIntegerValue) { \ 119 | NSNumber *newMaxLength = @(maxLength); \ 120 | [lengths replaceObjectAtIndex:columns.count withObject:newMaxLength]; \ 121 | } 122 | NSMutableArray *rows = [NSMutableArray array]; 123 | NSMutableArray *lengths = [NSMutableArray array]; 124 | __weak GBOptionsHelper *blockSelf = self; 125 | __block NSUInteger settingsHierarchyLevels = 0; 126 | 127 | // First add header row. Note that first element is the setting. 128 | NSMutableArray *headers = [NSMutableArray arrayWithObject:@"Option"]; 129 | [lengths addObject:@([headers.lastObject length])]; 130 | [settings enumerateSettings:^(GBSettings *settings, BOOL *stop) { 131 | [headers addObject:settings.name]; 132 | [lengths addObject:@(settings.name.length)]; 133 | settingsHierarchyLevels++; 134 | }]; 135 | [rows addObject:headers]; 136 | 137 | // Append all rows for options. 138 | __block NSUInteger lastSeparatorIndex = 0; 139 | [self enumerateOptions:^(OptionDefinition *definition, BOOL *stop) { 140 | if (![blockSelf isPrint:definition]) return; 141 | if ([self isOptionGroupEnd:definition]) return; 142 | 143 | // Add separator. Note that we don't care about its length, we'll simply draw it over the whole line if needed. 144 | if ([blockSelf isSeparator:definition]) { 145 | if (rows.count == lastSeparatorIndex) { 146 | [rows removeLastObject]; 147 | [rows removeLastObject]; 148 | } 149 | [rows addObject:@[]]; 150 | [rows addObject:@[ definition.description ] ]; 151 | lastSeparatorIndex = rows.count; 152 | return; 153 | } 154 | 155 | // Add group. 156 | if ([blockSelf isOptionGroup:definition]) { 157 | NSMutableString *description = [definition.longOption mutableCopy]; 158 | if ([definition.description length] > 0) [description appendFormat:@" %@", definition.description]; 159 | [rows addObject:@[]]; 160 | [rows addObject:@[ description ]]; 161 | return; 162 | } 163 | 164 | NSMutableArray *columns = [NSMutableArray array]; 165 | NSString *longOption = definition.longOption; 166 | GB_UPDATE_MAX_LENGTH(longOption) 167 | [columns addObject:longOption]; 168 | 169 | // Now append value for the option on each settings level and update maximum size. 170 | [settings enumerateSettings:^(GBSettings *settings, BOOL *stop) { 171 | NSString *columnData = @""; 172 | if ([settings isKeyPresentAtThisLevel:longOption]) { 173 | id value = [settings objectForKey:longOption]; 174 | if ([settings isKeyArray:longOption]) { 175 | NSMutableString *arrayValue = [NSMutableString string]; 176 | [(NSArray *)value enumerateObjectsUsingBlock:^(NSString *obj, NSUInteger idx, BOOL *stop) { 177 | GBSettings *level = [settings settingsForArrayValue:obj key:longOption]; 178 | if (level != settings) return; 179 | if (arrayValue.length > 0) [arrayValue appendString:@", "]; 180 | [arrayValue appendString:obj]; 181 | }]; 182 | columnData = arrayValue; 183 | } else { 184 | columnData = [value description]; 185 | } 186 | } 187 | GB_UPDATE_MAX_LENGTH(columnData) 188 | [columns addObject:columnData]; 189 | }]; 190 | 191 | // Add the row. 192 | [rows addObject:columns]; 193 | }]; 194 | 195 | // Remove last separator if there were no values. 196 | if (rows.count == lastSeparatorIndex) { 197 | [rows removeLastObject]; 198 | [rows removeLastObject]; 199 | } 200 | 201 | // Render header. 202 | [self replacePlaceholdersAndPrintStringFromBlock:self.printValuesHeader]; 203 | 204 | // Render all arguments if any. 205 | if (settings.arguments.count > 0) { 206 | [self replacePlaceholdersAndPrintStringFromBlock:self.printValuesArgumentsHeader]; 207 | [settings.arguments enumerateObjectsUsingBlock:^(NSString *argument, NSUInteger idx, BOOL *stop) { 208 | printf("- %s", argument.UTF8String); 209 | if (settingsHierarchyLevels > 1) { 210 | GBSettings *level = [settings settingsForArgument:argument]; 211 | printf(" (%s)", level.name.UTF8String); 212 | } 213 | printf("\n"); 214 | }]; 215 | printf("\n"); 216 | } 217 | 218 | // Render all rows. 219 | [self replacePlaceholdersAndPrintStringFromBlock:self.printValuesOptionsHeader]; 220 | [rows enumerateObjectsUsingBlock:^(NSArray *columns, NSUInteger rowIdx, BOOL *stopRow) { 221 | NSMutableString *output = [NSMutableString string]; 222 | [columns enumerateObjectsUsingBlock:^(NSString *value, NSUInteger colIdx, BOOL *stopCol) { 223 | NSUInteger columnSize = [[lengths objectAtIndex:colIdx] unsignedIntegerValue]; 224 | NSUInteger valueSize = value.length; 225 | [output appendString:value]; 226 | while (valueSize <= columnSize) { 227 | [output appendString:@" "]; 228 | valueSize++; 229 | } 230 | }]; 231 | printf("%s\n", output.UTF8String); 232 | }]; 233 | 234 | // Render footer. 235 | [self replacePlaceholdersAndPrintStringFromBlock:self.printValuesFooter]; 236 | } 237 | 238 | - (void)printVersion { 239 | NSMutableString *output = [NSMutableString stringWithFormat:@"%@", self.applicationNameFromBlockOrDefault]; 240 | NSString *version = self.applicationVersionFromBlockOrNil; 241 | NSString *build = self.applicationBuildFromBlockOrNil; 242 | if (version) [output appendFormat:@": version %@", version]; 243 | if (build) [output appendFormat:@" (build %@)", build]; 244 | printf("%s\n", output.UTF8String); 245 | } 246 | 247 | - (void)printHelp { 248 | // Prepare all rows. 249 | __block NSUInteger maxNameTypeLength = 0; 250 | __block NSUInteger lastSeparatorIndex = NSNotFound; 251 | NSMutableArray *rows = [NSMutableArray array]; 252 | [self enumerateOptions:^(OptionDefinition *definition, BOOL *stop) { 253 | if (![self isHelp:definition]) return; 254 | if ([self isOptionGroupEnd:definition]) return; 255 | 256 | // Prepare separator. Remove previous one if there were no values prepared for it. 257 | if ([self isSeparator:definition]) { 258 | if (rows.count == lastSeparatorIndex) { 259 | [rows removeLastObject]; 260 | [rows removeLastObject]; 261 | } 262 | [rows addObject:@[]]; 263 | [rows addObject:@[ definition.description ]]; 264 | lastSeparatorIndex = rows.count; 265 | return; 266 | } 267 | 268 | // Add group. 269 | if ([self isOptionGroup:definition]) { 270 | NSMutableString *description = [definition.longOption mutableCopy]; 271 | if ([definition.description length] > 0) [description appendFormat:@" %@", definition.description]; 272 | [rows addObject:@[]]; 273 | [rows addObject:@[ description ]]; 274 | return; 275 | } 276 | 277 | // Prepare option description. 278 | NSString *shortOption = (definition.shortOption > 0) ? [NSString stringWithFormat:@"-%c", definition.shortOption] : @" "; 279 | NSString *longOption = [NSString stringWithFormat:@"--%@", definition.longOption]; 280 | NSString *description = definition.description ? definition.description : @""; 281 | NSUInteger requirements = [self requirements:definition]; 282 | 283 | // Prepare option type and update longest option+type string size for better alignment later on. 284 | NSString *type = @""; 285 | if (requirements == GBValueRequired) 286 | type = @" "; 287 | else if (requirements == GBValueOptional) 288 | type = @" []"; 289 | maxNameTypeLength = MAX(longOption.length + type.length, maxNameTypeLength); 290 | NSString *nameAndType = [NSString stringWithFormat:@"%@%@", longOption, type]; 291 | 292 | // Add option info to rows array. 293 | NSMutableArray *columns = [NSMutableArray array]; 294 | [columns addObject:shortOption]; 295 | [columns addObject:nameAndType]; 296 | [columns addObject:description]; 297 | [rows addObject:columns]; 298 | }]; 299 | 300 | // Remove last separator if there were no values. 301 | if (rows.count == lastSeparatorIndex) { 302 | [rows removeLastObject]; 303 | [rows removeLastObject]; 304 | } 305 | 306 | // Render header. 307 | [self replacePlaceholdersAndPrintStringFromBlock:self.printHelpHeader]; 308 | 309 | // Render all rows aligning long option columns properly. 310 | [rows enumerateObjectsUsingBlock:^(NSArray *columns, NSUInteger rowIdx, BOOL *stop) { 311 | NSMutableString *output = [NSMutableString string]; 312 | [columns enumerateObjectsUsingBlock:^(NSString *column, NSUInteger colIdx, BOOL *stop) { 313 | [output appendFormat:@"%@ ", column]; 314 | if (colIdx == 1) { 315 | NSUInteger length = column.length; 316 | while (length < maxNameTypeLength) { 317 | [output appendString:@" "]; 318 | length++; 319 | } 320 | } 321 | }]; 322 | printf("%s\n", output.UTF8String); 323 | }]; 324 | 325 | // Render footer. 326 | [self replacePlaceholdersAndPrintStringFromBlock:self.printHelpFooter]; 327 | } 328 | 329 | #pragma mark - Application information 330 | 331 | - (NSString *)applicationNameFromBlockOrDefault { 332 | if (self.applicationName) return self.applicationName(); 333 | NSProcessInfo *process = [NSProcessInfo processInfo]; 334 | return process.processName; 335 | } 336 | 337 | - (NSString *)applicationVersionFromBlockOrNil { 338 | if (self.applicationVersion) return self.applicationVersion(); 339 | return nil; 340 | } 341 | 342 | - (NSString *)applicationBuildFromBlockOrNil { 343 | if (self.applicationBuild) return self.applicationBuild(); 344 | return nil; 345 | } 346 | 347 | #pragma mark - Rendering helpers 348 | 349 | - (void)replacePlaceholdersAndPrintStringFromBlock:(GBOptionStringBlock)block { 350 | if (!block) { 351 | printf("\n"); 352 | return; 353 | } 354 | NSString *string = block(); 355 | if (self.applicationBuildFromBlockOrNil) 356 | string = [string stringByReplacingOccurrencesOfString:@"%APPNAME" withString:self.applicationNameFromBlockOrDefault]; 357 | if (self.applicationVersionFromBlockOrNil) 358 | string = [string stringByReplacingOccurrencesOfString:@"%APPVERSION" withString:self.applicationVersionFromBlockOrNil]; 359 | if (self.applicationBuildFromBlockOrNil) 360 | string = [string stringByReplacingOccurrencesOfString:@"%APPBUILD" withString:self.applicationBuildFromBlockOrNil]; 361 | printf("%s\n", string.UTF8String); 362 | } 363 | 364 | #pragma mark - Helper methods 365 | 366 | - (void)enumerateOptions:(void(^)(OptionDefinition *definition, BOOL *stop))handler { 367 | [self.registeredOptions enumerateObjectsUsingBlock:^(OptionDefinition *definition, NSUInteger idx, BOOL *stop) { 368 | handler(definition, stop); 369 | }]; 370 | } 371 | 372 | - (NSUInteger)requirements:(OptionDefinition *)definition { 373 | return (definition.flags & 0b11); 374 | } 375 | 376 | - (BOOL)isSeparator:(OptionDefinition *)definition { 377 | return ((definition.flags & GBOptionSeparator) > 0); 378 | } 379 | 380 | - (BOOL)isOptionGroup:(OptionDefinition *)definition { 381 | return ((definition.flags & GBOptionGroup) > 0); 382 | } 383 | 384 | - (BOOL)isOptionGroupEnd:(OptionDefinition *)definition { 385 | return ((definition.flags & GBOptionInternalEndGroup) > 0); 386 | } 387 | 388 | - (BOOL)isCmdLine:(OptionDefinition *)definition { 389 | return ((definition.flags & GBOptionNoCmdLine) == 0); 390 | } 391 | 392 | - (BOOL)isPrint:(OptionDefinition *)definition { 393 | return ((definition.flags & GBOptionNoPrint) == 0); 394 | } 395 | 396 | - (BOOL)isHelp:(OptionDefinition *)definition { 397 | return ((definition.flags & GBOptionNoHelp) == 0); 398 | } 399 | 400 | @end 401 | 402 | #pragma mark - 403 | 404 | @implementation GBCommandLineParser (GBOptionsHelper) 405 | 406 | - (void)registerOptions:(GBOptionsHelper *)options { 407 | [options registerOptionsToCommandLineParser:self]; 408 | } 409 | 410 | @end 411 | -------------------------------------------------------------------------------- /symbolicator.xcodeproj/project.pbxproj: -------------------------------------------------------------------------------- 1 | // !$*UTF8*$! 2 | { 3 | archiveVersion = 1; 4 | classes = { 5 | }; 6 | objectVersion = 46; 7 | objects = { 8 | 9 | /* Begin PBXBuildFile section */ 10 | 73261503194577F500DC069E /* main.swift in Sources */ = {isa = PBXBuildFile; fileRef = 73261502194577F500DC069E /* main.swift */; }; 11 | 7326150E194579F900DC069E /* Settings.swift in Sources */ = {isa = PBXBuildFile; fileRef = 7326150D194579F900DC069E /* Settings.swift */; }; 12 | 7326151019457E5B00DC069E /* Symbolicator.swift in Sources */ = {isa = PBXBuildFile; fileRef = 7326150F19457E5B00DC069E /* Symbolicator.swift */; }; 13 | 738B72F61947459400451933 /* RegExCategories.m in Sources */ = {isa = PBXBuildFile; fileRef = 738B72F51947459400451933 /* RegExCategories.m */; }; 14 | 738B72F819474C7D00451933 /* FileSymbolicator.swift in Sources */ = {isa = PBXBuildFile; fileRef = 738B72F719474C7D00451933 /* FileSymbolicator.swift */; }; 15 | 738B72FA194751F700451933 /* ArchiveHandler.swift in Sources */ = {isa = PBXBuildFile; fileRef = 738B72F9194751F700451933 /* ArchiveHandler.swift */; }; 16 | 7396BA251FD1675100714776 /* ApplicationVersion-AutoGenerated!.swift in Sources */ = {isa = PBXBuildFile; fileRef = 7396BA241FD1675100714776 /* ApplicationVersion-AutoGenerated!.swift */; }; 17 | 73C00D33194902A000D3C145 /* Options.swift in Sources */ = {isa = PBXBuildFile; fileRef = 73C00D32194902A000D3C145 /* Options.swift */; }; 18 | 9907F136D7584532AC171FC6 /* libPods.a in Frameworks */ = {isa = PBXBuildFile; fileRef = 060CC723AC96477D8741508C /* libPods.a */; }; 19 | /* End PBXBuildFile section */ 20 | 21 | /* Begin PBXCopyFilesBuildPhase section */ 22 | 732614FD194577F500DC069E /* CopyFiles */ = { 23 | isa = PBXCopyFilesBuildPhase; 24 | buildActionMask = 2147483647; 25 | dstPath = /usr/share/man/man1/; 26 | dstSubfolderSpec = 0; 27 | files = ( 28 | ); 29 | runOnlyForDeploymentPostprocessing = 1; 30 | }; 31 | /* End PBXCopyFilesBuildPhase section */ 32 | 33 | /* Begin PBXFileReference section */ 34 | 060CC723AC96477D8741508C /* libPods.a */ = {isa = PBXFileReference; explicitFileType = archive.ar; includeInIndex = 0; path = libPods.a; sourceTree = BUILT_PRODUCTS_DIR; }; 35 | 732614FF194577F500DC069E /* symbolicator */ = {isa = PBXFileReference; explicitFileType = "compiled.mach-o.executable"; includeInIndex = 0; path = symbolicator; sourceTree = BUILT_PRODUCTS_DIR; }; 36 | 73261502194577F500DC069E /* main.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = main.swift; sourceTree = ""; }; 37 | 732615091945789900DC069E /* symbolicator-Bridging-Header.h */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.c.h; path = "symbolicator-Bridging-Header.h"; sourceTree = ""; }; 38 | 7326150D194579F900DC069E /* Settings.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = Settings.swift; sourceTree = ""; }; 39 | 7326150F19457E5B00DC069E /* Symbolicator.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = Symbolicator.swift; sourceTree = ""; }; 40 | 738B72F41947459400451933 /* RegExCategories.h */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; path = RegExCategories.h; sourceTree = ""; }; 41 | 738B72F51947459400451933 /* RegExCategories.m */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.objc; path = RegExCategories.m; sourceTree = ""; }; 42 | 738B72F719474C7D00451933 /* FileSymbolicator.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = FileSymbolicator.swift; sourceTree = ""; }; 43 | 738B72F9194751F700451933 /* ArchiveHandler.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = ArchiveHandler.swift; sourceTree = ""; }; 44 | 7396BA241FD1675100714776 /* ApplicationVersion-AutoGenerated!.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = "ApplicationVersion-AutoGenerated!.swift"; sourceTree = ""; }; 45 | 73C00D32194902A000D3C145 /* Options.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = Options.swift; sourceTree = ""; }; 46 | B6BA76753D3441E88F04772E /* Pods.xcconfig */ = {isa = PBXFileReference; includeInIndex = 1; lastKnownFileType = text.xcconfig; name = Pods.xcconfig; path = Pods/Pods.xcconfig; sourceTree = ""; }; 47 | /* End PBXFileReference section */ 48 | 49 | /* Begin PBXFrameworksBuildPhase section */ 50 | 732614FC194577F500DC069E /* Frameworks */ = { 51 | isa = PBXFrameworksBuildPhase; 52 | buildActionMask = 2147483647; 53 | files = ( 54 | 9907F136D7584532AC171FC6 /* libPods.a in Frameworks */, 55 | ); 56 | runOnlyForDeploymentPostprocessing = 0; 57 | }; 58 | /* End PBXFrameworksBuildPhase section */ 59 | 60 | /* Begin PBXGroup section */ 61 | 732614F6194577F500DC069E = { 62 | isa = PBXGroup; 63 | children = ( 64 | 73261501194577F500DC069E /* symbolicator */, 65 | 73261500194577F500DC069E /* Products */, 66 | E0C192CFDF3E4F1C93DECA82 /* Frameworks */, 67 | B6BA76753D3441E88F04772E /* Pods.xcconfig */, 68 | ); 69 | sourceTree = ""; 70 | }; 71 | 73261500194577F500DC069E /* Products */ = { 72 | isa = PBXGroup; 73 | children = ( 74 | 732614FF194577F500DC069E /* symbolicator */, 75 | ); 76 | name = Products; 77 | sourceTree = ""; 78 | }; 79 | 73261501194577F500DC069E /* symbolicator */ = { 80 | isa = PBXGroup; 81 | children = ( 82 | 732615091945789900DC069E /* symbolicator-Bridging-Header.h */, 83 | 73261502194577F500DC069E /* main.swift */, 84 | 7326150F19457E5B00DC069E /* Symbolicator.swift */, 85 | 738B72F719474C7D00451933 /* FileSymbolicator.swift */, 86 | 738B72F9194751F700451933 /* ArchiveHandler.swift */, 87 | 7396BA241FD1675100714776 /* ApplicationVersion-AutoGenerated!.swift */, 88 | 7326150A194579C900DC069E /* CmdLine */, 89 | 738B72F21947456200451933 /* ThirdParty */, 90 | ); 91 | path = symbolicator; 92 | sourceTree = ""; 93 | }; 94 | 7326150A194579C900DC069E /* CmdLine */ = { 95 | isa = PBXGroup; 96 | children = ( 97 | 7326150D194579F900DC069E /* Settings.swift */, 98 | 73C00D32194902A000D3C145 /* Options.swift */, 99 | ); 100 | name = CmdLine; 101 | sourceTree = ""; 102 | }; 103 | 738B72F21947456200451933 /* ThirdParty */ = { 104 | isa = PBXGroup; 105 | children = ( 106 | 738B72F31947456C00451933 /* ObjCRegex */, 107 | ); 108 | name = ThirdParty; 109 | sourceTree = ""; 110 | }; 111 | 738B72F31947456C00451933 /* ObjCRegex */ = { 112 | isa = PBXGroup; 113 | children = ( 114 | 738B72F41947459400451933 /* RegExCategories.h */, 115 | 738B72F51947459400451933 /* RegExCategories.m */, 116 | ); 117 | path = ObjCRegex; 118 | sourceTree = ""; 119 | }; 120 | E0C192CFDF3E4F1C93DECA82 /* Frameworks */ = { 121 | isa = PBXGroup; 122 | children = ( 123 | 060CC723AC96477D8741508C /* libPods.a */, 124 | ); 125 | name = Frameworks; 126 | sourceTree = ""; 127 | }; 128 | /* End PBXGroup section */ 129 | 130 | /* Begin PBXNativeTarget section */ 131 | 732614FE194577F500DC069E /* symbolicator */ = { 132 | isa = PBXNativeTarget; 133 | buildConfigurationList = 73261506194577F500DC069E /* Build configuration list for PBXNativeTarget "symbolicator" */; 134 | buildPhases = ( 135 | E90AD747AA62416EBD425C82 /* Check Pods Manifest.lock */, 136 | 7396BA231FD166F900714776 /* Update Version and Build Number */, 137 | 732614FB194577F500DC069E /* Sources */, 138 | 732614FC194577F500DC069E /* Frameworks */, 139 | 732614FD194577F500DC069E /* CopyFiles */, 140 | C298F9506ADC4507AAB3BFFA /* Copy Pods Resources */, 141 | ); 142 | buildRules = ( 143 | ); 144 | dependencies = ( 145 | ); 146 | name = symbolicator; 147 | productName = symbolicator; 148 | productReference = 732614FF194577F500DC069E /* symbolicator */; 149 | productType = "com.apple.product-type.tool"; 150 | }; 151 | /* End PBXNativeTarget section */ 152 | 153 | /* Begin PBXProject section */ 154 | 732614F7194577F500DC069E /* Project object */ = { 155 | isa = PBXProject; 156 | attributes = { 157 | LastSwiftMigration = 0710; 158 | LastSwiftUpdateCheck = 0710; 159 | LastUpgradeCheck = 0820; 160 | ORGANIZATIONNAME = "Gentle Bytes"; 161 | TargetAttributes = { 162 | 732614FE194577F500DC069E = { 163 | CreatedOnToolsVersion = 6.0; 164 | LastSwiftMigration = 0820; 165 | }; 166 | }; 167 | }; 168 | buildConfigurationList = 732614FA194577F500DC069E /* Build configuration list for PBXProject "symbolicator" */; 169 | compatibilityVersion = "Xcode 3.2"; 170 | developmentRegion = English; 171 | hasScannedForEncodings = 0; 172 | knownRegions = ( 173 | en, 174 | ); 175 | mainGroup = 732614F6194577F500DC069E; 176 | productRefGroup = 73261500194577F500DC069E /* Products */; 177 | projectDirPath = ""; 178 | projectRoot = ""; 179 | targets = ( 180 | 732614FE194577F500DC069E /* symbolicator */, 181 | ); 182 | }; 183 | /* End PBXProject section */ 184 | 185 | /* Begin PBXShellScriptBuildPhase section */ 186 | 7396BA231FD166F900714776 /* Update Version and Build Number */ = { 187 | isa = PBXShellScriptBuildPhase; 188 | buildActionMask = 2147483647; 189 | files = ( 190 | ); 191 | inputPaths = ( 192 | ); 193 | name = "Update Version and Build Number"; 194 | outputPaths = ( 195 | ); 196 | runOnlyForDeploymentPostprocessing = 0; 197 | shellPath = /bin/sh; 198 | shellScript = "export FILENAME=\"${PROJECT_DIR}/symbolicator/ApplicationVersion-AutoGenerated!.swift\"\nexport APP_VERSION=`git describe --abbrev=0 --tags`\nexport APP_BUILD_NUMBER=`git rev-list --all|wc -l`\nexport COMPILE_DATE=\"$(date +'%D')\"\nexport COPYRIGHT_YEAR=`date \"+%Y\"`\n\necho \"${APP_VERSION} ${APP_BUILD_NUMBER}\"\necho \"Compiled on ${COMPILE_DATE}, Copyright year is ${COPYRIGHT_YEAR}\"\n\necho > ${FILENAME} \"/*\"\necho >> ${FILENAME} \"\"\necho >> ${FILENAME} \"AUTO GENERATED FILE - DO NOT EDIT MANUALLY!\"\necho >> ${FILENAME} \"\"\necho >> ${FILENAME} \"Created by Xcode build script on ${COMPILE_DATE}.\"\necho >> ${FILENAME} \"Copyright (c) ${COPYRIGHT_YEAR} Gentle Bytes. All rights reserved.\"\necho >> ${FILENAME} \"\"\necho >> ${FILENAME} \"*/\"\necho >> ${FILENAME} \"\"\necho >> ${FILENAME} \"import Foundation\"\necho >> ${FILENAME} \"\"\necho >> ${FILENAME} \"let GAppVersion = \\\"${APP_VERSION}\\\"\"\necho >> ${FILENAME} \"let GAppBuildNumber = ${APP_BUILD_NUMBER}\"\necho >> ${FILENAME} \"\"\n"; 199 | }; 200 | C298F9506ADC4507AAB3BFFA /* Copy Pods Resources */ = { 201 | isa = PBXShellScriptBuildPhase; 202 | buildActionMask = 2147483647; 203 | files = ( 204 | ); 205 | inputPaths = ( 206 | ); 207 | name = "Copy Pods Resources"; 208 | outputPaths = ( 209 | ); 210 | runOnlyForDeploymentPostprocessing = 0; 211 | shellPath = /bin/sh; 212 | shellScript = "\"${SRCROOT}/Pods/Pods-resources.sh\"\n"; 213 | showEnvVarsInLog = 0; 214 | }; 215 | E90AD747AA62416EBD425C82 /* Check Pods Manifest.lock */ = { 216 | isa = PBXShellScriptBuildPhase; 217 | buildActionMask = 2147483647; 218 | files = ( 219 | ); 220 | inputPaths = ( 221 | ); 222 | name = "Check Pods Manifest.lock"; 223 | outputPaths = ( 224 | ); 225 | runOnlyForDeploymentPostprocessing = 0; 226 | shellPath = /bin/sh; 227 | shellScript = "diff \"${PODS_ROOT}/../Podfile.lock\" \"${PODS_ROOT}/Manifest.lock\" > /dev/null\nif [[ $? != 0 ]] ; then\n cat << EOM\nerror: The sandbox is not in sync with the Podfile.lock. Run 'pod install' or update your CocoaPods installation.\nEOM\n exit 1\nfi\n"; 228 | showEnvVarsInLog = 0; 229 | }; 230 | /* End PBXShellScriptBuildPhase section */ 231 | 232 | /* Begin PBXSourcesBuildPhase section */ 233 | 732614FB194577F500DC069E /* Sources */ = { 234 | isa = PBXSourcesBuildPhase; 235 | buildActionMask = 2147483647; 236 | files = ( 237 | 7326150E194579F900DC069E /* Settings.swift in Sources */, 238 | 738B72FA194751F700451933 /* ArchiveHandler.swift in Sources */, 239 | 7396BA251FD1675100714776 /* ApplicationVersion-AutoGenerated!.swift in Sources */, 240 | 738B72F61947459400451933 /* RegExCategories.m in Sources */, 241 | 738B72F819474C7D00451933 /* FileSymbolicator.swift in Sources */, 242 | 7326151019457E5B00DC069E /* Symbolicator.swift in Sources */, 243 | 73261503194577F500DC069E /* main.swift in Sources */, 244 | 73C00D33194902A000D3C145 /* Options.swift in Sources */, 245 | ); 246 | runOnlyForDeploymentPostprocessing = 0; 247 | }; 248 | /* End PBXSourcesBuildPhase section */ 249 | 250 | /* Begin XCBuildConfiguration section */ 251 | 73261504194577F500DC069E /* Debug */ = { 252 | isa = XCBuildConfiguration; 253 | buildSettings = { 254 | ALWAYS_SEARCH_USER_PATHS = NO; 255 | CLANG_CXX_LANGUAGE_STANDARD = "gnu++0x"; 256 | CLANG_CXX_LIBRARY = "libc++"; 257 | CLANG_ENABLE_MODULES = YES; 258 | CLANG_ENABLE_OBJC_ARC = YES; 259 | CLANG_WARN_BOOL_CONVERSION = YES; 260 | CLANG_WARN_CONSTANT_CONVERSION = YES; 261 | CLANG_WARN_DIRECT_OBJC_ISA_USAGE = YES_ERROR; 262 | CLANG_WARN_EMPTY_BODY = YES; 263 | CLANG_WARN_ENUM_CONVERSION = YES; 264 | CLANG_WARN_INFINITE_RECURSION = YES; 265 | CLANG_WARN_INT_CONVERSION = YES; 266 | CLANG_WARN_OBJC_ROOT_CLASS = YES_ERROR; 267 | CLANG_WARN_SUSPICIOUS_MOVE = YES; 268 | CLANG_WARN_UNREACHABLE_CODE = YES; 269 | CLANG_WARN__DUPLICATE_METHOD_MATCH = YES; 270 | COPY_PHASE_STRIP = NO; 271 | ENABLE_STRICT_OBJC_MSGSEND = YES; 272 | ENABLE_TESTABILITY = YES; 273 | GCC_C_LANGUAGE_STANDARD = gnu99; 274 | GCC_DYNAMIC_NO_PIC = NO; 275 | GCC_NO_COMMON_BLOCKS = YES; 276 | GCC_OPTIMIZATION_LEVEL = 0; 277 | GCC_PREPROCESSOR_DEFINITIONS = ( 278 | "DEBUG=1", 279 | "$(inherited)", 280 | ); 281 | GCC_SYMBOLS_PRIVATE_EXTERN = NO; 282 | GCC_WARN_64_TO_32_BIT_CONVERSION = YES; 283 | GCC_WARN_ABOUT_RETURN_TYPE = YES_ERROR; 284 | GCC_WARN_UNDECLARED_SELECTOR = YES; 285 | GCC_WARN_UNINITIALIZED_AUTOS = YES_AGGRESSIVE; 286 | GCC_WARN_UNUSED_FUNCTION = YES; 287 | GCC_WARN_UNUSED_VARIABLE = YES; 288 | MACOSX_DEPLOYMENT_TARGET = 10.9; 289 | METAL_ENABLE_DEBUG_INFO = YES; 290 | ONLY_ACTIVE_ARCH = YES; 291 | SDKROOT = macosx; 292 | SWIFT_OPTIMIZATION_LEVEL = "-Onone"; 293 | }; 294 | name = Debug; 295 | }; 296 | 73261505194577F500DC069E /* Release */ = { 297 | isa = XCBuildConfiguration; 298 | buildSettings = { 299 | ALWAYS_SEARCH_USER_PATHS = NO; 300 | CLANG_CXX_LANGUAGE_STANDARD = "gnu++0x"; 301 | CLANG_CXX_LIBRARY = "libc++"; 302 | CLANG_ENABLE_MODULES = YES; 303 | CLANG_ENABLE_OBJC_ARC = YES; 304 | CLANG_WARN_BOOL_CONVERSION = YES; 305 | CLANG_WARN_CONSTANT_CONVERSION = YES; 306 | CLANG_WARN_DIRECT_OBJC_ISA_USAGE = YES_ERROR; 307 | CLANG_WARN_EMPTY_BODY = YES; 308 | CLANG_WARN_ENUM_CONVERSION = YES; 309 | CLANG_WARN_INFINITE_RECURSION = YES; 310 | CLANG_WARN_INT_CONVERSION = YES; 311 | CLANG_WARN_OBJC_ROOT_CLASS = YES_ERROR; 312 | CLANG_WARN_SUSPICIOUS_MOVE = YES; 313 | CLANG_WARN_UNREACHABLE_CODE = YES; 314 | CLANG_WARN__DUPLICATE_METHOD_MATCH = YES; 315 | COPY_PHASE_STRIP = YES; 316 | DEBUG_INFORMATION_FORMAT = "dwarf-with-dsym"; 317 | ENABLE_NS_ASSERTIONS = NO; 318 | ENABLE_STRICT_OBJC_MSGSEND = YES; 319 | GCC_C_LANGUAGE_STANDARD = gnu99; 320 | GCC_NO_COMMON_BLOCKS = YES; 321 | GCC_WARN_64_TO_32_BIT_CONVERSION = YES; 322 | GCC_WARN_ABOUT_RETURN_TYPE = YES_ERROR; 323 | GCC_WARN_UNDECLARED_SELECTOR = YES; 324 | GCC_WARN_UNINITIALIZED_AUTOS = YES_AGGRESSIVE; 325 | GCC_WARN_UNUSED_FUNCTION = YES; 326 | GCC_WARN_UNUSED_VARIABLE = YES; 327 | MACOSX_DEPLOYMENT_TARGET = 10.9; 328 | METAL_ENABLE_DEBUG_INFO = NO; 329 | SDKROOT = macosx; 330 | SWIFT_OPTIMIZATION_LEVEL = "-Owholemodule"; 331 | }; 332 | name = Release; 333 | }; 334 | 73261507194577F500DC069E /* Debug */ = { 335 | isa = XCBuildConfiguration; 336 | baseConfigurationReference = B6BA76753D3441E88F04772E /* Pods.xcconfig */; 337 | buildSettings = { 338 | OTHER_LDFLAGS = ""; 339 | PRODUCT_NAME = "$(TARGET_NAME)"; 340 | SWIFT_OBJC_BRIDGING_HEADER = "symbolicator/symbolicator-Bridging-Header.h"; 341 | SWIFT_VERSION = 3.0; 342 | }; 343 | name = Debug; 344 | }; 345 | 73261508194577F500DC069E /* Release */ = { 346 | isa = XCBuildConfiguration; 347 | baseConfigurationReference = B6BA76753D3441E88F04772E /* Pods.xcconfig */; 348 | buildSettings = { 349 | OTHER_LDFLAGS = ""; 350 | PRODUCT_NAME = "$(TARGET_NAME)"; 351 | SWIFT_OBJC_BRIDGING_HEADER = "symbolicator/symbolicator-Bridging-Header.h"; 352 | SWIFT_VERSION = 3.0; 353 | }; 354 | name = Release; 355 | }; 356 | /* End XCBuildConfiguration section */ 357 | 358 | /* Begin XCConfigurationList section */ 359 | 732614FA194577F500DC069E /* Build configuration list for PBXProject "symbolicator" */ = { 360 | isa = XCConfigurationList; 361 | buildConfigurations = ( 362 | 73261504194577F500DC069E /* Debug */, 363 | 73261505194577F500DC069E /* Release */, 364 | ); 365 | defaultConfigurationIsVisible = 0; 366 | defaultConfigurationName = Release; 367 | }; 368 | 73261506194577F500DC069E /* Build configuration list for PBXNativeTarget "symbolicator" */ = { 369 | isa = XCConfigurationList; 370 | buildConfigurations = ( 371 | 73261507194577F500DC069E /* Debug */, 372 | 73261508194577F500DC069E /* Release */, 373 | ); 374 | defaultConfigurationIsVisible = 0; 375 | defaultConfigurationName = Release; 376 | }; 377 | /* End XCConfigurationList section */ 378 | }; 379 | rootObject = 732614F7194577F500DC069E /* Project object */; 380 | } 381 | -------------------------------------------------------------------------------- /Pods/GBCli/Readme.markdown: -------------------------------------------------------------------------------- 1 | GBCli 2 | ----- 3 | 4 | GBCli is command line interface helper library. It provides classes for simplifying command line Objective C foundation tools code. The library is designed to be as flexible as possible to suit wide range of tools. It requires no external dependency besides *Foundation.framework*. The library was inspired by [DDCli](http://www.dribin.org/dave/software/#ddcli) by Dave Dribin. 5 | 6 | Here's how it happened (skip this part if you're only interested in techical stuff :) - When I started work on redesigning [appledoc](http://gentlebytes.com/appledoc), one of the first files I included was DDCli library. However I soon discovered it doesn't work well with arc. That, coupled with different workflow I wanted, prompted me to dig in Dave's code to see how I could change it to suit my needs better. To cut long story short - at the end I ended writing the whole library from scratch and now parsing is implemented manually, without relying on `getopt_long`. As I was adding more functionality, I realized, I could make it as reusable component and publish it on GitHub... 7 | 8 | This file serves as tutorial demonstrating what you can do with the library. 9 | 10 | 11 | Integrating to your project 12 | --------------------------- 13 | 14 | The simplest way of integrating GBCli is through cocoapods. Just add this line to your Podfile: 15 | 16 | ``` 17 | pod "GBCli" 18 | ``` 19 | 20 | Then import all files so compiler can see them: `#import `. 21 | 22 | If you prefer to include sources directly, just copy all .h and .m files from `GBCli/src` subfolder to your Xcode project and import `GBCli.h` header (in this case you'll probably need to use `#import "GBCli.h"`). 23 | 24 | 25 | Parsing command line arguments 26 | ------------------------------ 27 | 28 | The most basic building block of the library is parsing command line arguments. This is implemented with *GBCommandLineParser* class. To use it, you need to register all options first, then have it parse command line. Based on registered data, the class determines whether command line is valid or not. But that alone wouldn't be that useful, so the class also offers interface for getting the actual options and argument values. You can use callback API to be notified about each option and/or argument as it is parsed (**Note:** with *options* I refer to command line options such as `--verbose`, `--help` etc, while *arguments* are various arguments that follow the options such as paths to source files etc). 29 | 30 | Besides callback API, parser also stores all recognized options and arguments internally, so you can query it for values after parsing is complete. This can solve the problem with storing values. 31 | 32 | An example: 33 | 34 | ``` 35 | int main(int argc, char **argv) { 36 | // Create parser and register all options. 37 | GBCommandLineParser *parser = [[GBCommandLineParser alloc] init]; 38 | [parser registerOption:@"optiona" shortcut:'a' requirement:GBValueRequired]; 39 | [parser registerOption:@"optionb" shortcut:'b' requirement:GBValueOptional]; 40 | [parser registerOption:@"optionc" shortcut:'c' requirement:GBValueNone]; 41 | 42 | // Parse command line 43 | [parser parseOptionsWithArguments:argv count:argc block:^(GBParseFlags flags, NSString *option, id value, BOOL *stop) { 44 | switch (flags) { 45 | case GBParseFlagUnknownOption: 46 | printf("Unknown command line option %s, try --help!\n", [option UTF8String]); 47 | break; 48 | case GBParseFlagMissingValue: 49 | printf("Missing value for command line option %s, try --help!\n", [option UTF8String]); 50 | break; 51 | case GBParseFlagOption: 52 | // do something with 'option' and its 'value' 53 | break; 54 | case GBParseFlagArgument: 55 | // do something with argument 'value' 56 | break; 57 | } 58 | }]; 59 | ... 60 | // Now that parsing is complete, you can access options and arguments: 61 | id valuea = [parser valueForOption:@"optiona"]; 62 | NSArray *arguments = parser.arguments; 63 | ... 64 | } 65 | ``` 66 | 67 | In above example, we've registered 3 options: 68 | 69 | - `--optiona` with shortcut `-a` as a required value. 70 | - `--optionb` with shortcut `-b` as an optional value. 71 | - `--optionc` with shortcut `-c` and no value. 72 | 73 | You can also register only long option, without short variant - just pass `0` for the short option argument. As you can see you can specify each option's value as required, optional or no value. Here's how it works: 74 | 75 | - *Required values:* the option must be followed by a value like this: `--optiona value`, `-a value` or alternatively `--optiona=value` or `-a=value` (in later case there must be no space in between the option name, equal sign and the value!). If value is missing, parser will report missing value via *GBParseFlagMissingValue* flag. Note that you need to embed strings with whitespaces into quotes like this: `--some-option "My whitespaced string"`. 76 | - *Optional values:* the value is optional, so all of these are valid: `--optionb value`, `-b value`, `--optionb=value`, `-b=value`. In these cases, the given value is reported as a *NSString*. Additionally, you can also omit the value altogether: `--optionb` or `-b`. In this case, *NSNumber* setup as `@YES` is reported to block as long as the option is provided on command line. 77 | - *No value:* the option is a "command line boolean switch". If the option is found on command line (for example `--optionc` or `-c`) it is assumed as "enabled" and an `[NSNumber numberWithBool:YES]` is reported as its value in parsing block. These options can also use negated syntax in the form `--no-