diff --git a/Analyzer/XcodeZombieCodeAnalyzer b/Analyzer/XcodeZombieCodeAnalyzer new file mode 100755 index 0000000..abd60d7 Binary files /dev/null and b/Analyzer/XcodeZombieCodeAnalyzer differ diff --git a/Analyzer/preBuild.sh b/Analyzer/preBuild.sh index 91ba083..6d94715 100755 --- a/Analyzer/preBuild.sh +++ b/Analyzer/preBuild.sh @@ -5,7 +5,7 @@ cd $dir #Clear Invalid Data. rm *.jsonpart -#rm *.json +rm *.json #clang-format prjDir=$(dirname $dir) diff --git a/ClangZombieCodePlugin/ClangZombieCodePlugin.xcodeproj/project.pbxproj b/ClangZombieCodePlugin/ClangZombieCodePlugin.xcodeproj/project.pbxproj deleted file mode 100644 index 9666ad9..0000000 --- a/ClangZombieCodePlugin/ClangZombieCodePlugin.xcodeproj/project.pbxproj +++ /dev/null @@ -1,433 +0,0 @@ -// !$*UTF8*$! -{ - archiveVersion = 1; - classes = { - }; - objectVersion = 46; - objects = { - -/* Begin PBXBuildFile section */ - 293BFCC21DC104DF003FAEF0 /* ClangZombieCodePlugin.hpp in Headers */ = {isa = PBXBuildFile; fileRef = 293BFCC11DC104DF003FAEF0 /* ClangZombieCodePlugin.hpp */; }; - 293BFCC41DC104DF003FAEF0 /* ClangZombieCodePluginPriv.hpp in Headers */ = {isa = PBXBuildFile; fileRef = 293BFCC31DC104DF003FAEF0 /* ClangZombieCodePluginPriv.hpp */; }; - 293BFCC61DC104DF003FAEF0 /* ClangZombieCodePlugin.cpp in Sources */ = {isa = PBXBuildFile; fileRef = 293BFCC51DC104DF003FAEF0 /* ClangZombieCodePlugin.cpp */; }; - 293BFCF21DC105B0003FAEF0 /* json.hpp in Headers */ = {isa = PBXBuildFile; fileRef = 293BFCEF1DC105B0003FAEF0 /* json.hpp */; }; - 293BFCF31DC105B0003FAEF0 /* ZombieCodeUtil.cpp in Sources */ = {isa = PBXBuildFile; fileRef = 293BFCF01DC105B0003FAEF0 /* ZombieCodeUtil.cpp */; }; - 293BFCF41DC105B0003FAEF0 /* ZombieCodeUtil.hpp in Headers */ = {isa = PBXBuildFile; fileRef = 293BFCF11DC105B0003FAEF0 /* ZombieCodeUtil.hpp */; }; - 293BFCF71DC10749003FAEF0 /* libz.tbd in Frameworks */ = {isa = PBXBuildFile; fileRef = 293BFCF61DC10749003FAEF0 /* libz.tbd */; }; -/* End PBXBuildFile section */ - -/* Begin PBXFileReference section */ - 293BFCBE1DC104DF003FAEF0 /* libClangZombieCodePlugin.dylib */ = {isa = PBXFileReference; explicitFileType = "compiled.mach-o.dylib"; includeInIndex = 0; path = libClangZombieCodePlugin.dylib; sourceTree = BUILT_PRODUCTS_DIR; }; - 293BFCC11DC104DF003FAEF0 /* ClangZombieCodePlugin.hpp */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.cpp.h; path = ClangZombieCodePlugin.hpp; sourceTree = ""; }; - 293BFCC31DC104DF003FAEF0 /* ClangZombieCodePluginPriv.hpp */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.cpp.h; path = ClangZombieCodePluginPriv.hpp; sourceTree = ""; }; - 293BFCC51DC104DF003FAEF0 /* ClangZombieCodePlugin.cpp */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.cpp.cpp; path = ClangZombieCodePlugin.cpp; sourceTree = ""; }; - 293BFCEF1DC105B0003FAEF0 /* json.hpp */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.cpp.h; path = json.hpp; sourceTree = ""; }; - 293BFCF01DC105B0003FAEF0 /* ZombieCodeUtil.cpp */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.cpp.cpp; path = ZombieCodeUtil.cpp; sourceTree = ""; }; - 293BFCF11DC105B0003FAEF0 /* ZombieCodeUtil.hpp */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.cpp.h; path = ZombieCodeUtil.hpp; sourceTree = ""; }; - 293BFCF61DC10749003FAEF0 /* libz.tbd */ = {isa = PBXFileReference; lastKnownFileType = "sourcecode.text-based-dylib-definition"; name = libz.tbd; path = usr/lib/libz.tbd; sourceTree = SDKROOT; }; -/* End PBXFileReference section */ - -/* Begin PBXFrameworksBuildPhase section */ - 293BFCBB1DC104DF003FAEF0 /* Frameworks */ = { - isa = PBXFrameworksBuildPhase; - buildActionMask = 2147483647; - files = ( - 293BFCF71DC10749003FAEF0 /* libz.tbd in Frameworks */, - ); - runOnlyForDeploymentPostprocessing = 0; - }; -/* End PBXFrameworksBuildPhase section */ - -/* Begin PBXGroup section */ - 293BFCB51DC104DF003FAEF0 = { - isa = PBXGroup; - children = ( - 293BFCC01DC104DF003FAEF0 /* ClangZombieCodePlugin */, - 293BFCBF1DC104DF003FAEF0 /* Products */, - 293BFCF51DC10749003FAEF0 /* Frameworks */, - ); - sourceTree = ""; - }; - 293BFCBF1DC104DF003FAEF0 /* Products */ = { - isa = PBXGroup; - children = ( - 293BFCBE1DC104DF003FAEF0 /* libClangZombieCodePlugin.dylib */, - ); - name = Products; - sourceTree = ""; - }; - 293BFCC01DC104DF003FAEF0 /* ClangZombieCodePlugin */ = { - isa = PBXGroup; - children = ( - 293BFCEF1DC105B0003FAEF0 /* json.hpp */, - 293BFCF01DC105B0003FAEF0 /* ZombieCodeUtil.cpp */, - 293BFCF11DC105B0003FAEF0 /* ZombieCodeUtil.hpp */, - 293BFCC11DC104DF003FAEF0 /* ClangZombieCodePlugin.hpp */, - 293BFCC31DC104DF003FAEF0 /* ClangZombieCodePluginPriv.hpp */, - 293BFCC51DC104DF003FAEF0 /* ClangZombieCodePlugin.cpp */, - ); - path = ClangZombieCodePlugin; - sourceTree = ""; - }; - 293BFCF51DC10749003FAEF0 /* Frameworks */ = { - isa = PBXGroup; - children = ( - 293BFCF61DC10749003FAEF0 /* libz.tbd */, - ); - name = Frameworks; - sourceTree = ""; - }; -/* End PBXGroup section */ - -/* Begin PBXHeadersBuildPhase section */ - 293BFCBC1DC104DF003FAEF0 /* Headers */ = { - isa = PBXHeadersBuildPhase; - buildActionMask = 2147483647; - files = ( - 293BFCF21DC105B0003FAEF0 /* json.hpp in Headers */, - 293BFCC21DC104DF003FAEF0 /* ClangZombieCodePlugin.hpp in Headers */, - 293BFCC41DC104DF003FAEF0 /* ClangZombieCodePluginPriv.hpp in Headers */, - 293BFCF41DC105B0003FAEF0 /* ZombieCodeUtil.hpp in Headers */, - ); - runOnlyForDeploymentPostprocessing = 0; - }; -/* End PBXHeadersBuildPhase section */ - -/* Begin PBXNativeTarget section */ - 293BFCBD1DC104DF003FAEF0 /* ClangZombieCodePlugin */ = { - isa = PBXNativeTarget; - buildConfigurationList = 293BFCC91DC104DF003FAEF0 /* Build configuration list for PBXNativeTarget "ClangZombieCodePlugin" */; - buildPhases = ( - 293BFCBA1DC104DF003FAEF0 /* Sources */, - 293BFCBB1DC104DF003FAEF0 /* Frameworks */, - 293BFCBC1DC104DF003FAEF0 /* Headers */, - ); - buildRules = ( - ); - dependencies = ( - ); - name = ClangZombieCodePlugin; - productName = ClangZombieCodePlugin; - productReference = 293BFCBE1DC104DF003FAEF0 /* libClangZombieCodePlugin.dylib */; - productType = "com.apple.product-type.library.dynamic"; - }; -/* End PBXNativeTarget section */ - -/* Begin PBXProject section */ - 293BFCB61DC104DF003FAEF0 /* Project object */ = { - isa = PBXProject; - attributes = { - LastUpgradeCheck = 0800; - ORGANIZATIONNAME = KyleWong; - TargetAttributes = { - 293BFCBD1DC104DF003FAEF0 = { - CreatedOnToolsVersion = 8.0; - DevelopmentTeam = VP3AM7E5ZL; - ProvisioningStyle = Automatic; - }; - }; - }; - buildConfigurationList = 293BFCB91DC104DF003FAEF0 /* Build configuration list for PBXProject "ClangZombieCodePlugin" */; - compatibilityVersion = "Xcode 3.2"; - developmentRegion = English; - hasScannedForEncodings = 0; - knownRegions = ( - en, - ); - mainGroup = 293BFCB51DC104DF003FAEF0; - productRefGroup = 293BFCBF1DC104DF003FAEF0 /* Products */; - projectDirPath = ""; - projectRoot = ""; - targets = ( - 293BFCBD1DC104DF003FAEF0 /* ClangZombieCodePlugin */, - ); - }; -/* End PBXProject section */ - -/* Begin PBXSourcesBuildPhase section */ - 293BFCBA1DC104DF003FAEF0 /* Sources */ = { - isa = PBXSourcesBuildPhase; - buildActionMask = 2147483647; - files = ( - 293BFCF31DC105B0003FAEF0 /* ZombieCodeUtil.cpp in Sources */, - 293BFCC61DC104DF003FAEF0 /* ClangZombieCodePlugin.cpp in Sources */, - ); - runOnlyForDeploymentPostprocessing = 0; - }; -/* End PBXSourcesBuildPhase section */ - -/* Begin XCBuildConfiguration section */ - 293BFCC71DC104DF003FAEF0 /* Debug */ = { - isa = XCBuildConfiguration; - buildSettings = { - ALWAYS_SEARCH_USER_PATHS = NO; - CLANG_ANALYZER_NONNULL = YES; - CLANG_CXX_LANGUAGE_STANDARD = "gnu++0x"; - CLANG_CXX_LIBRARY = "libc++"; - CLANG_ENABLE_MODULES = YES; - CLANG_ENABLE_OBJC_ARC = YES; - CLANG_WARN_BOOL_CONVERSION = YES; - CLANG_WARN_CONSTANT_CONVERSION = YES; - CLANG_WARN_DIRECT_OBJC_ISA_USAGE = YES_ERROR; - CLANG_WARN_DOCUMENTATION_COMMENTS = YES; - CLANG_WARN_EMPTY_BODY = YES; - CLANG_WARN_ENUM_CONVERSION = YES; - CLANG_WARN_INFINITE_RECURSION = YES; - CLANG_WARN_INT_CONVERSION = YES; - CLANG_WARN_OBJC_ROOT_CLASS = YES_ERROR; - CLANG_WARN_SUSPICIOUS_MOVES = YES; - CLANG_WARN_UNREACHABLE_CODE = YES; - CLANG_WARN__DUPLICATE_METHOD_MATCH = YES; - CODE_SIGN_IDENTITY = "-"; - COPY_PHASE_STRIP = NO; - DEBUG_INFORMATION_FORMAT = dwarf; - ENABLE_STRICT_OBJC_MSGSEND = YES; - ENABLE_TESTABILITY = YES; - GCC_C_LANGUAGE_STANDARD = gnu99; - GCC_DYNAMIC_NO_PIC = NO; - GCC_NO_COMMON_BLOCKS = YES; - GCC_OPTIMIZATION_LEVEL = 0; - GCC_PREPROCESSOR_DEFINITIONS = ( - "DEBUG=1", - "$(inherited)", - ); - GCC_WARN_64_TO_32_BIT_CONVERSION = YES; - GCC_WARN_ABOUT_RETURN_TYPE = YES_ERROR; - GCC_WARN_UNDECLARED_SELECTOR = YES; - GCC_WARN_UNINITIALIZED_AUTOS = YES_AGGRESSIVE; - GCC_WARN_UNUSED_FUNCTION = YES; - GCC_WARN_UNUSED_VARIABLE = YES; - MACOSX_DEPLOYMENT_TARGET = 10.12; - MTL_ENABLE_DEBUG_INFO = YES; - ONLY_ACTIVE_ARCH = YES; - SDKROOT = macosx; - }; - name = Debug; - }; - 293BFCC81DC104DF003FAEF0 /* Release */ = { - isa = XCBuildConfiguration; - buildSettings = { - ALWAYS_SEARCH_USER_PATHS = NO; - CLANG_ANALYZER_NONNULL = YES; - CLANG_CXX_LANGUAGE_STANDARD = "gnu++0x"; - CLANG_CXX_LIBRARY = "libc++"; - CLANG_ENABLE_MODULES = YES; - CLANG_ENABLE_OBJC_ARC = YES; - CLANG_WARN_BOOL_CONVERSION = YES; - CLANG_WARN_CONSTANT_CONVERSION = YES; - CLANG_WARN_DIRECT_OBJC_ISA_USAGE = YES_ERROR; - CLANG_WARN_DOCUMENTATION_COMMENTS = YES; - CLANG_WARN_EMPTY_BODY = YES; - CLANG_WARN_ENUM_CONVERSION = YES; - CLANG_WARN_INFINITE_RECURSION = YES; - CLANG_WARN_INT_CONVERSION = YES; - CLANG_WARN_OBJC_ROOT_CLASS = YES_ERROR; - CLANG_WARN_SUSPICIOUS_MOVES = YES; - CLANG_WARN_UNREACHABLE_CODE = YES; - CLANG_WARN__DUPLICATE_METHOD_MATCH = YES; - CODE_SIGN_IDENTITY = "-"; - COPY_PHASE_STRIP = NO; - DEBUG_INFORMATION_FORMAT = "dwarf-with-dsym"; - ENABLE_NS_ASSERTIONS = NO; - ENABLE_STRICT_OBJC_MSGSEND = YES; - GCC_C_LANGUAGE_STANDARD = gnu99; - GCC_NO_COMMON_BLOCKS = YES; - GCC_WARN_64_TO_32_BIT_CONVERSION = YES; - GCC_WARN_ABOUT_RETURN_TYPE = YES_ERROR; - GCC_WARN_UNDECLARED_SELECTOR = YES; - GCC_WARN_UNINITIALIZED_AUTOS = YES_AGGRESSIVE; - GCC_WARN_UNUSED_FUNCTION = YES; - GCC_WARN_UNUSED_VARIABLE = YES; - MACOSX_DEPLOYMENT_TARGET = 10.12; - MTL_ENABLE_DEBUG_INFO = NO; - SDKROOT = macosx; - }; - name = Release; - }; - 293BFCCA1DC104DF003FAEF0 /* Debug */ = { - isa = XCBuildConfiguration; - buildSettings = { - CONFIGURATION_BUILD_DIR = /opt/llvm/clangplugin; - DEVELOPMENT_TEAM = VP3AM7E5ZL; - DYLIB_COMPATIBILITY_VERSION = 1; - DYLIB_CURRENT_VERSION = 1; - EXECUTABLE_PREFIX = lib; - GCC_ENABLE_CPP_EXCEPTIONS = YES; - GCC_ENABLE_CPP_RTTI = YES; - GCC_SYMBOLS_PRIVATE_EXTERN = YES; - HEADER_SEARCH_PATHS = ( - /opt/llvm/llvm/include, - /opt/llvm/llvm/tools/clang/include, - /opt/llvm/llvm_build/include, - /opt/llvm/llvm_build/tools/clang/include, - ); - LIBRARY_SEARCH_PATHS = /opt/llvm/llvm_build/lib; - OTHER_CPLUSPLUSFLAGS = ( - "-fPIC", - "-fno-common", - "-Woverloaded-virtual", - "-Wcast-qual", - "-fno-strict-aliasing", - "-pedantic", - "-Wno-long-long", - "-Wall", - "-Wno-unused-parameter", - "-Wwrite-strings", - "-fno-rtti", - "-fPIC", - ); - OTHER_LDFLAGS = ( - "-dynamiclib", - "-Wl,-headerpad_max_install_names", - "-lclang", - "-lclangFrontend", - "-lclangAST", - "-lclangAnalysis", - "-lclangBasic", - "-lclangCodeGen", - "-lclangDriver", - "-lclangFrontendTool", - "-lclangLex", - "-lclangParse", - "-lclangSema", - "-lclangEdit", - "-lclangSerialization", - "-lclangStaticAnalyzerCheckers", - "-lclangStaticAnalyzerCore", - "-lclangStaticAnalyzerFrontend", - "-lLLVMX86CodeGen", - "-lLLVMX86AsmParser", - "-lLLVMX86Disassembler", - "-lLLVMExecutionEngine", - "-lLLVMAsmPrinter", - "-lLLVMSelectionDAG", - "-lLLVMX86AsmPrinter", - "-lLLVMX86Info", - "-lLLVMMCParser", - "-lLLVMCodeGen", - "-lLLVMX86Utils", - "-lLLVMScalarOpts", - "-lLLVMInstCombine", - "-lLLVMTransformUtils", - "-lLLVMAnalysis", - "-lLLVMTarget", - "-lLLVMCore", - "-lLLVMMC", - "-lLLVMSupport", - "-lLLVMBitReader", - "-lLLVMOption", - "-lLLVMProfileData", - "-lpthread", - "-lcurses", - "-lLLVMDemangle", - ); - PRODUCT_NAME = "$(TARGET_NAME)"; - }; - name = Debug; - }; - 293BFCCB1DC104DF003FAEF0 /* Release */ = { - isa = XCBuildConfiguration; - buildSettings = { - CONFIGURATION_BUILD_DIR = /opt/llvm/clangplugin; - DEVELOPMENT_TEAM = VP3AM7E5ZL; - DYLIB_COMPATIBILITY_VERSION = 1; - DYLIB_CURRENT_VERSION = 1; - EXECUTABLE_PREFIX = lib; - GCC_ENABLE_CPP_EXCEPTIONS = YES; - GCC_ENABLE_CPP_RTTI = YES; - GCC_SYMBOLS_PRIVATE_EXTERN = YES; - HEADER_SEARCH_PATHS = ( - /opt/llvm/llvm/include, - /opt/llvm/llvm/tools/clang/include, - /opt/llvm/llvm_build/include, - /opt/llvm/llvm_build/tools/clang/include, - ); - LIBRARY_SEARCH_PATHS = /opt/llvm/llvm_build/lib; - OTHER_CPLUSPLUSFLAGS = ( - "-fPIC", - "-fno-common", - "-Woverloaded-virtual", - "-Wcast-qual", - "-fno-strict-aliasing", - "-pedantic", - "-Wno-long-long", - "-Wall", - "-Wno-unused-parameter", - "-Wwrite-strings", - "-fno-rtti", - "-fPIC", - ); - OTHER_LDFLAGS = ( - "-dynamiclib", - "-Wl,-headerpad_max_install_names", - "-lclang", - "-lclangFrontend", - "-lclangAST", - "-lclangAnalysis", - "-lclangBasic", - "-lclangCodeGen", - "-lclangDriver", - "-lclangFrontendTool", - "-lclangLex", - "-lclangParse", - "-lclangSema", - "-lclangEdit", - "-lclangSerialization", - "-lclangStaticAnalyzerCheckers", - "-lclangStaticAnalyzerCore", - "-lclangStaticAnalyzerFrontend", - "-lLLVMX86CodeGen", - "-lLLVMX86AsmParser", - "-lLLVMX86Disassembler", - "-lLLVMExecutionEngine", - "-lLLVMAsmPrinter", - "-lLLVMSelectionDAG", - "-lLLVMX86AsmPrinter", - "-lLLVMX86Info", - "-lLLVMMCParser", - "-lLLVMCodeGen", - "-lLLVMX86Utils", - "-lLLVMScalarOpts", - "-lLLVMInstCombine", - "-lLLVMTransformUtils", - "-lLLVMAnalysis", - "-lLLVMTarget", - "-lLLVMCore", - "-lLLVMMC", - "-lLLVMSupport", - "-lLLVMBitReader", - "-lLLVMOption", - "-lLLVMProfileData", - "-lpthread", - "-lcurses", - "-lLLVMDemangle", - ); - PRODUCT_NAME = "$(TARGET_NAME)"; - }; - name = Release; - }; -/* End XCBuildConfiguration section */ - -/* Begin XCConfigurationList section */ - 293BFCB91DC104DF003FAEF0 /* Build configuration list for PBXProject "ClangZombieCodePlugin" */ = { - isa = XCConfigurationList; - buildConfigurations = ( - 293BFCC71DC104DF003FAEF0 /* Debug */, - 293BFCC81DC104DF003FAEF0 /* Release */, - ); - defaultConfigurationIsVisible = 0; - defaultConfigurationName = Release; - }; - 293BFCC91DC104DF003FAEF0 /* Build configuration list for PBXNativeTarget "ClangZombieCodePlugin" */ = { - isa = XCConfigurationList; - buildConfigurations = ( - 293BFCCA1DC104DF003FAEF0 /* Debug */, - 293BFCCB1DC104DF003FAEF0 /* Release */, - ); - defaultConfigurationIsVisible = 0; - defaultConfigurationName = Release; - }; -/* End XCConfigurationList section */ - }; - rootObject = 293BFCB61DC104DF003FAEF0 /* Project object */; -} diff --git a/ClangZombieCodePlugin/ClangZombieCodePlugin.xcodeproj/xcuserdata/kylewong.xcuserdatad/xcschemes/ClangZombieCodePlugin.xcscheme b/ClangZombieCodePlugin/ClangZombieCodePlugin.xcodeproj/xcuserdata/kylewong.xcuserdatad/xcschemes/ClangZombieCodePlugin.xcscheme deleted file mode 100644 index 7a1c796..0000000 --- a/ClangZombieCodePlugin/ClangZombieCodePlugin.xcodeproj/xcuserdata/kylewong.xcuserdatad/xcschemes/ClangZombieCodePlugin.xcscheme +++ /dev/null @@ -1,80 +0,0 @@ - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - diff --git a/ClangZombieCodePlugin/ClangZombieCodePlugin.xcodeproj/xcuserdata/kylewong.xcuserdatad/xcschemes/xcschememanagement.plist b/ClangZombieCodePlugin/ClangZombieCodePlugin.xcodeproj/xcuserdata/kylewong.xcuserdatad/xcschemes/xcschememanagement.plist deleted file mode 100644 index 9cb6919..0000000 --- a/ClangZombieCodePlugin/ClangZombieCodePlugin.xcodeproj/xcuserdata/kylewong.xcuserdatad/xcschemes/xcschememanagement.plist +++ /dev/null @@ -1,22 +0,0 @@ - - - - - SchemeUserState - - ClangZombieCodePlugin.xcscheme - - orderHint - 0 - - - SuppressBuildableAutocreation - - 293BFCBD1DC104DF003FAEF0 - - primary - - - - - diff --git a/ClangZombieCodePlugin/ClangZombieCodePlugin/ClangZombieCodePluginPriv.hpp b/ClangZombieCodePlugin/ClangZombieCodePlugin/ClangZombieCodePluginPriv.hpp deleted file mode 100644 index 4b8517a..0000000 --- a/ClangZombieCodePlugin/ClangZombieCodePlugin/ClangZombieCodePluginPriv.hpp +++ /dev/null @@ -1,18 +0,0 @@ -/* - * ClangZombieCodePluginPriv.hpp - * ClangZombieCodePlugin - * - * Created by KyleWong on 26/10/2016. - * Copyright © 2016 KyleWong. All rights reserved. - * - */ - -/* The classes below are not exported */ -#pragma GCC visibility push(hidden) - -class ClangZombieCodePluginPriv -{ - public: -}; - -#pragma GCC visibility pop diff --git a/README.md b/README.md index f29cc42..487d396 100644 --- a/README.md +++ b/README.md @@ -1,3 +1,9 @@ # XcodeZombieCode iOS APP分析无用代码(A source-code level/Clang Approach) [CLANG技术分享系列四:IOS APP无用代码/重复代码分析](http://kangwang1988.github.io/tech/2016/11/01/find-unused-duplicate-code-of-your-app-using-clang-plugin.html) + +Branch[Master]: +Using clang plugin as an approach.If you want to debug the analyzing code, use logs. + +Branch[Libtooling]: +Using libtooling as an approach.You can debug the analyzing code directly. diff --git a/XcodeZombieCode.xcworkspace/contents.xcworkspacedata b/XcodeZombieCode.xcworkspace/contents.xcworkspacedata index 858f5a9..c9d5077 100644 --- a/XcodeZombieCode.xcworkspace/contents.xcworkspacedata +++ b/XcodeZombieCode.xcworkspace/contents.xcworkspacedata @@ -1,9 +1,6 @@ - - diff --git a/XcodeZombieCode.xcworkspace/xcuserdata/kylewong.xcuserdatad/IDEFindNavigatorScopes.plist b/XcodeZombieCode.xcworkspace/xcuserdata/kylewong.xcuserdatad/IDEFindNavigatorScopes.plist new file mode 100644 index 0000000..5dd5da8 --- /dev/null +++ b/XcodeZombieCode.xcworkspace/xcuserdata/kylewong.xcuserdatad/IDEFindNavigatorScopes.plist @@ -0,0 +1,5 @@ + + + + + diff --git a/XcodeZombieCode.xcworkspace/xcuserdata/kylewong.xcuserdatad/UserInterfaceState.xcuserstate b/XcodeZombieCode.xcworkspace/xcuserdata/kylewong.xcuserdatad/UserInterfaceState.xcuserstate index 8591038..7818754 100644 Binary files a/XcodeZombieCode.xcworkspace/xcuserdata/kylewong.xcuserdatad/UserInterfaceState.xcuserstate and b/XcodeZombieCode.xcworkspace/xcuserdata/kylewong.xcuserdatad/UserInterfaceState.xcuserstate differ diff --git a/XcodeZombieCode.xcworkspace/xcuserdata/kylewong.xcuserdatad/xcdebugger/Breakpoints_v2.xcbkptlist b/XcodeZombieCode.xcworkspace/xcuserdata/kylewong.xcuserdatad/xcdebugger/Breakpoints_v2.xcbkptlist new file mode 100644 index 0000000..1e11ee0 --- /dev/null +++ b/XcodeZombieCode.xcworkspace/xcuserdata/kylewong.xcuserdatad/xcdebugger/Breakpoints_v2.xcbkptlist @@ -0,0 +1,23 @@ + + + + + + + + + diff --git a/XcodeZombieCodeAnalyzer/XcodeZombieCodeAnalyzer.xcodeproj/project.pbxproj b/XcodeZombieCodeAnalyzer/XcodeZombieCodeAnalyzer.xcodeproj/project.pbxproj index 5b85819..0f161fa 100644 --- a/XcodeZombieCodeAnalyzer/XcodeZombieCodeAnalyzer.xcodeproj/project.pbxproj +++ b/XcodeZombieCodeAnalyzer/XcodeZombieCodeAnalyzer.xcodeproj/project.pbxproj @@ -7,7 +7,36 @@ objects = { /* Begin PBXBuildFile section */ - 293BFD231DC10A55003FAEF0 /* main.cpp in Sources */ = {isa = PBXBuildFile; fileRef = 293BFD221DC10A55003FAEF0 /* main.cpp */; }; + 29C306381F0A3A7500A5C308 /* libz.tbd in Frameworks */ = {isa = PBXBuildFile; fileRef = 29C306371F0A3A6E00A5C308 /* libz.tbd */; }; + 29D8D5AF1F0A5FAB0030D29C /* json.hpp in Sources */ = {isa = PBXBuildFile; fileRef = 29C306411F0A3B1A00A5C308 /* json.hpp */; }; + 29D8D5B01F0A5FAB0030D29C /* ZombieCodeUtil.cpp in Sources */ = {isa = PBXBuildFile; fileRef = 29C306421F0A3B1A00A5C308 /* ZombieCodeUtil.cpp */; }; + 29D8D5B11F0A5FAB0030D29C /* ClangZombieCodePlugin.cpp in Sources */ = {isa = PBXBuildFile; fileRef = 29C306431F0A3B1A00A5C308 /* ClangZombieCodePlugin.cpp */; }; + 29D8D5B21F0A5FAB0030D29C /* ZombieCodeUtil.hpp in Sources */ = {isa = PBXBuildFile; fileRef = 29C306441F0A3B1A00A5C308 /* ZombieCodeUtil.hpp */; }; + 29D8D5B31F0A5FAB0030D29C /* ClangZombieCodePlugin.hpp in Sources */ = {isa = PBXBuildFile; fileRef = 29C306451F0A3B1A00A5C308 /* ClangZombieCodePlugin.hpp */; }; + 29D8D5B41F0A5FAB0030D29C /* main.cpp in Sources */ = {isa = PBXBuildFile; fileRef = 293BFD221DC10A55003FAEF0 /* main.cpp */; }; + 29D8D5B61F0A76D00030D29C /* libncurses.tbd in Frameworks */ = {isa = PBXBuildFile; fileRef = 29D8D5B51F0A76BD0030D29C /* libncurses.tbd */; }; + 29D8D5E91F0A7B7C0030D29C /* XCVersionGroup.m in Sources */ = {isa = PBXBuildFile; fileRef = 29D8D5BE1F0A7B560030D29C /* XCVersionGroup.m */; }; + 29D8D5EA1F0A7B7C0030D29C /* XcodeSourceTreeType.m in Sources */ = {isa = PBXBuildFile; fileRef = 29D8D5C01F0A7B560030D29C /* XcodeSourceTreeType.m */; }; + 29D8D5EB1F0A7B7C0030D29C /* XCSourceFileDefinition.m in Sources */ = {isa = PBXBuildFile; fileRef = 29D8D5C21F0A7B560030D29C /* XCSourceFileDefinition.m */; }; + 29D8D5EC1F0A7B7C0030D29C /* XCSubProjectDefinition.m in Sources */ = {isa = PBXBuildFile; fileRef = 29D8D5C31F0A7B560030D29C /* XCSubProjectDefinition.m */; }; + 29D8D5ED1F0A7B7C0030D29C /* XCProjectBuildConfig.m in Sources */ = {isa = PBXBuildFile; fileRef = 29D8D5C41F0A7B560030D29C /* XCProjectBuildConfig.m */; }; + 29D8D5EE1F0A7B7C0030D29C /* XCTarget.m in Sources */ = {isa = PBXBuildFile; fileRef = 29D8D5C71F0A7B560030D29C /* XCTarget.m */; }; + 29D8D5EF1F0A7B7C0030D29C /* NSString+RemoveEmoji.m in Sources */ = {isa = PBXBuildFile; fileRef = 29D8D5CA1F0A7B560030D29C /* NSString+RemoveEmoji.m */; }; + 29D8D5F01F0A7B7C0030D29C /* XCKeyBuilder.m in Sources */ = {isa = PBXBuildFile; fileRef = 29D8D5CC1F0A7B560030D29C /* XCKeyBuilder.m */; }; + 29D8D5F11F0A7B7C0030D29C /* XcodeSourceFileType.m in Sources */ = {isa = PBXBuildFile; fileRef = 29D8D5CF1F0A7B560030D29C /* XcodeSourceFileType.m */; }; + 29D8D5F21F0A7B7C0030D29C /* XCXibDefinition.m in Sources */ = {isa = PBXBuildFile; fileRef = 29D8D5D41F0A7B560030D29C /* XCXibDefinition.m */; }; + 29D8D5F31F0A7B7C0030D29C /* XCBuildShellScript.m in Sources */ = {isa = PBXBuildFile; fileRef = 29D8D5D61F0A7B560030D29C /* XCBuildShellScript.m */; }; + 29D8D5F41F0A7B7C0030D29C /* XCFrameworkDefinition.m in Sources */ = {isa = PBXBuildFile; fileRef = 29D8D5D81F0A7B560030D29C /* XCFrameworkDefinition.m */; }; + 29D8D5F51F0A7B7C0030D29C /* XCFileOperationQueue.m in Sources */ = {isa = PBXBuildFile; fileRef = 29D8D5D91F0A7B560030D29C /* XCFileOperationQueue.m */; }; + 29D8D5F61F0A7B7C0030D29C /* XCAbstractDefinition.m in Sources */ = {isa = PBXBuildFile; fileRef = 29D8D5DA1F0A7B560030D29C /* XCAbstractDefinition.m */; }; + 29D8D5F71F0A7B7C0030D29C /* XCProject.m in Sources */ = {isa = PBXBuildFile; fileRef = 29D8D5DB1F0A7B560030D29C /* XCProject.m */; }; + 29D8D5F81F0A7B7C0030D29C /* XCClassDefinition.m in Sources */ = {isa = PBXBuildFile; fileRef = 29D8D5DF1F0A7B560030D29C /* XCClassDefinition.m */; }; + 29D8D5F91F0A7B7D0030D29C /* XcodeMemberType.m in Sources */ = {isa = PBXBuildFile; fileRef = 29D8D5E11F0A7B560030D29C /* XcodeMemberType.m */; }; + 29D8D5FA1F0A7B7D0030D29C /* XCSourceFile.m in Sources */ = {isa = PBXBuildFile; fileRef = 29D8D5E21F0A7B560030D29C /* XCSourceFile.m */; }; + 29D8D5FB1F0A7B7D0030D29C /* XCBuildShellScriptDefinition.m in Sources */ = {isa = PBXBuildFile; fileRef = 29D8D5E41F0A7B560030D29C /* XCBuildShellScriptDefinition.m */; }; + 29D8D5FC1F0A7B7D0030D29C /* XCProject+SubProject.m in Sources */ = {isa = PBXBuildFile; fileRef = 29D8D5E51F0A7B560030D29C /* XCProject+SubProject.m */; }; + 29D8D5FD1F0A7B7D0030D29C /* XCGroup.m in Sources */ = {isa = PBXBuildFile; fileRef = 29D8D5E61F0A7B560030D29C /* XCGroup.m */; }; + 29D8D5FE1F0A7B7D0030D29C /* XcodeEditorCPPWrapper.mm in Sources */ = {isa = PBXBuildFile; fileRef = 29D8D5E81F0A7B560030D29C /* XcodeEditorCPPWrapper.mm */; }; /* End PBXBuildFile section */ /* Begin PBXCopyFilesBuildPhase section */ @@ -24,9 +53,61 @@ /* Begin PBXFileReference section */ 293BFD161DC10854003FAEF0 /* XcodeZombieCodeAnalyzer */ = {isa = PBXFileReference; explicitFileType = "compiled.mach-o.executable"; includeInIndex = 0; path = XcodeZombieCodeAnalyzer; sourceTree = BUILT_PRODUCTS_DIR; }; - 293BFD201DC10A55003FAEF0 /* ZombieCodeUtil.hpp */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.cpp.h; path = ZombieCodeUtil.hpp; sourceTree = ""; }; - 293BFD211DC10A55003FAEF0 /* json.hpp */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.cpp.h; path = json.hpp; sourceTree = ""; }; 293BFD221DC10A55003FAEF0 /* main.cpp */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.cpp.cpp; path = main.cpp; sourceTree = ""; }; + 29C306371F0A3A6E00A5C308 /* libz.tbd */ = {isa = PBXFileReference; lastKnownFileType = "sourcecode.text-based-dylib-definition"; name = libz.tbd; path = usr/lib/libz.tbd; sourceTree = SDKROOT; }; + 29C306411F0A3B1A00A5C308 /* json.hpp */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.cpp.h; path = json.hpp; sourceTree = ""; }; + 29C306421F0A3B1A00A5C308 /* ZombieCodeUtil.cpp */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.cpp.cpp; path = ZombieCodeUtil.cpp; sourceTree = ""; }; + 29C306431F0A3B1A00A5C308 /* ClangZombieCodePlugin.cpp */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.cpp.cpp; path = ClangZombieCodePlugin.cpp; sourceTree = ""; }; + 29C306441F0A3B1A00A5C308 /* ZombieCodeUtil.hpp */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.cpp.h; path = ZombieCodeUtil.hpp; sourceTree = ""; }; + 29C306451F0A3B1A00A5C308 /* ClangZombieCodePlugin.hpp */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.cpp.h; path = ClangZombieCodePlugin.hpp; sourceTree = ""; }; + 29D8D5B51F0A76BD0030D29C /* libncurses.tbd */ = {isa = PBXFileReference; lastKnownFileType = "sourcecode.text-based-dylib-definition"; name = libncurses.tbd; path = usr/lib/libncurses.tbd; sourceTree = SDKROOT; }; + 29D8D5B71F0A7B560030D29C /* XcodeEditorCPPWrapper.h */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.c.h; path = XcodeEditorCPPWrapper.h; sourceTree = ""; }; + 29D8D5BA1F0A7B560030D29C /* XCXibDefinition.h */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.c.h; path = XCXibDefinition.h; sourceTree = ""; }; + 29D8D5BB1F0A7B560030D29C /* XcodeGroupMember.h */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.c.h; path = XcodeGroupMember.h; sourceTree = ""; }; + 29D8D5BC1F0A7B560030D29C /* XCFileOperationQueue.h */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.c.h; path = XCFileOperationQueue.h; sourceTree = ""; }; + 29D8D5BD1F0A7B560030D29C /* XCFrameworkDefinition.h */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.c.h; path = XCFrameworkDefinition.h; sourceTree = ""; }; + 29D8D5BE1F0A7B560030D29C /* XCVersionGroup.m */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.c.objc; path = XCVersionGroup.m; sourceTree = ""; }; + 29D8D5BF1F0A7B560030D29C /* XCBuildShellScript.h */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.c.h; path = XCBuildShellScript.h; sourceTree = ""; }; + 29D8D5C01F0A7B560030D29C /* XcodeSourceTreeType.m */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.c.objc; path = XcodeSourceTreeType.m; sourceTree = ""; }; + 29D8D5C11F0A7B560030D29C /* XCClassDefinition.h */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.c.h; path = XCClassDefinition.h; sourceTree = ""; }; + 29D8D5C21F0A7B560030D29C /* XCSourceFileDefinition.m */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.c.objc; path = XCSourceFileDefinition.m; sourceTree = ""; }; + 29D8D5C31F0A7B560030D29C /* XCSubProjectDefinition.m */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.c.objc; path = XCSubProjectDefinition.m; sourceTree = ""; }; + 29D8D5C41F0A7B560030D29C /* XCProjectBuildConfig.m */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.c.objc; path = XCProjectBuildConfig.m; sourceTree = ""; }; + 29D8D5C51F0A7B560030D29C /* XCProject.h */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.c.h; path = XCProject.h; sourceTree = ""; }; + 29D8D5C61F0A7B560030D29C /* XCAbstractDefinition.h */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.c.h; path = XCAbstractDefinition.h; sourceTree = ""; }; + 29D8D5C71F0A7B560030D29C /* XCTarget.m */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.c.objc; path = XCTarget.m; sourceTree = ""; }; + 29D8D5C81F0A7B560030D29C /* XCGroup.h */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.c.h; path = XCGroup.h; sourceTree = ""; }; + 29D8D5CA1F0A7B560030D29C /* NSString+RemoveEmoji.m */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.c.objc; path = "NSString+RemoveEmoji.m"; sourceTree = ""; }; + 29D8D5CB1F0A7B560030D29C /* XCKeyBuilder.h */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.c.h; path = XCKeyBuilder.h; sourceTree = ""; }; + 29D8D5CC1F0A7B560030D29C /* XCKeyBuilder.m */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.c.objc; path = XCKeyBuilder.m; sourceTree = ""; }; + 29D8D5CD1F0A7B560030D29C /* NSString+RemoveEmoji.h */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.c.h; path = "NSString+RemoveEmoji.h"; sourceTree = ""; }; + 29D8D5CE1F0A7B560030D29C /* XCProject+SubProject.h */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.c.h; path = "XCProject+SubProject.h"; sourceTree = ""; }; + 29D8D5CF1F0A7B560030D29C /* XcodeSourceFileType.m */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.c.objc; path = XcodeSourceFileType.m; sourceTree = ""; }; + 29D8D5D01F0A7B560030D29C /* XCBuildShellScriptDefinition.h */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.c.h; path = XCBuildShellScriptDefinition.h; sourceTree = ""; }; + 29D8D5D11F0A7B560030D29C /* XCSourceFile.h */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.c.h; path = XCSourceFile.h; sourceTree = ""; }; + 29D8D5D21F0A7B560030D29C /* XcodeEditor.h */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.c.h; path = XcodeEditor.h; sourceTree = ""; }; + 29D8D5D31F0A7B560030D29C /* XcodeMemberType.h */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.c.h; path = XcodeMemberType.h; sourceTree = ""; }; + 29D8D5D41F0A7B560030D29C /* XCXibDefinition.m */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.c.objc; path = XCXibDefinition.m; sourceTree = ""; }; + 29D8D5D51F0A7B560030D29C /* XCBuildFile.h */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.c.h; path = XCBuildFile.h; sourceTree = ""; }; + 29D8D5D61F0A7B560030D29C /* XCBuildShellScript.m */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.c.objc; path = XCBuildShellScript.m; sourceTree = ""; }; + 29D8D5D71F0A7B560030D29C /* XCVersionGroup.h */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.c.h; path = XCVersionGroup.h; sourceTree = ""; }; + 29D8D5D81F0A7B560030D29C /* XCFrameworkDefinition.m */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.c.objc; path = XCFrameworkDefinition.m; sourceTree = ""; }; + 29D8D5D91F0A7B560030D29C /* XCFileOperationQueue.m */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.c.objc; path = XCFileOperationQueue.m; sourceTree = ""; }; + 29D8D5DA1F0A7B560030D29C /* XCAbstractDefinition.m */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.c.objc; path = XCAbstractDefinition.m; sourceTree = ""; }; + 29D8D5DB1F0A7B560030D29C /* XCProject.m */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.c.objc; path = XCProject.m; sourceTree = ""; }; + 29D8D5DC1F0A7B560030D29C /* XCProjectBuildConfig.h */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.c.h; path = XCProjectBuildConfig.h; sourceTree = ""; }; + 29D8D5DD1F0A7B560030D29C /* XCSubProjectDefinition.h */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.c.h; path = XCSubProjectDefinition.h; sourceTree = ""; }; + 29D8D5DE1F0A7B560030D29C /* XCSourceFileDefinition.h */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.c.h; path = XCSourceFileDefinition.h; sourceTree = ""; }; + 29D8D5DF1F0A7B560030D29C /* XCClassDefinition.m */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.c.objc; path = XCClassDefinition.m; sourceTree = ""; }; + 29D8D5E01F0A7B560030D29C /* XcodeSourceTreeType.h */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.c.h; path = XcodeSourceTreeType.h; sourceTree = ""; }; + 29D8D5E11F0A7B560030D29C /* XcodeMemberType.m */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.c.objc; path = XcodeMemberType.m; sourceTree = ""; }; + 29D8D5E21F0A7B560030D29C /* XCSourceFile.m */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.c.objc; path = XCSourceFile.m; sourceTree = ""; }; + 29D8D5E31F0A7B560030D29C /* XcodeSourceFileType.h */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.c.h; path = XcodeSourceFileType.h; sourceTree = ""; }; + 29D8D5E41F0A7B560030D29C /* XCBuildShellScriptDefinition.m */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.c.objc; path = XCBuildShellScriptDefinition.m; sourceTree = ""; }; + 29D8D5E51F0A7B560030D29C /* XCProject+SubProject.m */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.c.objc; path = "XCProject+SubProject.m"; sourceTree = ""; }; + 29D8D5E61F0A7B560030D29C /* XCGroup.m */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.c.objc; path = XCGroup.m; sourceTree = ""; }; + 29D8D5E71F0A7B560030D29C /* XCTarget.h */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.c.h; path = XCTarget.h; sourceTree = ""; }; + 29D8D5E81F0A7B560030D29C /* XcodeEditorCPPWrapper.mm */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.cpp.objcpp; path = XcodeEditorCPPWrapper.mm; sourceTree = ""; }; /* End PBXFileReference section */ /* Begin PBXFrameworksBuildPhase section */ @@ -34,6 +115,8 @@ isa = PBXFrameworksBuildPhase; buildActionMask = 2147483647; files = ( + 29D8D5B61F0A76D00030D29C /* libncurses.tbd in Frameworks */, + 29C306381F0A3A7500A5C308 /* libz.tbd in Frameworks */, ); runOnlyForDeploymentPostprocessing = 0; }; @@ -45,6 +128,7 @@ children = ( 293BFD181DC10854003FAEF0 /* XcodeZombieCodeAnalyzer */, 293BFD171DC10854003FAEF0 /* Products */, + 29C306361F0A3A6D00A5C308 /* Frameworks */, ); sourceTree = ""; }; @@ -59,13 +143,104 @@ 293BFD181DC10854003FAEF0 /* XcodeZombieCodeAnalyzer */ = { isa = PBXGroup; children = ( - 293BFD201DC10A55003FAEF0 /* ZombieCodeUtil.hpp */, - 293BFD211DC10A55003FAEF0 /* json.hpp */, + 29D8D5B81F0A7B560030D29C /* Vendor */, + 29D8D5B71F0A7B560030D29C /* XcodeEditorCPPWrapper.h */, + 29D8D5E81F0A7B560030D29C /* XcodeEditorCPPWrapper.mm */, + 29C306401F0A3B1A00A5C308 /* Analyze */, 293BFD221DC10A55003FAEF0 /* main.cpp */, ); path = XcodeZombieCodeAnalyzer; sourceTree = ""; }; + 29C306361F0A3A6D00A5C308 /* Frameworks */ = { + isa = PBXGroup; + children = ( + 29D8D5B51F0A76BD0030D29C /* libncurses.tbd */, + 29C306371F0A3A6E00A5C308 /* libz.tbd */, + ); + name = Frameworks; + sourceTree = ""; + }; + 29C306401F0A3B1A00A5C308 /* Analyze */ = { + isa = PBXGroup; + children = ( + 29C306411F0A3B1A00A5C308 /* json.hpp */, + 29C306421F0A3B1A00A5C308 /* ZombieCodeUtil.cpp */, + 29C306431F0A3B1A00A5C308 /* ClangZombieCodePlugin.cpp */, + 29C306441F0A3B1A00A5C308 /* ZombieCodeUtil.hpp */, + 29C306451F0A3B1A00A5C308 /* ClangZombieCodePlugin.hpp */, + ); + path = Analyze; + sourceTree = ""; + }; + 29D8D5B81F0A7B560030D29C /* Vendor */ = { + isa = PBXGroup; + children = ( + 29D8D5B91F0A7B560030D29C /* XcodeEditor */, + ); + path = Vendor; + sourceTree = ""; + }; + 29D8D5B91F0A7B560030D29C /* XcodeEditor */ = { + isa = PBXGroup; + children = ( + 29D8D5BA1F0A7B560030D29C /* XCXibDefinition.h */, + 29D8D5BB1F0A7B560030D29C /* XcodeGroupMember.h */, + 29D8D5BC1F0A7B560030D29C /* XCFileOperationQueue.h */, + 29D8D5BD1F0A7B560030D29C /* XCFrameworkDefinition.h */, + 29D8D5BE1F0A7B560030D29C /* XCVersionGroup.m */, + 29D8D5BF1F0A7B560030D29C /* XCBuildShellScript.h */, + 29D8D5C01F0A7B560030D29C /* XcodeSourceTreeType.m */, + 29D8D5C11F0A7B560030D29C /* XCClassDefinition.h */, + 29D8D5C21F0A7B560030D29C /* XCSourceFileDefinition.m */, + 29D8D5C31F0A7B560030D29C /* XCSubProjectDefinition.m */, + 29D8D5C41F0A7B560030D29C /* XCProjectBuildConfig.m */, + 29D8D5C51F0A7B560030D29C /* XCProject.h */, + 29D8D5C61F0A7B560030D29C /* XCAbstractDefinition.h */, + 29D8D5C71F0A7B560030D29C /* XCTarget.m */, + 29D8D5C81F0A7B560030D29C /* XCGroup.h */, + 29D8D5C91F0A7B560030D29C /* Utils */, + 29D8D5CE1F0A7B560030D29C /* XCProject+SubProject.h */, + 29D8D5CF1F0A7B560030D29C /* XcodeSourceFileType.m */, + 29D8D5D01F0A7B560030D29C /* XCBuildShellScriptDefinition.h */, + 29D8D5D11F0A7B560030D29C /* XCSourceFile.h */, + 29D8D5D21F0A7B560030D29C /* XcodeEditor.h */, + 29D8D5D31F0A7B560030D29C /* XcodeMemberType.h */, + 29D8D5D41F0A7B560030D29C /* XCXibDefinition.m */, + 29D8D5D51F0A7B560030D29C /* XCBuildFile.h */, + 29D8D5D61F0A7B560030D29C /* XCBuildShellScript.m */, + 29D8D5D71F0A7B560030D29C /* XCVersionGroup.h */, + 29D8D5D81F0A7B560030D29C /* XCFrameworkDefinition.m */, + 29D8D5D91F0A7B560030D29C /* XCFileOperationQueue.m */, + 29D8D5DA1F0A7B560030D29C /* XCAbstractDefinition.m */, + 29D8D5DB1F0A7B560030D29C /* XCProject.m */, + 29D8D5DC1F0A7B560030D29C /* XCProjectBuildConfig.h */, + 29D8D5DD1F0A7B560030D29C /* XCSubProjectDefinition.h */, + 29D8D5DE1F0A7B560030D29C /* XCSourceFileDefinition.h */, + 29D8D5DF1F0A7B560030D29C /* XCClassDefinition.m */, + 29D8D5E01F0A7B560030D29C /* XcodeSourceTreeType.h */, + 29D8D5E11F0A7B560030D29C /* XcodeMemberType.m */, + 29D8D5E21F0A7B560030D29C /* XCSourceFile.m */, + 29D8D5E31F0A7B560030D29C /* XcodeSourceFileType.h */, + 29D8D5E41F0A7B560030D29C /* XCBuildShellScriptDefinition.m */, + 29D8D5E51F0A7B560030D29C /* XCProject+SubProject.m */, + 29D8D5E61F0A7B560030D29C /* XCGroup.m */, + 29D8D5E71F0A7B560030D29C /* XCTarget.h */, + ); + path = XcodeEditor; + sourceTree = ""; + }; + 29D8D5C91F0A7B560030D29C /* Utils */ = { + isa = PBXGroup; + children = ( + 29D8D5CA1F0A7B560030D29C /* NSString+RemoveEmoji.m */, + 29D8D5CB1F0A7B560030D29C /* XCKeyBuilder.h */, + 29D8D5CC1F0A7B560030D29C /* XCKeyBuilder.m */, + 29D8D5CD1F0A7B560030D29C /* NSString+RemoveEmoji.h */, + ); + path = Utils; + sourceTree = ""; + }; /* End PBXGroup section */ /* Begin PBXNativeTarget section */ @@ -124,7 +299,34 @@ isa = PBXSourcesBuildPhase; buildActionMask = 2147483647; files = ( - 293BFD231DC10A55003FAEF0 /* main.cpp in Sources */, + 29D8D5E91F0A7B7C0030D29C /* XCVersionGroup.m in Sources */, + 29D8D5EA1F0A7B7C0030D29C /* XcodeSourceTreeType.m in Sources */, + 29D8D5EB1F0A7B7C0030D29C /* XCSourceFileDefinition.m in Sources */, + 29D8D5EC1F0A7B7C0030D29C /* XCSubProjectDefinition.m in Sources */, + 29D8D5ED1F0A7B7C0030D29C /* XCProjectBuildConfig.m in Sources */, + 29D8D5EE1F0A7B7C0030D29C /* XCTarget.m in Sources */, + 29D8D5EF1F0A7B7C0030D29C /* NSString+RemoveEmoji.m in Sources */, + 29D8D5F01F0A7B7C0030D29C /* XCKeyBuilder.m in Sources */, + 29D8D5F11F0A7B7C0030D29C /* XcodeSourceFileType.m in Sources */, + 29D8D5F21F0A7B7C0030D29C /* XCXibDefinition.m in Sources */, + 29D8D5F31F0A7B7C0030D29C /* XCBuildShellScript.m in Sources */, + 29D8D5F41F0A7B7C0030D29C /* XCFrameworkDefinition.m in Sources */, + 29D8D5F51F0A7B7C0030D29C /* XCFileOperationQueue.m in Sources */, + 29D8D5F61F0A7B7C0030D29C /* XCAbstractDefinition.m in Sources */, + 29D8D5F71F0A7B7C0030D29C /* XCProject.m in Sources */, + 29D8D5F81F0A7B7C0030D29C /* XCClassDefinition.m in Sources */, + 29D8D5F91F0A7B7D0030D29C /* XcodeMemberType.m in Sources */, + 29D8D5FA1F0A7B7D0030D29C /* XCSourceFile.m in Sources */, + 29D8D5FB1F0A7B7D0030D29C /* XCBuildShellScriptDefinition.m in Sources */, + 29D8D5FC1F0A7B7D0030D29C /* XCProject+SubProject.m in Sources */, + 29D8D5FD1F0A7B7D0030D29C /* XCGroup.m in Sources */, + 29D8D5FE1F0A7B7D0030D29C /* XcodeEditorCPPWrapper.mm in Sources */, + 29D8D5AF1F0A5FAB0030D29C /* json.hpp in Sources */, + 29D8D5B01F0A5FAB0030D29C /* ZombieCodeUtil.cpp in Sources */, + 29D8D5B11F0A5FAB0030D29C /* ClangZombieCodePlugin.cpp in Sources */, + 29D8D5B21F0A5FAB0030D29C /* ZombieCodeUtil.hpp in Sources */, + 29D8D5B31F0A5FAB0030D29C /* ClangZombieCodePlugin.hpp in Sources */, + 29D8D5B41F0A5FAB0030D29C /* main.cpp in Sources */, ); runOnlyForDeploymentPostprocessing = 0; }; @@ -154,7 +356,7 @@ CLANG_WARN__DUPLICATE_METHOD_MATCH = YES; CODE_SIGN_IDENTITY = "-"; COPY_PHASE_STRIP = NO; - DEBUG_INFORMATION_FORMAT = dwarf; + DEBUG_INFORMATION_FORMAT = "dwarf-with-dsym"; ENABLE_STRICT_OBJC_MSGSEND = YES; ENABLE_TESTABILITY = YES; GCC_C_LANGUAGE_STANDARD = gnu99; @@ -171,7 +373,7 @@ GCC_WARN_UNINITIALIZED_AUTOS = YES_AGGRESSIVE; GCC_WARN_UNUSED_FUNCTION = YES; GCC_WARN_UNUSED_VARIABLE = YES; - MACOSX_DEPLOYMENT_TARGET = 10.12; + MACOSX_DEPLOYMENT_TARGET = 10.13; MTL_ENABLE_DEBUG_INFO = YES; ONLY_ACTIVE_ARCH = YES; SDKROOT = macosx; @@ -204,6 +406,7 @@ DEBUG_INFORMATION_FORMAT = "dwarf-with-dsym"; ENABLE_NS_ASSERTIONS = NO; ENABLE_STRICT_OBJC_MSGSEND = YES; + ENABLE_TESTABILITY = YES; GCC_C_LANGUAGE_STANDARD = gnu99; GCC_NO_COMMON_BLOCKS = YES; GCC_WARN_64_TO_32_BIT_CONVERSION = YES; @@ -212,8 +415,9 @@ GCC_WARN_UNINITIALIZED_AUTOS = YES_AGGRESSIVE; GCC_WARN_UNUSED_FUNCTION = YES; GCC_WARN_UNUSED_VARIABLE = YES; - MACOSX_DEPLOYMENT_TARGET = 10.12; + MACOSX_DEPLOYMENT_TARGET = 10.13; MTL_ENABLE_DEBUG_INFO = NO; + ONLY_ACTIVE_ARCH = YES; SDKROOT = macosx; }; name = Release; @@ -221,18 +425,100 @@ 293BFD1E1DC10854003FAEF0 /* Debug */ = { isa = XCBuildConfiguration; buildSettings = { + ARCHS = "$(ARCHS_STANDARD)"; + CLANG_CXX_LANGUAGE_STANDARD = "c++17"; + CODE_SIGN_IDENTITY = "Mac Developer"; + CODE_SIGN_STYLE = Automatic; CONFIGURATION_BUILD_DIR = $SRCROOT/../Analyzer; - DEVELOPMENT_TEAM = VP3AM7E5ZL; + DEBUG_INFORMATION_FORMAT = dwarf; + DEVELOPMENT_TEAM = ""; + HEADER_SEARCH_PATHS = ( + /opt/llvm/llvm/include, + /opt/llvm/llvm/tools/clang/include, + /opt/llvm/llvm_build/include, + /opt/llvm/llvm_build/tools/clang/include, + ); + LIBRARY_SEARCH_PATHS = /opt/llvm/llvm_build/lib; + MACOSX_DEPLOYMENT_TARGET = 10.13; + ONLY_ACTIVE_ARCH = YES; + OTHER_LDFLAGS = ( + "-lclangTooling", + "-lLLVMSupport", + "-lLLVMDemangle", + "-lclangFrontend", + "-lclangSema", + "-lclangAST", + "-lclangBasic", + "-lclangSerialization", + "-lLLVMIRReader", + "-lclangLex", + "-lclangEdit", + "-lclangAnalysis", + "-lclangDriver", + "-lLLVMBitReader", + "-lLLVMOption", + "-lLLVMMC", + "-lLLVMProfileData", + "-lLLVMMIRParser", + "-lLLVMCore", + "-lclangParse", + "-lLLVMMCParser", + "-lLLVMBinaryFormat", + ); PRODUCT_NAME = "$(TARGET_NAME)"; + PROVISIONING_PROFILE_SPECIFIER = ""; + SDKROOT = macosx10.13; + VALID_ARCHS = x86_64; }; name = Debug; }; 293BFD1F1DC10854003FAEF0 /* Release */ = { isa = XCBuildConfiguration; buildSettings = { + ARCHS = "$(ARCHS_STANDARD)"; + CLANG_CXX_LANGUAGE_STANDARD = "c++17"; + CODE_SIGN_IDENTITY = "Mac Developer"; + CODE_SIGN_STYLE = Automatic; CONFIGURATION_BUILD_DIR = $SRCROOT/../Analyzer; - DEVELOPMENT_TEAM = VP3AM7E5ZL; + DEBUG_INFORMATION_FORMAT = dwarf; + DEVELOPMENT_TEAM = ""; + HEADER_SEARCH_PATHS = ( + /opt/llvm/llvm/include, + /opt/llvm/llvm/tools/clang/include, + /opt/llvm/llvm_build/include, + /opt/llvm/llvm_build/tools/clang/include, + ); + LIBRARY_SEARCH_PATHS = /opt/llvm/llvm_build/lib; + MACOSX_DEPLOYMENT_TARGET = 10.13; + ONLY_ACTIVE_ARCH = YES; + OTHER_LDFLAGS = ( + "-lclangTooling", + "-lLLVMSupport", + "-lLLVMDemangle", + "-lclangFrontend", + "-lclangSema", + "-lclangAST", + "-lclangBasic", + "-lclangSerialization", + "-lLLVMIRReader", + "-lclangLex", + "-lclangEdit", + "-lclangAnalysis", + "-lclangDriver", + "-lLLVMBitReader", + "-lLLVMOption", + "-lLLVMMC", + "-lLLVMProfileData", + "-lLLVMMIRParser", + "-lLLVMCore", + "-lclangParse", + "-lLLVMMCParser", + "-lLLVMBinaryFormat", + ); PRODUCT_NAME = "$(TARGET_NAME)"; + PROVISIONING_PROFILE_SPECIFIER = ""; + SDKROOT = macosx10.13; + VALID_ARCHS = x86_64; }; name = Release; }; @@ -246,7 +532,7 @@ 293BFD1C1DC10854003FAEF0 /* Release */, ); defaultConfigurationIsVisible = 0; - defaultConfigurationName = Release; + defaultConfigurationName = Debug; }; 293BFD1D1DC10854003FAEF0 /* Build configuration list for PBXNativeTarget "XcodeZombieCodeAnalyzer" */ = { isa = XCConfigurationList; @@ -255,7 +541,7 @@ 293BFD1F1DC10854003FAEF0 /* Release */, ); defaultConfigurationIsVisible = 0; - defaultConfigurationName = Release; + defaultConfigurationName = Debug; }; /* End XCConfigurationList section */ }; diff --git a/XcodeZombieCodeAnalyzer/XcodeZombieCodeAnalyzer.xcodeproj/xcuserdata/kylewong.xcuserdatad/xcschemes/XcodeZombieCodeAnalyzer.xcscheme b/XcodeZombieCodeAnalyzer/XcodeZombieCodeAnalyzer.xcodeproj/xcuserdata/kylewong.xcuserdatad/xcschemes/XcodeZombieCodeAnalyzer.xcscheme index 807e030..1ba728b 100644 --- a/XcodeZombieCodeAnalyzer/XcodeZombieCodeAnalyzer.xcodeproj/xcuserdata/kylewong.xcuserdatad/xcschemes/XcodeZombieCodeAnalyzer.xcscheme +++ b/XcodeZombieCodeAnalyzer/XcodeZombieCodeAnalyzer.xcodeproj/xcuserdata/kylewong.xcuserdatad/xcschemes/XcodeZombieCodeAnalyzer.xcscheme @@ -1,7 +1,7 @@ + version = "1.7"> @@ -51,6 +51,42 @@ debugDocumentVersioning = "YES" debugServiceExtension = "internal" allowLocationSimulation = "YES"> + + + + + + + + + + + + + + + + + + + + diff --git a/ClangZombieCodePlugin/ClangZombieCodePlugin/ClangZombieCodePlugin.cpp b/XcodeZombieCodeAnalyzer/XcodeZombieCodeAnalyzer/Analyze/ClangZombieCodePlugin.cpp similarity index 99% rename from ClangZombieCodePlugin/ClangZombieCodePlugin/ClangZombieCodePlugin.cpp rename to XcodeZombieCodeAnalyzer/XcodeZombieCodeAnalyzer/Analyze/ClangZombieCodePlugin.cpp index 4aca260..6530663 100644 --- a/ClangZombieCodePlugin/ClangZombieCodePlugin/ClangZombieCodePlugin.cpp +++ b/XcodeZombieCodeAnalyzer/XcodeZombieCodeAnalyzer/Analyze/ClangZombieCodePlugin.cpp @@ -1,6 +1,6 @@ /* * ClangZombieCodePlugin.cpp - * ClangZombieCodePlugin + * ClangZombieCodeDemo * * Created by KyleWong on 26/10/2016. * Copyright © 2016 KyleWong. All rights reserved. @@ -9,7 +9,6 @@ #include #include "ClangZombieCodePlugin.hpp" -#include "ClangZombieCodePluginPriv.hpp" namespace ZombieCode { @@ -314,7 +313,7 @@ namespace ZombieCode string s0; raw_string_ostream param0(s0); objcExpr->getArg(0)->printPretty(param0, 0, Policy); - string classExpr = trim(param0.str()); + string classExpr = trim_copy(param0.str()); remove_blank(classExpr); if(has_suffix(classExpr,"class]")){ classExpr = classExpr.substr(1,classExpr.length()-string("class]").length()-1); @@ -497,6 +496,3 @@ namespace ZombieCode return true; } } - -static clang::FrontendPluginRegistry::Add -X("ClangZombieCodePlugin", "ClangZombieCodePlugin"); diff --git a/ClangZombieCodePlugin/ClangZombieCodePlugin/ClangZombieCodePlugin.hpp b/XcodeZombieCodeAnalyzer/XcodeZombieCodeAnalyzer/Analyze/ClangZombieCodePlugin.hpp similarity index 99% rename from ClangZombieCodePlugin/ClangZombieCodePlugin/ClangZombieCodePlugin.hpp rename to XcodeZombieCodeAnalyzer/XcodeZombieCodeAnalyzer/Analyze/ClangZombieCodePlugin.hpp index 115c853..10038f6 100644 --- a/ClangZombieCodePlugin/ClangZombieCodePlugin/ClangZombieCodePlugin.hpp +++ b/XcodeZombieCodeAnalyzer/XcodeZombieCodeAnalyzer/Analyze/ClangZombieCodePlugin.hpp @@ -1,6 +1,6 @@ /* * ClangZombieCodePlugin.hpp - * ClangZombieCodePlugin + * ClangZombieCodeDemo * * Created by KyleWong on 26/10/2016. * Copyright © 2016 KyleWong. All rights reserved. diff --git a/ClangZombieCodePlugin/ClangZombieCodePlugin/ZombieCodeUtil.cpp b/XcodeZombieCodeAnalyzer/XcodeZombieCodeAnalyzer/Analyze/ZombieCodeUtil.cpp similarity index 99% rename from ClangZombieCodePlugin/ClangZombieCodePlugin/ZombieCodeUtil.cpp rename to XcodeZombieCodeAnalyzer/XcodeZombieCodeAnalyzer/Analyze/ZombieCodeUtil.cpp index ec1191d..0fdb361 100644 --- a/ClangZombieCodePlugin/ClangZombieCodePlugin/ZombieCodeUtil.cpp +++ b/XcodeZombieCodeAnalyzer/XcodeZombieCodeAnalyzer/Analyze/ZombieCodeUtil.cpp @@ -1,6 +1,6 @@ // // ZombieCodeUtil.cpp -// ClangZombieCodePlugin +// ClangZombieCodeDemo // // Created by KyleWong on 11/10/2016. // diff --git a/ClangZombieCodePlugin/ClangZombieCodePlugin/ZombieCodeUtil.hpp b/XcodeZombieCodeAnalyzer/XcodeZombieCodeAnalyzer/Analyze/ZombieCodeUtil.hpp similarity index 75% rename from ClangZombieCodePlugin/ClangZombieCodePlugin/ZombieCodeUtil.hpp rename to XcodeZombieCodeAnalyzer/XcodeZombieCodeAnalyzer/Analyze/ZombieCodeUtil.hpp index c82f72d..873a694 100644 --- a/ClangZombieCodePlugin/ClangZombieCodePlugin/ZombieCodeUtil.hpp +++ b/XcodeZombieCodeAnalyzer/XcodeZombieCodeAnalyzer/Analyze/ZombieCodeUtil.hpp @@ -1,6 +1,6 @@ // // ZombieCodeUtil.hpp -// ClangZombieCodePlugin +// ClangZombieCodeDemo // // Created by KyleWong on 14/10/2016. // @@ -9,12 +9,13 @@ #ifndef ZombieCodeUtil_hpp #define ZombieCodeUtil_hpp #include -#include +#include #include #include #include #include #include +#include #include "json.hpp" #define kAppMainEntryClass "UIApplication" @@ -46,22 +47,46 @@ static inline bool has_suffix(const std::string &str, const std::string &suffix) str.compare(str.size() - suffix.size(), suffix.size(), suffix) == 0; } -static inline string <rim(std::string &s) { - s.erase(s.begin(), find_if(s.begin(), s.end(), - not1(ptr_fun(isspace)))); +static inline void ltrim(std::string &s) { + s.erase(s.begin(), std::find_if(s.begin(), s.end(), [](int ch) { + return !std::isspace(ch); + })); +} + +// trim from end (in place) +static inline void rtrim(std::string &s) { + s.erase(std::find_if(s.rbegin(), s.rend(), [](int ch) { + return !std::isspace(ch); + }).base(), s.end()); +} + +// trim from both ends (in place) +static inline void trim(std::string &s) { + ltrim(s); + rtrim(s); +} + +// trim from start (copying) +static inline std::string ltrim_copy(std::string s) { + ltrim(s); + return s; +} + +// trim from end (copying) +static inline std::string rtrim_copy(std::string s) { + rtrim(s); return s; } -// trim from end -static inline string &rtrim(string &s) { - s.erase(find_if(s.rbegin(), s.rend(), - not1(ptr_fun(isspace))).base(), s.end()); +// trim from both ends (copying) +static inline std::string trim_copy(std::string s) { + trim(s); return s; } -// trim from both ends -static inline string &trim(string &s) { - return ltrim(rtrim(s)); +static bool file_exists(const string& name) { + struct stat buffer; + return (stat (name.c_str(), &buffer) == 0); } static inline string absolutePathFromRelative(string relativePath){ diff --git a/ClangZombieCodePlugin/ClangZombieCodePlugin/json.hpp b/XcodeZombieCodeAnalyzer/XcodeZombieCodeAnalyzer/Analyze/json.hpp similarity index 100% rename from ClangZombieCodePlugin/ClangZombieCodePlugin/json.hpp rename to XcodeZombieCodeAnalyzer/XcodeZombieCodeAnalyzer/Analyze/json.hpp diff --git a/XcodeZombieCodeAnalyzer/XcodeZombieCodeAnalyzer/Vendor/XcodeEditor/Utils/NSString+RemoveEmoji.h b/XcodeZombieCodeAnalyzer/XcodeZombieCodeAnalyzer/Vendor/XcodeEditor/Utils/NSString+RemoveEmoji.h new file mode 100644 index 0000000..5fc0890 --- /dev/null +++ b/XcodeZombieCodeAnalyzer/XcodeZombieCodeAnalyzer/Vendor/XcodeEditor/Utils/NSString+RemoveEmoji.h @@ -0,0 +1,11 @@ +#import + +@interface NSString (RemoveEmoji) + +- (BOOL)isIncludingEmoji; + +- (instancetype)stringByRemovingEmoji; + +- (instancetype)removedEmojiString __attribute__((deprecated)); + +@end diff --git a/XcodeZombieCodeAnalyzer/XcodeZombieCodeAnalyzer/Vendor/XcodeEditor/Utils/NSString+RemoveEmoji.m b/XcodeZombieCodeAnalyzer/XcodeZombieCodeAnalyzer/Vendor/XcodeEditor/Utils/NSString+RemoveEmoji.m new file mode 100644 index 0000000..49859d6 --- /dev/null +++ b/XcodeZombieCodeAnalyzer/XcodeZombieCodeAnalyzer/Vendor/XcodeEditor/Utils/NSString+RemoveEmoji.m @@ -0,0 +1,70 @@ +#import "NSString+RemoveEmoji.h" + +static NSCharacterSet *VariationSelectors = nil; + +@implementation NSString (RemoveEmoji) + ++ (void)load { + VariationSelectors = + [NSCharacterSet characterSetWithRange:NSMakeRange(0xFE00, 16)]; +} + +- (BOOL)isEmoji { + if ([self rangeOfCharacterFromSet:VariationSelectors].location != + NSNotFound) { + return YES; + } + + const unichar high = [self characterAtIndex:0]; + + // Surrogate pair (U+1D000-1F9FF) + if (0xD800 <= high && high <= 0xDBFF) { + const unichar low = [self characterAtIndex:1]; + const int codepoint = ((high - 0xD800) * 0x400) + (low - 0xDC00) + 0x10000; + + return (0x1D000 <= codepoint && codepoint <= 0x1F9FF); + + // Not surrogate pair (U+2100-27BF) + } else { + return (0x2100 <= high && high <= 0x27BF); + } +} + +- (BOOL)isIncludingEmoji { + BOOL __block result = NO; + + [self + enumerateSubstringsInRange:NSMakeRange(0, [self length]) + options:NSStringEnumerationByComposedCharacterSequences + usingBlock:^(NSString *substring, NSRange substringRange, + NSRange enclosingRange, BOOL *stop) { + if ([substring isEmoji]) { + *stop = YES; + result = YES; + } + }]; + + return result; +} + +- (instancetype)stringByRemovingEmoji { + NSMutableString *__block buffer = + [NSMutableString stringWithCapacity:[self length]]; + + [self + enumerateSubstringsInRange:NSMakeRange(0, [self length]) + options:NSStringEnumerationByComposedCharacterSequences + usingBlock:^(NSString *substring, NSRange substringRange, + NSRange enclosingRange, BOOL *stop) { + [buffer appendString:([substring isEmoji]) ? @"" + : substring]; + }]; + + return buffer; +} + +- (instancetype)removedEmojiString { + return [self stringByRemovingEmoji]; +} + +@end diff --git a/XcodeZombieCodeAnalyzer/XcodeZombieCodeAnalyzer/Vendor/XcodeEditor/Utils/XCKeyBuilder.h b/XcodeZombieCodeAnalyzer/XcodeZombieCodeAnalyzer/Vendor/XcodeEditor/Utils/XCKeyBuilder.h new file mode 100644 index 0000000..a4f0bb8 --- /dev/null +++ b/XcodeZombieCodeAnalyzer/XcodeZombieCodeAnalyzer/Vendor/XcodeEditor/Utils/XCKeyBuilder.h @@ -0,0 +1,34 @@ +//////////////////////////////////////////////////////////////////////////////// +// +// JASPER BLUES +// Copyright 2012 - 2013 Jasper Blues +// All Rights Reserved. +// +// NOTICE: Jasper Blues permits you to use, modify, and distribute this file +// in accordance with the terms of the license agreement accompanying it. +// +//////////////////////////////////////////////////////////////////////////////// + +#import + +#define HASH_VALUE_STORAGE_SIZE 48 +#define MD5_DIGEST_LENGTH 16 + +typedef struct { + char value[MD5_DIGEST_LENGTH]; +} HashValueMD5Hash; + +@interface XCKeyBuilder : NSObject { + unsigned char _value[HASH_VALUE_STORAGE_SIZE]; +} + ++ (XCKeyBuilder *)forItemNamed:(NSString *)name; + ++ (XCKeyBuilder *)createUnique; + +- (id)initHashValueMD5HashWithBytes:(const void *)bytes + length:(NSUInteger)length; + +- (NSString *)build; + +@end diff --git a/XcodeZombieCodeAnalyzer/XcodeZombieCodeAnalyzer/Vendor/XcodeEditor/Utils/XCKeyBuilder.m b/XcodeZombieCodeAnalyzer/XcodeZombieCodeAnalyzer/Vendor/XcodeEditor/Utils/XCKeyBuilder.m new file mode 100644 index 0000000..ae9d0db --- /dev/null +++ b/XcodeZombieCodeAnalyzer/XcodeZombieCodeAnalyzer/Vendor/XcodeEditor/Utils/XCKeyBuilder.m @@ -0,0 +1,66 @@ +//////////////////////////////////////////////////////////////////////////////// +// +// JASPER BLUES +// Copyright 2012 - 2013 Jasper Blues +// All Rights Reserved. +// +// NOTICE: Jasper Blues permits you to use, modify, and distribute this file +// in accordance with the terms of the license agreement accompanying it. +// +//////////////////////////////////////////////////////////////////////////////// + +#import "XCKeyBuilder.h" +#import + +#if MD5_DIGEST_LENGTH != CC_MD5_DIGEST_LENGTH +#error Digest length in XCKeyBuilder.h (MD5_DIGEST_LENGTH) disagress with CommonCrypto value (CC_MD5_DIGEST_LENGTH) +#endif + +@implementation XCKeyBuilder + +//------------------------------------------------------------------------------------------- +#pragma mark - Class Methods +//------------------------------------------------------------------------------------------- + ++ (XCKeyBuilder *)forItemNamed:(NSString *)name { + return [self createUnique]; +} + ++ (XCKeyBuilder *)createUnique { + CFUUIDRef theUUID = CFUUIDCreate(NULL); + CFUUIDBytes bytes = CFUUIDGetUUIDBytes(theUUID); + CFRelease(theUUID); + + return [[XCKeyBuilder alloc] initHashValueMD5HashWithBytes:&bytes + length:sizeof(bytes)]; +} + +//------------------------------------------------------------------------------------------- +#pragma mark - Initialization & Destruction +//------------------------------------------------------------------------------------------- + +- (id)initHashValueMD5HashWithBytes:(const void *)bytes + length:(NSUInteger)length { + self = [super init]; + if (self != nil) { + CC_MD5(bytes, (int)length, _value); + } + return self; +} + +//------------------------------------------------------------------------------------------- +#pragma mark - Interface Methods +//------------------------------------------------------------------------------------------- + +- (NSString *)build { + NSInteger byteLength = sizeof(HashValueMD5Hash); + NSMutableString *stringValue = + [NSMutableString stringWithCapacity:byteLength * 2]; + NSInteger i; + for (i = 0; i < byteLength; i++) { + [stringValue appendFormat:@"%02x", _value[i]]; + } + return [[stringValue substringToIndex:24] uppercaseString]; +} + +@end diff --git a/XcodeZombieCodeAnalyzer/XcodeZombieCodeAnalyzer/Vendor/XcodeEditor/XCAbstractDefinition.h b/XcodeZombieCodeAnalyzer/XcodeZombieCodeAnalyzer/Vendor/XcodeEditor/XCAbstractDefinition.h new file mode 100644 index 0000000..3769a86 --- /dev/null +++ b/XcodeZombieCodeAnalyzer/XcodeZombieCodeAnalyzer/Vendor/XcodeEditor/XCAbstractDefinition.h @@ -0,0 +1,48 @@ +//////////////////////////////////////////////////////////////////////////////// +// +// JASPER BLUES +// Copyright 2012 - 2013 Jasper Blues +// All Rights Reserved. +// +// NOTICE: Jasper Blues permits you to use, modify, and distribute this file +// in accordance with the terms of the license agreement accompanying it. +// +//////////////////////////////////////////////////////////////////////////////// + +#import + +typedef enum { + + /** + * Creates the reference in the project and writes the contents to disk. If a + * file already exists at the specified + * location, its contents will be updated. + */ + XCFileOperationTypeOverwrite, + + /** + * Creates the reference in the project. If a file already exists at the + * specified location, the contents will not + * be updated. + */ + XCFileOperationTypeAcceptExisting, + + /** + * Creates the reference in the project, but does not write to disk. The + * filesystem is expected to be updated + * through some other means. + */ + XCFileOperationTypeReferenceOnly +} XCFileOperationType; + +/** + * Holds properties to all types of resource that can be added to an Xcode + * project. + */ +@interface XCAbstractDefinition : NSObject { + XCFileOperationType _fileOperationType; +} + +@property(nonatomic) XCFileOperationType fileOperationType; + +@end \ No newline at end of file diff --git a/XcodeZombieCodeAnalyzer/XcodeZombieCodeAnalyzer/Vendor/XcodeEditor/XCAbstractDefinition.m b/XcodeZombieCodeAnalyzer/XcodeZombieCodeAnalyzer/Vendor/XcodeEditor/XCAbstractDefinition.m new file mode 100644 index 0000000..6e72c5c --- /dev/null +++ b/XcodeZombieCodeAnalyzer/XcodeZombieCodeAnalyzer/Vendor/XcodeEditor/XCAbstractDefinition.m @@ -0,0 +1,30 @@ +//////////////////////////////////////////////////////////////////////////////// +// +// JASPER BLUES +// Copyright 2012 - 2013 Jasper Blues +// All Rights Reserved. +// +// NOTICE: Jasper Blues permits you to use, modify, and distribute this file +// in accordance with the terms of the license agreement accompanying it. +// +//////////////////////////////////////////////////////////////////////////////// + +#import "XCAbstractDefinition.h" + +@implementation XCAbstractDefinition + +@synthesize fileOperationType = _fileOperationType; + +/* ====================================================================================================================================== + */ +#pragma mark - Initialization & Destruction + +- (id)init { + self = [super init]; + if (self) { + _fileOperationType = XCFileOperationTypeOverwrite; + } + return self; +} + +@end \ No newline at end of file diff --git a/XcodeZombieCodeAnalyzer/XcodeZombieCodeAnalyzer/Vendor/XcodeEditor/XCBuildFile.h b/XcodeZombieCodeAnalyzer/XcodeZombieCodeAnalyzer/Vendor/XcodeEditor/XCBuildFile.h new file mode 100644 index 0000000..5716202 --- /dev/null +++ b/XcodeZombieCodeAnalyzer/XcodeZombieCodeAnalyzer/Vendor/XcodeEditor/XCBuildFile.h @@ -0,0 +1,18 @@ +// +// XCBuildFile.h +// XcodeEditor +// +// Created by joel on 01/02/16. +// Copyright © 2016 appsquickly. All rights reserved. +// + +#import "XcodeMemberType.h" +#import + +@protocol XCBuildFile + +- (void)becomeBuildFile; +- (XcodeMemberType)buildPhase; +- (NSString *)buildFileKey; +- (BOOL)isBuildFile; +@end diff --git a/XcodeZombieCodeAnalyzer/XcodeZombieCodeAnalyzer/Vendor/XcodeEditor/XCBuildShellScript.h b/XcodeZombieCodeAnalyzer/XcodeZombieCodeAnalyzer/Vendor/XcodeEditor/XCBuildShellScript.h new file mode 100644 index 0000000..48326ca --- /dev/null +++ b/XcodeZombieCodeAnalyzer/XcodeZombieCodeAnalyzer/Vendor/XcodeEditor/XCBuildShellScript.h @@ -0,0 +1,69 @@ +// +// XCBuildShellScript.h +// xcode-editor +// +// Created by joel on 03/02/16. +// +// + +#import "XcodeGroupMember.h" +#import "XcodeSourceFileType.h" +#import + +@class XCProject; + +@interface XCBuildShellScript : NSObject { + NSString *_key; + +@private + XCProject *_project; + BOOL _runOnlyForDeploymentPostprocessing; + NSArray *_files; + NSArray *_inputPaths; + NSArray *_outputPaths; + NSString *_name; + NSString *_shellPath; + NSString *_shellScript; +} + +@property(nonatomic, strong, readonly, nonnull) NSString *key; +@property(nonatomic, strong, readonly, nonnull) NSString *name; +@property(nonatomic, readonly) BOOL runOnlyForDeploymentPostprocessing; +@property(nonatomic, nonnull, strong, readonly) NSString *shellScript; +@property(nonatomic, nonnull, strong, readonly) NSString *shellPath; +@property(nonatomic, nonnull, strong, readonly) NSArray *files; +@property(nonatomic, nonnull, strong, readonly) NSArray *inputPaths; +@property(nonatomic, nonnull, strong, readonly) + NSArray *outputPaths; + +//------------------------------------------------------------------------------------------- +#pragma mark - Initialization & Destruction +//------------------------------------------------------------------------------------------- + ++ (XCBuildShellScript *_Nonnull) + shellScriptWithProject:(XCProject *_Nonnull)project + key:(NSString *_Nonnull)key + name:(NSString *_Nullable)name + files:(NSArray *_Nullable)files + inputPaths: + (NSArray *_Nullable)inputPaths + outputPaths: + (NSArray *_Nullable)outputPaths + runOnlyForDeploymentPostprocessing:(BOOL)runOnlyForDeploymentPostprocessing + shellPath:(NSString *_Nullable)shellPath + shellScript:(NSString *_Nonnull)shellScript; + +- (instancetype _Nonnull)initWithProject:(XCProject *_Nonnull)project + key:(NSString *_Nonnull)key + name:(NSString *_Nullable)name + files:(NSArray *_Nullable)files + inputPaths: + (NSArray *_Nullable)inputPaths + outputPaths: + (NSArray *_Nullable)outputPaths + runOnlyForDeploymentPostprocessing: + (BOOL)runOnlyForDeploymentPostprocessing + shellPath:(NSString *_Nullable)shellPath + shellScript:(NSString *_Nonnull)shellScript; + +@end diff --git a/XcodeZombieCodeAnalyzer/XcodeZombieCodeAnalyzer/Vendor/XcodeEditor/XCBuildShellScript.m b/XcodeZombieCodeAnalyzer/XcodeZombieCodeAnalyzer/Vendor/XcodeEditor/XCBuildShellScript.m new file mode 100644 index 0000000..a3e233e --- /dev/null +++ b/XcodeZombieCodeAnalyzer/XcodeZombieCodeAnalyzer/Vendor/XcodeEditor/XCBuildShellScript.m @@ -0,0 +1,73 @@ +// +// XCBuildShellScript.m +// xcode-editor +// +// Created by joel on 03/02/16. +// +// + +#import "XCBuildShellScript.h" + +#import "NSString+RemoveEmoji.h" + +@implementation XCBuildShellScript + +//------------------------------------------------------------------------------------------- +#pragma mark - Class Methods +//------------------------------------------------------------------------------------------- + ++ (XCBuildShellScript *_Nonnull) + shellScriptWithProject:(XCProject *)project + key:(NSString *)key + name:(NSString *)name + files:(NSArray *)files + inputPaths:(NSArray *)inputPaths + outputPaths:(NSArray *)outputPaths + runOnlyForDeploymentPostprocessing:(BOOL)runOnlyForDeploymentPostprocessing + shellPath:(NSString *)shellPath + shellScript:(NSString *)shellScript { + return [[XCBuildShellScript alloc] + initWithProject:project + key:key + name:name + files:files + inputPaths:inputPaths + outputPaths:outputPaths + runOnlyForDeploymentPostprocessing:runOnlyForDeploymentPostprocessing + shellPath:shellPath + shellScript:shellScript]; +} + +//------------------------------------------------------------------------------------------- +#pragma mark - Initialization & Destruction +//------------------------------------------------------------------------------------------- + +- (instancetype _Nonnull)initWithProject:(XCProject *)project + key:(NSString *)key + name:(NSString *)name + files:(NSArray *)files + inputPaths:(NSArray *)inputPaths + outputPaths:(NSArray *)outputPaths + runOnlyForDeploymentPostprocessing: + (BOOL)runOnlyForDeploymentPostprocessing + shellPath:(NSString *)shellPath + shellScript:(NSString *)shellScript { + self = [super init]; + if (self) { + + _project = project; + _key = key; + _name = [name stringByRemovingEmoji]; + + _files = files != nil ? files : @[]; + _inputPaths = inputPaths != nil ? inputPaths : @[]; + _outputPaths = outputPaths != nil ? outputPaths : @[]; + + _runOnlyForDeploymentPostprocessing = runOnlyForDeploymentPostprocessing; + _shellPath = _shellPath != nil ? _shellPath : @"/bin/sh"; + _shellScript = shellScript; + } + return self; +} + +@end diff --git a/XcodeZombieCodeAnalyzer/XcodeZombieCodeAnalyzer/Vendor/XcodeEditor/XCBuildShellScriptDefinition.h b/XcodeZombieCodeAnalyzer/XcodeZombieCodeAnalyzer/Vendor/XcodeEditor/XCBuildShellScriptDefinition.h new file mode 100644 index 0000000..44b4598 --- /dev/null +++ b/XcodeZombieCodeAnalyzer/XcodeZombieCodeAnalyzer/Vendor/XcodeEditor/XCBuildShellScriptDefinition.h @@ -0,0 +1,60 @@ +// +// XCBuildShellScriptDefinition.h +// xcode-editor +// +// Created by joel on 03/02/16. +// +// + +#import "XCAbstractDefinition.h" +#import + +@interface XCBuildShellScriptDefinition : XCAbstractDefinition { + NSString *_key; + +@private + BOOL _runOnlyForDeploymentPostprocessing; + NSArray *_files; + NSArray *_inputPaths; + NSArray *_outputPaths; + NSString *_name; + NSString *_shellPath; + NSString *_shellScript; +} + +@property(nonatomic, strong, readonly, nonnull) NSString *key; +@property(nonatomic, strong, readonly, nonnull) NSString *name; +@property(nonatomic, readonly) BOOL runOnlyForDeploymentPostprocessing; +@property(nonatomic, nonnull, strong, readonly) NSString *shellScript; +@property(nonatomic, nonnull, strong, readonly) NSString *shellPath; +@property(nonatomic, nonnull, strong, readonly) NSArray *files; +@property(nonatomic, nonnull, strong, readonly) NSArray *inputPaths; +@property(nonatomic, nonnull, strong, readonly) + NSArray *outputPaths; + +//------------------------------------------------------------------------------------------- +#pragma mark - Initialization & Destruction +//------------------------------------------------------------------------------------------- + ++ (XCBuildShellScriptDefinition *_Nonnull) + shellScriptDefinitionWithName:(NSString *_Nullable)name + files:(NSArray *_Nullable)files + inputPaths: + (NSArray *_Nullable)inputPaths + outputPaths: + (NSArray *_Nullable)outputPaths + runOnlyForDeploymentPostprocessing:(BOOL)runOnlyForDeploymentPostprocessing + shellPath:(NSString *_Nullable)shellPath + shellScript:(NSString *_Nonnull)shellScript; + +- (instancetype _Nonnull)initWithName:(NSString *_Nullable)name + files:(NSArray *_Nullable)files + inputPaths: + (NSArray *_Nullable)inputPaths + outputPaths: + (NSArray *_Nullable)outputPaths + runOnlyForDeploymentPostprocessing:(BOOL)runOnlyForDeploymentPostprocessing + shellPath:(NSString *_Nullable)shellPath + shellScript:(NSString *_Nonnull)shellScript; + +@end diff --git a/XcodeZombieCodeAnalyzer/XcodeZombieCodeAnalyzer/Vendor/XcodeEditor/XCBuildShellScriptDefinition.m b/XcodeZombieCodeAnalyzer/XcodeZombieCodeAnalyzer/Vendor/XcodeEditor/XCBuildShellScriptDefinition.m new file mode 100644 index 0000000..70a9b5e --- /dev/null +++ b/XcodeZombieCodeAnalyzer/XcodeZombieCodeAnalyzer/Vendor/XcodeEditor/XCBuildShellScriptDefinition.m @@ -0,0 +1,66 @@ +// +// XCBuildShellScriptDefinition.m +// xcode-editor +// +// Created by joel on 03/02/16. +// +// + +#import "XCBuildShellScriptDefinition.h" + +#import "NSString+RemoveEmoji.h" + +@implementation XCBuildShellScriptDefinition +//------------------------------------------------------------------------------------------- +#pragma mark - Class Methods +//------------------------------------------------------------------------------------------- + ++ (XCBuildShellScriptDefinition *_Nonnull) + shellScriptDefinitionWithName:(NSString *)name + files:(NSArray *_Nullable)files + inputPaths: + (NSArray *_Nullable)inputPaths + outputPaths: + (NSArray *_Nullable)outputPaths + runOnlyForDeploymentPostprocessing:(BOOL)runOnlyForDeploymentPostprocessing + shellPath:(NSString *_Nullable)shellPath + shellScript:(NSString *_Nonnull)shellScript { + return [[XCBuildShellScriptDefinition alloc] + initWithName:name + files:files + inputPaths:inputPaths + outputPaths:outputPaths + runOnlyForDeploymentPostprocessing:runOnlyForDeploymentPostprocessing + shellPath:shellPath + shellScript:shellScript]; +} + +//------------------------------------------------------------------------------------------- +#pragma mark - Initialization & Destruction +//------------------------------------------------------------------------------------------- + +- (instancetype _Nonnull)initWithName:(NSString *_Nullable)name + files:(NSArray *_Nullable)files + inputPaths: + (NSArray *_Nullable)inputPaths + outputPaths: + (NSArray *_Nullable)outputPaths + runOnlyForDeploymentPostprocessing:(BOOL)runOnlyForDeploymentPostprocessing + shellPath:(NSString *_Nullable)shellPath + shellScript:(NSString *_Nonnull)shellScript { + self = [super init]; + if (self) { + _name = [name stringByRemovingEmoji]; + + _files = files != nil ? files : @[]; + _inputPaths = inputPaths != nil ? inputPaths : @[]; + _outputPaths = outputPaths != nil ? outputPaths : @[]; + + _runOnlyForDeploymentPostprocessing = runOnlyForDeploymentPostprocessing; + _shellPath = _shellPath != nil ? _shellPath : @"/bin/sh"; + _shellScript = shellScript; + } + return self; +} + +@end diff --git a/XcodeZombieCodeAnalyzer/XcodeZombieCodeAnalyzer/Vendor/XcodeEditor/XCClassDefinition.h b/XcodeZombieCodeAnalyzer/XcodeZombieCodeAnalyzer/Vendor/XcodeEditor/XCClassDefinition.h new file mode 100644 index 0000000..a51ce92 --- /dev/null +++ b/XcodeZombieCodeAnalyzer/XcodeZombieCodeAnalyzer/Vendor/XcodeEditor/XCClassDefinition.h @@ -0,0 +1,62 @@ +//////////////////////////////////////////////////////////////////////////////// +// +// JASPER BLUES +// Copyright 2012 - 2013 Jasper Blues +// All Rights Reserved. +// +// NOTICE: Jasper Blues permits you to use, modify, and distribute this file +// in accordance with the terms of the license agreement accompanying it. +// +//////////////////////////////////////////////////////////////////////////////// + +#import "XCAbstractDefinition.h" +#import + +typedef enum { + ObjectiveC, + ObjectiveCPlusPlus, + CPlusPlus, +} ClassDefinitionLanguage; + +@interface XCClassDefinition : XCAbstractDefinition { + + NSString *_className; + NSString *_header; + NSString *_source; + +@private + ClassDefinitionLanguage _language; +} + +@property(strong, nonatomic, readonly) NSString *className; +@property(nonatomic, strong) NSString *header; +@property(nonatomic, strong) NSString *source; + ++ (XCClassDefinition *)classDefinitionWithName:(NSString *)fileName; + ++ (XCClassDefinition *)classDefinitionWithName:(NSString *)className + language: + (ClassDefinitionLanguage)language; + +/** + * Initializes a new objective-c class definition. + */ +- (id)initWithName:(NSString *)fileName; + +/** + * Initializes a new class definition with the specified language. + */ +- (id)initWithName:(NSString *)className + language:(ClassDefinitionLanguage)language; + +- (BOOL)isObjectiveC; + +- (BOOL)isObjectiveCPlusPlus; + +- (BOOL)isCPlusPlus; + +- (NSString *)headerFileName; + +- (NSString *)sourceFileName; + +@end diff --git a/XcodeZombieCodeAnalyzer/XcodeZombieCodeAnalyzer/Vendor/XcodeEditor/XCClassDefinition.m b/XcodeZombieCodeAnalyzer/XcodeZombieCodeAnalyzer/Vendor/XcodeEditor/XCClassDefinition.m new file mode 100644 index 0000000..c06647f --- /dev/null +++ b/XcodeZombieCodeAnalyzer/XcodeZombieCodeAnalyzer/Vendor/XcodeEditor/XCClassDefinition.m @@ -0,0 +1,90 @@ +//////////////////////////////////////////////////////////////////////////////// +// +// JASPER BLUES +// Copyright 2012 - 2013 Jasper Blues +// All Rights Reserved. +// +// NOTICE: Jasper Blues permits you to use, modify, and distribute this file +// in accordance with the terms of the license agreement accompanying it. +// +//////////////////////////////////////////////////////////////////////////////// + +#import "XCClassDefinition.h" + +@implementation XCClassDefinition + +@synthesize className = _className; +@synthesize header = _header; +@synthesize source = _source; + +/* ====================================================================================================================================== + */ +#pragma mark - Class Methods + ++ (XCClassDefinition *)classDefinitionWithName:(NSString *)fileName { + return [[XCClassDefinition alloc] initWithName:fileName]; +} + ++ (XCClassDefinition *)classDefinitionWithName:(NSString *)className + language: + (ClassDefinitionLanguage)language { + return [[XCClassDefinition alloc] initWithName:className language:language]; +} + +/* ====================================================================================================================================== + */ +#pragma mark - Initialization & Destruction + +- (id)initWithName:(NSString *)className { + return [self initWithName:className language:ObjectiveC]; +} + +- (id)initWithName:(NSString *)className + language:(ClassDefinitionLanguage)language { + self = [super init]; + if (self) { + _className = [className copy]; + if (!(language == ObjectiveC || language == ObjectiveCPlusPlus || + language == CPlusPlus)) { + [NSException + raise:NSInvalidArgumentException + format:@"Language must be one of ObjectiveC, ObjectiveCPlusPlus"]; + } + _language = language; + } + return self; +} + +/* ====================================================================================================================================== + */ +#pragma mark - Interface Methods + +- (BOOL)isObjectiveC { + return _language == ObjectiveC; +} + +- (BOOL)isObjectiveCPlusPlus { + return _language == ObjectiveCPlusPlus; +} + +- (BOOL)isCPlusPlus { + return _language == CPlusPlus; +} + +- (NSString *)headerFileName { + return [_className stringByAppendingString:@".h"]; +} + +- (NSString *)sourceFileName { + NSString *sourceFileName = nil; + if ([self isObjectiveC]) { + sourceFileName = [_className stringByAppendingString:@".m"]; + } else if ([self isObjectiveCPlusPlus]) { + sourceFileName = [_className stringByAppendingString:@".mm"]; + } else if ([self isCPlusPlus]) { + sourceFileName = [_className stringByAppendingString:@".cpp"]; + } + return sourceFileName; +} + +@end \ No newline at end of file diff --git a/XcodeZombieCodeAnalyzer/XcodeZombieCodeAnalyzer/Vendor/XcodeEditor/XCFileOperationQueue.h b/XcodeZombieCodeAnalyzer/XcodeZombieCodeAnalyzer/Vendor/XcodeEditor/XCFileOperationQueue.h new file mode 100644 index 0000000..baf066c --- /dev/null +++ b/XcodeZombieCodeAnalyzer/XcodeZombieCodeAnalyzer/Vendor/XcodeEditor/XCFileOperationQueue.h @@ -0,0 +1,47 @@ +//////////////////////////////////////////////////////////////////////////////// +// +// JASPER BLUES +// Copyright 2012 Jasper Blues +// All Rights Reserved. +// +// NOTICE: Jasper Blues permits you to use, modify, and distribute this file +// in accordance with the terms of the license agreement accompanying it. +// +//////////////////////////////////////////////////////////////////////////////// + +#import + +@interface XCFileOperationQueue : NSObject { + +@private + NSString *_baseDirectory; + NSMutableDictionary *_filesToWrite; + NSMutableDictionary *_frameworksToCopy; + NSMutableArray *_filesToDelete; + NSMutableArray *_directoriesToCreate; +} + +- (id)initWithBaseDirectory:(NSString *)baseDirectory; + +- (BOOL)fileWithName:(NSString *)name + existsInProjectDirectory:(NSString *)directory; + +- (void)queueTextFile:(NSString *)fileName + inDirectory:(NSString *)directory + withContents:(NSString *)contents; + +- (void)queueDataFile:(NSString *)fileName + inDirectory:(NSString *)directory + withContents:(NSData *)contents; + +- (void)queueFrameworkWithFilePath:(NSString *)filePath + inDirectory:(NSString *)directory; + +- (void)queueDeletion:(NSString *)filePath; + +- (void)queueDirectory:(NSString *)withName + inDirectory:(NSString *)parentDirectory; + +- (void)commitFileOperations; + +@end diff --git a/XcodeZombieCodeAnalyzer/XcodeZombieCodeAnalyzer/Vendor/XcodeEditor/XCFileOperationQueue.m b/XcodeZombieCodeAnalyzer/XcodeZombieCodeAnalyzer/Vendor/XcodeEditor/XCFileOperationQueue.m new file mode 100644 index 0000000..9e74db4 --- /dev/null +++ b/XcodeZombieCodeAnalyzer/XcodeZombieCodeAnalyzer/Vendor/XcodeEditor/XCFileOperationQueue.m @@ -0,0 +1,181 @@ +//////////////////////////////////////////////////////////////////////////////// +// +// JASPER BLUES +// Copyright 2012 Jasper Blues +// All Rights Reserved. +// +// NOTICE: Jasper Blues permits you to use, modify, and distribute this file +// in accordance with the terms of the license agreement accompanying it. +// +//////////////////////////////////////////////////////////////////////////////// + +#import "XCFileOperationQueue.h" + +@interface XCFileOperationQueue () + +- (NSString *)destinationPathFor:(NSString *)fileName + inProjectDirectory:(NSString *)directory; + +- (void)performFileWrites; + +- (void)performCopyFrameworks; + +- (void)performFileDeletions; + +- (void)performCreateDirectories; + +@end + +@implementation XCFileOperationQueue + +/* ====================================================================================================================================== + */ +#pragma mark - Initialization & Destruction + +- (id)initWithBaseDirectory:(NSString *)baseDirectory { + self = [super init]; + if (self) { + _baseDirectory = [baseDirectory copy]; + _filesToWrite = [[NSMutableDictionary alloc] init]; + _frameworksToCopy = [[NSMutableDictionary alloc] init]; + _filesToDelete = [[NSMutableArray alloc] init]; + _directoriesToCreate = [[NSMutableArray alloc] init]; + } + return self; +} + +/* ====================================================================================================================================== + */ +#pragma mark - Interface Methods + +- (BOOL)fileWithName:(NSString *)name + existsInProjectDirectory:(NSString *)directory { + NSString *filePath = [self destinationPathFor:name + inProjectDirectory:directory]; + return [[NSFileManager defaultManager] fileExistsAtPath:filePath]; +} + +- (void)queueTextFile:(NSString *)fileName + inDirectory:(NSString *)directory + withContents:(NSString *)contents { + [_filesToWrite setObject:[contents dataUsingEncoding:NSUTF8StringEncoding] + forKey:[self destinationPathFor:fileName + inProjectDirectory:directory]]; +} + +- (void)queueDataFile:(NSString *)fileName + inDirectory:(NSString *)directory + withContents:(NSData *)contents { + [_filesToWrite setObject:contents + forKey:[self destinationPathFor:fileName + inProjectDirectory:directory]]; +} + +- (void)queueFrameworkWithFilePath:(NSString *)filePath + inDirectory:(NSString *)directory { + + NSURL *sourceUrl = [NSURL fileURLWithPath:filePath isDirectory:YES]; + NSString *destinationPath = + [[_baseDirectory stringByAppendingPathComponent:directory] + stringByAppendingPathComponent:[filePath lastPathComponent]]; + NSURL *destinationUrl = [NSURL fileURLWithPath:destinationPath + isDirectory:YES]; + [_frameworksToCopy setObject:sourceUrl forKey:destinationUrl]; +} + +- (void)queueDeletion:(NSString *)filePath { + NSLog(@"Queue deletion at: %@", filePath); + [_filesToDelete addObject:filePath]; +} + +- (void)queueDirectory:(NSString *)withName + inDirectory:(NSString *)parentDirectory { + [_directoriesToCreate addObject:[self destinationPathFor:withName + inProjectDirectory:parentDirectory]]; +} + +- (void)commitFileOperations { + [self performFileWrites]; + [self performCopyFrameworks]; + [self performFileDeletions]; + [self performCreateDirectories]; +} + +/* ====================================================================================================================================== + */ +#pragma mark - Private Methods + +- (NSString *)destinationPathFor:(NSString *)fileName + inProjectDirectory:(NSString *)directory { + return [[_baseDirectory stringByAppendingPathComponent:directory] + stringByAppendingPathComponent:fileName]; +} + +- (void)performFileWrites { + [_filesToWrite enumerateKeysAndObjectsUsingBlock:^(NSString *filePath, + NSData *data, BOOL *stop) { + NSError *error = nil; + if (![data writeToFile:filePath options:NSDataWritingAtomic error:&error]) { + [NSException raise:NSInternalInconsistencyException + format:@"Error writing file at filePath: %@, error: %@", + filePath, error]; + } + }]; + [_filesToWrite removeAllObjects]; +} + +- (void)performCopyFrameworks { + [_frameworksToCopy + enumerateKeysAndObjectsUsingBlock:^(NSURL *destinationUrl, + NSURL *frameworkPath, BOOL *stop) { + NSLog(@"$$$$$$$$$$$$$$ destination url: %@", destinationUrl); + NSFileManager *fileManager = [NSFileManager defaultManager]; + + if ([fileManager fileExistsAtPath:[destinationUrl path]]) { + [fileManager removeItemAtURL:destinationUrl error:nil]; + } + NSError *error = nil; + if (![fileManager copyItemAtURL:frameworkPath + toURL:destinationUrl + error:&error]) { + [NSException raise:NSInternalInconsistencyException + format:@"%@", error.localizedDescription]; + } + }]; + [_frameworksToCopy removeAllObjects]; +} + +- (void)performFileDeletions { + for (NSString *filePath in [_filesToDelete reverseObjectEnumerator]) { + NSString *fullPath = + [_baseDirectory stringByAppendingPathComponent:filePath]; + NSError *error = nil; + + if (![[NSFileManager defaultManager] removeItemAtPath:fullPath + error:&error]) { + NSLog(@"failed to remove item at path; error == %@", error); + [NSException raise:NSInternalInconsistencyException + format:@"Error deleting file at filePath: %@", filePath]; + } else { + NSLog(@"Deleted: %@", fullPath); + } + } + [_filesToDelete removeAllObjects]; +} + +- (void)performCreateDirectories { + for (NSString *filePath in _directoriesToCreate) { + NSFileManager *fileManager = [NSFileManager defaultManager]; + if (![fileManager fileExistsAtPath:filePath]) { + if (![fileManager createDirectoryAtPath:filePath + withIntermediateDirectories:YES + attributes:nil + error:nil]) { + [NSException raise:NSInvalidArgumentException + format:@"Error: Create folder failed %@", filePath]; + } + } + } +} + +@end \ No newline at end of file diff --git a/XcodeZombieCodeAnalyzer/XcodeZombieCodeAnalyzer/Vendor/XcodeEditor/XCFrameworkDefinition.h b/XcodeZombieCodeAnalyzer/XcodeZombieCodeAnalyzer/Vendor/XcodeEditor/XCFrameworkDefinition.h new file mode 100644 index 0000000..3edff54 --- /dev/null +++ b/XcodeZombieCodeAnalyzer/XcodeZombieCodeAnalyzer/Vendor/XcodeEditor/XCFrameworkDefinition.h @@ -0,0 +1,42 @@ +//////////////////////////////////////////////////////////////////////////////// +// +// JASPER BLUES +// Copyright 2012 Jasper Blues +// All Rights Reserved. +// +// NOTICE: Jasper Blues permits you to use, modify, and distribute this file +// in accordance with the terms of the license agreement accompanying it. +// +//////////////////////////////////////////////////////////////////////////////// + +#import "XCAbstractDefinition.h" +#import "XcodeSourceTreeType.h" +#import + +@interface XCFrameworkDefinition : XCAbstractDefinition { + NSString *_filePath; + BOOL _copyToDestination; + XcodeSourceTreeType _sourceTree; +} + +@property(nonatomic, strong, readonly) NSString *filePath; +@property(nonatomic, readonly) BOOL copyToDestination; +@property(nonatomic, readonly) XcodeSourceTreeType sourceTree; + ++ (XCFrameworkDefinition *)frameworkDefinitionWithFilePath:(NSString *)filePath + copyToDestination: + (BOOL)copyToDestination; ++ (XCFrameworkDefinition *) + frameworkDefinitionWithFilePath:(NSString *)filePath + copyToDestination:(BOOL)copyToDestination + sourceTree:(XcodeSourceTreeType)sourceTree; + +- (id)initWithFilePath:(NSString *)filePath + copyToDestination:(BOOL)copyToDestination; +- (id)initWithFilePath:(NSString *)filePath + copyToDestination:(BOOL)copyToDestination + sourceTree:(XcodeSourceTreeType)sourceTree; + +- (NSString *)fileName; + +@end \ No newline at end of file diff --git a/XcodeZombieCodeAnalyzer/XcodeZombieCodeAnalyzer/Vendor/XcodeEditor/XCFrameworkDefinition.m b/XcodeZombieCodeAnalyzer/XcodeZombieCodeAnalyzer/Vendor/XcodeEditor/XCFrameworkDefinition.m new file mode 100644 index 0000000..1a5aff3 --- /dev/null +++ b/XcodeZombieCodeAnalyzer/XcodeZombieCodeAnalyzer/Vendor/XcodeEditor/XCFrameworkDefinition.m @@ -0,0 +1,77 @@ +//////////////////////////////////////////////////////////////////////////////// +// +// JASPER BLUES +// Copyright 2012 Jasper Blues +// All Rights Reserved. +// +// NOTICE: Jasper Blues permits you to use, modify, and distribute this file +// in accordance with the terms of the license agreement accompanying it. +// +//////////////////////////////////////////////////////////////////////////////// + +#import "XCFrameworkDefinition.h" +#import "XCProject.h" + +@implementation XCFrameworkDefinition + +@synthesize filePath = _filePath; +@synthesize copyToDestination = _copyToDestination; +@synthesize sourceTree = _sourceTree; + +//------------------------------------------------------------------------------------------- +#pragma mark - Class Methods +//------------------------------------------------------------------------------------------- + ++ (XCFrameworkDefinition *)frameworkDefinitionWithFilePath:(NSString *)filePath + copyToDestination: + (BOOL)copyToDestination { + return + [XCFrameworkDefinition frameworkDefinitionWithFilePath:filePath + copyToDestination:copyToDestination + sourceTree:SourceTreeGroup]; +} + ++ (XCFrameworkDefinition *) + frameworkDefinitionWithFilePath:(NSString *)filePath + copyToDestination:(BOOL)copyToDestination + sourceTree:(XcodeSourceTreeType)sourceTree { + + return [[XCFrameworkDefinition alloc] initWithFilePath:filePath + copyToDestination:copyToDestination + sourceTree:sourceTree]; +} + +//------------------------------------------------------------------------------------------- +#pragma mark - Initialization & Destruction +//------------------------------------------------------------------------------------------- + +- (id)initWithFilePath:(NSString *)filePath + copyToDestination:(BOOL)copyToDestination { + return [self initWithFilePath:filePath + copyToDestination:copyToDestination + sourceTree:SourceTreeGroup]; +} + +- (id)initWithFilePath:(NSString *)filePath + copyToDestination:(BOOL)copyToDestination + sourceTree:(XcodeSourceTreeType)sourceTree { + self = [super init]; + if (self) { + _filePath = [filePath copy]; + _copyToDestination = copyToDestination; + _sourceTree = sourceTree; + } + return self; +} + +//------------------------------------------------------------------------------------------- +#pragma mark - Interface Methods +//------------------------------------------------------------------------------------------- + +- (NSString *)fileName { + return + [[_filePath lastPathComponent] stringByReplacingOccurrencesOfString:@"/" + withString:@""]; +} + +@end \ No newline at end of file diff --git a/XcodeZombieCodeAnalyzer/XcodeZombieCodeAnalyzer/Vendor/XcodeEditor/XCGroup.h b/XcodeZombieCodeAnalyzer/XcodeZombieCodeAnalyzer/Vendor/XcodeEditor/XCGroup.h new file mode 100644 index 0000000..0437b4d --- /dev/null +++ b/XcodeZombieCodeAnalyzer/XcodeZombieCodeAnalyzer/Vendor/XcodeEditor/XCGroup.h @@ -0,0 +1,265 @@ +//////////////////////////////////////////////////////////////////////////////// +// +// JASPER BLUES +// Copyright 2012 Jasper Blues +// All Rights Reserved. +// +// NOTICE: Jasper Blues permits you to use, modify, and distribute this file +// in accordance with the terms of the license agreement accompanying it. +// +//////////////////////////////////////////////////////////////////////////////// + +#import "XCBuildFile.h" +#import "XcodeGroupMember.h" +#import "XcodeSourceFileType.h" +#import + +@class XCProject; +@class XCClassDefinition; +@class XCSourceFile; +@class XCXibDefinition; +@class XCFileOperationQueue; +@class XCFrameworkDefinition; +@class XCSourceFileDefinition; +@class XCSubProjectDefinition; +@class XCTarget; +@class XCVersionGroup; + +/** + * Represents a _group container in an Xcode project. A group can contain + * members of type `XCSourceFile` or other groups. + */ +@interface XCGroup : NSObject { + + NSString *_pathRelativeToParent; + NSString *_key; + NSString *_alias; + +@private + NSString *_pathRelativeToProjectRoot; + NSMutableArray *_children; + NSMutableArray *_members; + + NSNumber *_isBuildFile; + NSString *_buildFileKey; + + XCFileOperationQueue *_fileOperationQueue; + XCProject *_project; + XcodeMemberType _memberType; +} + +/** + * The alias of the group, which can be used to give the group a name other than + * the last path component. + * + * See: [XcodeGroupMember displayName] + */ +@property(nonatomic, strong, readonly) NSString *alias; + +/** + * The path of the group relative to the group's parent. + * + * See: [XcodeGroupMember displayName] + */ +@property(nonatomic, strong, readonly) NSString *pathRelativeToParent; + +/** + * The group's unique key. + */ +@property(nonatomic, strong, readonly) NSString *key; + +/** + * An array containing the groups members as `XcodeGroupMember` types. + */ +@property(nonatomic, strong, readonly) + NSMutableArray> *children; + +#pragma mark Initializers + ++ (XCGroup *)groupWithProject:(XCProject *)project + key:(NSString *)key + alias:(NSString *)alias + path:(NSString *)path + children:(NSArray> *)children; + ++ (XCGroup *)groupWithProject:(XCProject *)project + key:(NSString *)key + alias:(NSString *)alias + path:(NSString *)path + children:(NSArray> *)children + memberType:(XcodeMemberType)groupType; + +- (id)initWithProject:(XCProject *)project + key:(NSString *)key + alias:(NSString *)alias + path:(NSString *)path + children:(NSArray> *)children; + +- (id)initWithProject:(XCProject *)project + key:(NSString *)key + alias:(NSString *)alias + path:(NSString *)path + children:(NSArray> *)children + memberType:(XcodeMemberType)groupType; + +#pragma mark Parent group + +- (void)removeFromParentGroup; + +- (void)removeFromParentDeletingChildren:(BOOL)deleteChildren; + +- (XCGroup *)parentGroup; + +- (BOOL)isRootGroup; + +- (BOOL)isEmpty; + +#pragma mark Adding children +/** + * Adds a class to the _group, as specified by the ClassDefinition. If the + * _group already contains a class by the same + * name, the contents will be updated. + */ +- (void)addClass:(XCClassDefinition *)classDefinition; + +/** + * Adds a class to the _group, making it a member of the specified + * [targets](XCTarget). + */ +- (void)addClass:(XCClassDefinition *)classDefinition + toTargets:(NSArray *)targets; + +/** + * Removes a class from the _group + */ +- (void)removeClass:(XCClassDefinition *)classDefinition + fromTargets:(NSArray *)targets; + +/** + * Removes a header from the _group + */ +- (void)removeHeader:(XCClassDefinition *)classDefinition; + +/** + * Adds a framework to the _group. If the _group already contains the framework, + * the contents will be updated if the + * framework definition's copyToDestination flag is yes, otherwise it will be + * ignored. + */ +- (void)addFramework:(XCFrameworkDefinition *)frameworkDefinition; + +/** + * Removes a framework from the _group + */ +- (void)removeFramework:(XCFrameworkDefinition *)frameworkDefinition + fromTargets:(NSArray *)targets; + +/** + * Adds a _group with a path relative to this _group. + */ +- (XCGroup *)addGroupWithPath:(NSString *)path; + +/** + * Adds a _group with an alias to this _group. + */ +- (XCGroup *)addGroupWithAlias:(NSString *)alias; + +/** + * Adds a _group with an alias to this _group and a particular type (only + * PBXGroupType and PBXVariantGroupType are valid) + */ +- (XCGroup *)addGroupWithAlias:(NSString *)alias + groupType:(XcodeMemberType)type; + +/** + * Adds a version group with a path relative to this group. + */ +- (XCVersionGroup *)addVersionGroupWithPath:(NSString *)path; + +/** + * Adds a reference to a folder + */ +- (void)addFolderReference:(NSString *)sourceFolder; + +- (void)addFileReference:(NSString *)filePath + withType:(XcodeSourceFileType)type; + +/** + * Adds a framework to the _group, making it a member of the specified targets. + */ +- (XCSourceFile *)addFramework:(XCFrameworkDefinition *)framework + toTargets:(NSArray *)targets; + +/** + * Adds a source file of arbitrary type - image resource, header, etc. + */ +- (void)addSourceFile:(XCSourceFileDefinition *)sourceFileDefinition; + +/** + * Adds a xib file to the _group. If the _group already contains a class by the + * same name, the contents will be updated. + */ +- (void)addXib:(XCXibDefinition *)xibDefinition; + +/** + * Adds a xib to the _group, making it a member of the specified + * [targets](XCTarget). + */ +- (void)addXib:(XCXibDefinition *)xibDefinition + toTargets:(NSArray *)targets; + +/** + * Adds a sub-project to the _group. If the _group already contains a + * sub-project by the same name, the contents will be + * updated. + * Returns boolean success/fail; if method fails, caller should assume that + * project file is corrupt (or file format has + * changed). + */ +- (void)addSubProject:(XCSubProjectDefinition *)projectDefinition; + +/** + * Adds a sub-project to the _group, making it a member of the specified + * [targets](XCTarget). + */ +- (void)addSubProject:(XCSubProjectDefinition *)projectDefinition + toTargets:(NSArray *)targets; + +- (void)removeSubProject:(XCSubProjectDefinition *)projectDefinition; + +- (void)removeSubProject:(XCSubProjectDefinition *)projectDefinition + fromTargets:(NSArray *)targets; + +#pragma mark Locating children +/** + * Instances of `XCSourceFile` and `XCGroup` returned as the type + * `XcodeGroupMember`. + */ +- (NSArray> *)members; + +/** + * Keys of members from this group and any child groups. + */ +- (NSArray *)recursiveMembers; + +/** + * Keys of members from this group + */ +- (NSArray *)buildFileKeys; + +/** + * Returns the child with the specified key, or nil. + */ +- (id)memberWithKey:(NSString *)key; + +/** + * Returns the child with the specified name, or nil. + */ +- (id)memberWithDisplayName:(NSString *)name; + +/** + * Remove the member with the specified key + */ +- (void)removeMemberWithKey:(NSString *)key; + +@end diff --git a/XcodeZombieCodeAnalyzer/XcodeZombieCodeAnalyzer/Vendor/XcodeEditor/XCGroup.m b/XcodeZombieCodeAnalyzer/XcodeZombieCodeAnalyzer/Vendor/XcodeEditor/XCGroup.m new file mode 100644 index 0000000..813b0a4 --- /dev/null +++ b/XcodeZombieCodeAnalyzer/XcodeZombieCodeAnalyzer/Vendor/XcodeEditor/XCGroup.m @@ -0,0 +1,1033 @@ +//////////////////////////////////////////////////////////////////////////////// +// +// JASPER BLUES +// Copyright 2012 Jasper Blues +// All Rights Reserved. +// +// NOTICE: Jasper Blues permits you to use, modify, and distribute this file +// in accordance with the terms of the license agreement accompanying it. +// +//////////////////////////////////////////////////////////////////////////////// + +#import "XCGroup.h" +#import "Utils/XCKeyBuilder.h" +#import "XCClassDefinition.h" +#import "XCFileOperationQueue.h" +#import "XCFrameworkDefinition.h" +#import "XCProject+SubProject.h" +#import "XCProject.h" +#import "XCSourceFile.h" +#import "XCSourceFileDefinition.h" +#import "XCSubProjectDefinition.h" +#import "XCTarget.h" +#import "XCVersionGroup.h" +#import "XCXibDefinition.h" +#import "XcodeMemberType.h" + +@implementation XCGroup + +//------------------------------------------------------------------------------------------- +#pragma mark - Class Methods +//------------------------------------------------------------------------------------------- + ++ (XCGroup *)groupWithProject:(XCProject *)project + key:(NSString *)key + alias:(NSString *)alias + path:(NSString *)path + children:(NSArray *)children { + + return [[XCGroup alloc] initWithProject:project + key:key + alias:alias + path:path + children:children + memberType:PBXGroupType]; +} + ++ (XCGroup *)groupWithProject:(XCProject *)project + key:(NSString *)key + alias:(NSString *)alias + path:(NSString *)path + children:(NSArray> *)children + memberType:(XcodeMemberType)groupType { + return [[XCGroup alloc] initWithProject:project + key:key + alias:alias + path:path + children:children + memberType:groupType]; +} + +//------------------------------------------------------------------------------------------- +#pragma mark - Initialization & Destruction +//------------------------------------------------------------------------------------------- + +- (id)initWithProject:(XCProject *)project + key:(NSString *)key + alias:(NSString *)alias + path:(NSString *)path + children:(NSArray> *)children + memberType:(XcodeMemberType)groupType { + self = [super init]; + + assert(groupType == PBXGroupType || groupType == PBXVariantGroupType); + + if (self) { + _project = project; + _fileOperationQueue = [_project fileOperationQueue]; + _key = [key copy]; + _alias = [alias copy]; + _pathRelativeToParent = [path copy]; + + _children = [children mutableCopy]; + if (!_children) { + _children = [[NSMutableArray alloc] init]; + } + + _memberType = groupType; + } + return self; +} + +- (id)initWithProject:(XCProject *)project + key:(NSString *)key + alias:(NSString *)alias + path:(NSString *)path + children:(NSArray *)children { + return [self initWithProject:project + key:key + alias:alias + path:path + children:children + memberType:PBXGroupType]; +} + +//------------------------------------------------------------------------------------------- +#pragma mark - Interface Methods +//------------------------------------------------------------------------------------------- + +#pragma mark Parent _group + +- (void)removeFromParentGroup { + [self removeFromParentDeletingChildren:NO]; +} + +- (void)removeFromParentDeletingChildren:(BOOL)deleteChildren { + if (deleteChildren) { + [_fileOperationQueue queueDeletion:[self pathRelativeToProjectRoot]]; + } + NSDictionary *dictionary = [_project objects][_key]; + NSLog(@"Here's the dictionary: %@", dictionary); + + [[_project objects] removeObjectForKey:_key]; + + dictionary = [_project objects][_key]; + NSLog(@"Here's the dictionary: %@", dictionary); + + for (XCTarget *target in [_project targets]) { + [target removeMembersWithKeys:[self recursiveMembers]]; + } + NSLog(@"Done!!!"); +} + +- (XCGroup *)parentGroup { + return [_project groupForGroupMemberWithKey:_key]; +} + +- (BOOL)isRootGroup { + return [self pathRelativeToParent] == nil && [self displayName] == nil; +} + +- (BOOL)isEmpty { + return [self.members count] == 0; +} + +//------------------------------------------------------------------------------------------- +#pragma mark Adding children + +- (void)addClass:(XCClassDefinition *)classDefinition { + + if ([classDefinition header]) { + [self makeGroupMemberWithName:[classDefinition headerFileName] + contents:[classDefinition header] + type:SourceCodeHeader + fileOperationStyle:[classDefinition fileOperationType]]; + } + + if ([classDefinition isObjectiveC]) { + [self makeGroupMemberWithName:[classDefinition sourceFileName] + contents:[classDefinition source] + type:SourceCodeObjC + fileOperationStyle:[classDefinition fileOperationType]]; + } else if ([classDefinition isObjectiveCPlusPlus]) { + [self makeGroupMemberWithName:[classDefinition sourceFileName] + contents:[classDefinition source] + type:SourceCodeObjCPlusPlus + fileOperationStyle:[classDefinition fileOperationType]]; + } + + [_project objects][_key] = [self asDictionary]; +} + +- (void)addClass:(XCClassDefinition *)classDefinition + toTargets:(NSArray *)targets { + [self addClass:classDefinition]; + XCSourceFile *sourceFile = + [_project fileWithName:[classDefinition sourceFileName]]; + [self addSourceFile:sourceFile toTargets:targets]; +} + +- (void)removeClass:(XCClassDefinition *)classDefinition + fromTargets:(NSArray *)targets { + XCSourceFile *sourceFile = + [_project fileWithName:[classDefinition className]]; + [sourceFile removeBuildFile]; + + NSString *classKey = sourceFile.key; + // XCSourceFile *frameworkSourceRef = (XCSourceFile *) [self + // memberWithDisplayName:[classDefinition sourceFileName]]; + for (XCTarget *target in targets) { + [target removeMemberWithKey:classKey]; + } + //[frameworkSourceRef removeBuildFile]; + + [self removeMemberWithKey:classKey]; + [_project removeObjectWithKey:classKey]; +} + +- (void)removeHeader:(XCClassDefinition *)classDefinition { + XCSourceFile *sourceFile = + [_project fileWithName:[classDefinition className]]; + [self removeMemberWithKey:[sourceFile key]]; + [_project removeObjectWithKey:[sourceFile key]]; +} + +- (void)addFramework:(XCFrameworkDefinition *)frameworkDefinition { + if (([self memberWithDisplayName:[frameworkDefinition fileName]]) == nil) { + NSLog(@"frame doesnt exists. creating %@", [frameworkDefinition fileName]); + NSLog(@"existing members: %@", [self members]); + NSDictionary *fileReference; + if ([frameworkDefinition copyToDestination]) { + fileReference = + [self makeFileReferenceWithPath:[frameworkDefinition fileName] + name:nil + type:Framework + sourceTree:frameworkDefinition.sourceTree]; + BOOL copyFramework = NO; + if ([frameworkDefinition fileOperationType] == + XCFileOperationTypeOverwrite) { + copyFramework = YES; + } else if ([frameworkDefinition fileOperationType] == + XCFileOperationTypeAcceptExisting) { + NSString *frameworkName = + [[frameworkDefinition filePath] lastPathComponent]; + if (![_fileOperationQueue + fileWithName:frameworkName + existsInProjectDirectory:[self pathRelativeToProjectRoot]]) { + copyFramework = YES; + } + } + if (copyFramework) { + [_fileOperationQueue + queueFrameworkWithFilePath:[frameworkDefinition filePath] + inDirectory:[self pathRelativeToProjectRoot]]; + } + } else { + NSString *path = [frameworkDefinition filePath]; + NSString *name = [frameworkDefinition fileName]; + fileReference = + [self makeFileReferenceWithPath:path + name:name + type:Framework + sourceTree:frameworkDefinition.sourceTree]; + } + NSString *frameworkKey = + [[XCKeyBuilder forItemNamed:[frameworkDefinition fileName]] build]; + [_project objects][frameworkKey] = fileReference; + [self addMemberWithKey:frameworkKey]; + } + [_project objects][_key] = [self asDictionary]; +} + +- (XCSourceFile *)addFramework:(XCFrameworkDefinition *)frameworkDefinition + toTargets:(NSArray *)targets { + [self addFramework:frameworkDefinition]; + XCSourceFile *frameworkSourceRef = (XCSourceFile *)[self + memberWithDisplayName:[frameworkDefinition fileName]]; + [self addSourceFile:frameworkSourceRef toTargets:targets]; + return frameworkSourceRef; +} + +- (void)removeFramework:(XCFrameworkDefinition *)frameworkDefinition + fromTargets:(NSArray *)targets { + XCSourceFile *file = [_project fileWithName:[frameworkDefinition filePath]]; + NSString *frameworkKey = file.key; + if (!file) + return; + + XCSourceFile *frameworkSourceRef = (XCSourceFile *)[self + memberWithDisplayName:[frameworkDefinition fileName]]; + for (XCTarget *target in targets) { + [target removeMemberWithKey:frameworkKey]; + } + [frameworkSourceRef removeBuildFile]; + + [self removeMemberWithKey:frameworkKey]; + [_project removeObjectWithKey:frameworkKey]; +} + +- (void)addFolderReference:(NSString *)sourceFolder { + NSDictionary *folderReferenceDictionary = + [self makeFileReferenceWithPath:sourceFolder + name:[sourceFolder lastPathComponent] + type:Folder + sourceTree:SourceTreeGroup]; + NSString *folderReferenceKey = + [[XCKeyBuilder forItemNamed:[sourceFolder lastPathComponent]] build]; + [self addMemberWithKey:folderReferenceKey]; + [_project objects][folderReferenceKey] = folderReferenceDictionary; + [_project objects][_key] = [self asDictionary]; +} + +- (void)addFileReference:(NSString *)filePath + withType:(XcodeSourceFileType)type { + NSDictionary *folderReferenceDictionary = + [self makeFileReferenceWithPath:filePath + name:[filePath lastPathComponent] + type:type + sourceTree:SourceTreeGroup]; + NSString *folderReferenceKey = + [[XCKeyBuilder forItemNamed:[filePath lastPathComponent]] build]; + [self addMemberWithKey:folderReferenceKey]; + [_project objects][folderReferenceKey] = folderReferenceDictionary; + [_project objects][_key] = [self asDictionary]; +} + +- (XCGroup *)addGroupWithPath:(NSString *)path { + NSString *groupKeyPath = + self.pathRelativeToProjectRoot + ? [self.pathRelativeToProjectRoot stringByAppendingPathComponent:path] + : path; + + NSString *groupKey = [[XCKeyBuilder forItemNamed:groupKeyPath] build]; + + NSArray *members = [self members]; + for (id groupMember in members) { + if ([groupMember groupMemberType] == PBXGroupType || + [groupMember groupMemberType] == PBXVariantGroupType) { + + if ([[[groupMember pathRelativeToProjectRoot] lastPathComponent] + isEqualToString:path] || + [[groupMember displayName] isEqualToString:path] || + [[groupMember key] isEqualToString:groupKey]) { + return nil; + } + } + } + + XCGroup *group = [[XCGroup alloc] initWithProject:_project + key:groupKey + alias:nil + path:path + children:nil]; + NSDictionary *groupDict = [group asDictionary]; + + [_project objects][groupKey] = groupDict; + [_fileOperationQueue queueDirectory:path + inDirectory:[self pathRelativeToProjectRoot]]; + [self addMemberWithKey:groupKey]; + + NSDictionary *dict = [self asDictionary]; + [_project objects][_key] = dict; + + return group; +} + +- (XCGroup *)addGroupWithAlias:(NSString *)alias { + return [self addGroupWithAlias:alias groupType:PBXGroupType]; +} + +- (XCGroup *)addGroupWithAlias:(NSString *)alias + groupType:(XcodeMemberType)type { + NSString *groupKey = [[XCKeyBuilder forItemNamed:alias] build]; + + NSArray *members = [self members]; + for (id groupMember in members) { + if ([groupMember groupMemberType] == PBXGroupType || + [groupMember groupMemberType] == PBXVariantGroupType) { + + if ([[groupMember displayName] isEqualToString:alias] || + [[groupMember key] isEqualToString:groupKey]) { + return nil; + } + } + } + + XCGroup *group = [[XCGroup alloc] initWithProject:_project + key:groupKey + alias:alias + path:nil + children:nil + memberType:type]; + NSDictionary *groupDict = [group asDictionary]; + + [_project objects][groupKey] = groupDict; + [self addMemberWithKey:groupKey]; + + NSDictionary *dict = [self asDictionary]; + [_project objects][_key] = dict; + + return group; +} + +- (XCVersionGroup *)addVersionGroupWithPath:(NSString *)path { + NSString *groupKeyPath = + self.pathRelativeToProjectRoot + ? [self.pathRelativeToProjectRoot stringByAppendingPathComponent:path] + : path; + + NSString *groupKey = [[XCKeyBuilder forItemNamed:groupKeyPath] build]; + + NSArray *members = [self members]; + for (id groupMember in members) { + if ([groupMember groupMemberType] == XCVersionGroupType) { + + if ([[[groupMember pathRelativeToProjectRoot] lastPathComponent] + isEqualToString:path] || + [[groupMember displayName] isEqualToString:path] || + [[groupMember key] isEqualToString:groupKey]) { + return nil; + } + } + } + + XCVersionGroup *group = [[XCVersionGroup alloc] initWithProject:_project + key:groupKey + path:path + children:nil + currentVersion:nil]; + NSDictionary *groupDict = [group asDictionary]; + + [_project objects][groupKey] = groupDict; + [_fileOperationQueue queueDirectory:path + inDirectory:[self pathRelativeToProjectRoot]]; + [self addMemberWithKey:groupKey]; + + NSDictionary *dict = [self asDictionary]; + [_project objects][_key] = dict; + + return group; +} + +- (void)addSourceFile:(XCSourceFileDefinition *)sourceFileDefinition { + [self makeGroupMemberWithName:[sourceFileDefinition sourceFileName] + contents:[sourceFileDefinition data] + type:[sourceFileDefinition type] + fileOperationStyle:[sourceFileDefinition fileOperationType]]; + [_project objects][_key] = [self asDictionary]; +} + +- (void)addXib:(XCXibDefinition *)xibDefinition { + [self makeGroupMemberWithName:[xibDefinition xibFileName] + contents:[xibDefinition content] + type:XibFile + fileOperationStyle:[xibDefinition fileOperationType]]; + [_project objects][_key] = [self asDictionary]; +} + +- (void)addXib:(XCXibDefinition *)xibDefinition toTargets:(NSArray *)targets { + [self addXib:xibDefinition]; + XCSourceFile *sourceFile = + [_project fileWithName:[xibDefinition xibFileName]]; + [self addSourceFile:sourceFile toTargets:targets]; +} + +// adds an xcodeproj as a subproject of the current project. +- (void)addSubProject:(XCSubProjectDefinition *)projectDefinition { + // set up path to the xcodeproj file as Xcode sees it - path to top level of + // project + _group path if any + [projectDefinition initFullProjectPath:_project.filePath + groupPath:[self pathRelativeToParent]]; + + // create PBXFileReference for xcodeproj file and add to PBXGroup for the + // current _group + // (will retrieve existing if already there) + [self makeGroupMemberWithName:[projectDefinition projectFileName] + path:[projectDefinition pathRelativeToProjectRoot] + type:XcodeProject + fileOperationStyle:[projectDefinition fileOperationType]]; + [_project objects][_key] = [self asDictionary]; + + // create PBXContainerItemProxies and PBXReferenceProxies + [_project addProxies:projectDefinition]; + + // add projectReferences key to PBXProject + [self addProductsGroupToProject:projectDefinition]; +} + +// adds an xcodeproj as a subproject of the current project, and also adds all +// build products except for test bundle(s) +// to targets. +- (void)addSubProject:(XCSubProjectDefinition *)projectDefinition + toTargets:(NSArray *)targets { + [self addSubProject:projectDefinition]; + + // add subproject's build products to targets (does not add the subproject's + // test bundle) + NSArray *buildProductFiles = + [_project buildProductsForTargets:[projectDefinition projectKey]]; + for (XCSourceFile *file in buildProductFiles) { + [self addSourceFile:file toTargets:targets]; + } + // add main target of subproject as target dependency to main target of + // project + [_project addAsTargetDependency:projectDefinition toTargets:targets]; +} + +// removes an xcodeproj from the current project. +- (void)removeSubProject:(XCSubProjectDefinition *)projectDefinition { + if (projectDefinition == nil) { + return; + } + + // set up path to the xcodeproj file as Xcode sees it - path to top level of + // project + _group path if any + [projectDefinition initFullProjectPath:_project.filePath + groupPath:[self pathRelativeToParent]]; + + NSString *xcodeprojKey = [projectDefinition projectKey]; + + // Remove from _group and remove PBXFileReference + [self removeGroupMemberWithKey:xcodeprojKey]; + + // remove PBXContainerItemProxies and PBXReferenceProxies + [_project removeProxies:xcodeprojKey]; + + // get the key for the Products _group + NSString *productsGroupKey = [_project productsGroupKeyForKey:xcodeprojKey]; + + // remove from the ProjectReferences array of PBXProject + [_project removeFromProjectReferences:xcodeprojKey + forProductsGroup:productsGroupKey]; + + // remove PDXBuildFile entries + [self removeProductsGroupFromProject:productsGroupKey]; + + // remove Products _group + [[_project objects] removeObjectForKey:productsGroupKey]; + + // remove from all targets + [_project removeTargetDependencies:[projectDefinition name]]; +} + +- (void)removeSubProject:(XCSubProjectDefinition *)projectDefinition + fromTargets:(NSArray *)targets { + if (projectDefinition == nil) { + return; + } + + // set up path to the xcodeproj file as Xcode sees it - path to top level of + // project + _group path if any + [projectDefinition initFullProjectPath:_project.filePath + groupPath:[self pathRelativeToParent]]; + + NSString *xcodeprojKey = [projectDefinition projectKey]; + + // Remove PBXBundleFile entries and corresponding inclusion in + // PBXFrameworksBuildPhase and PBXResourcesBuidPhase + NSString *productsGroupKey = [_project productsGroupKeyForKey:xcodeprojKey]; + [self removeProductsGroupFromProject:productsGroupKey]; + + // Remove the PBXContainerItemProxy for this xcodeproj with proxyType 1 + NSString *containerItemProxyKey = [_project + containerItemProxyKeyForName:[projectDefinition pathRelativeToProjectRoot] + proxyType:@"1"]; + if (containerItemProxyKey != nil) { + [[_project objects] removeObjectForKey:containerItemProxyKey]; + } + + // Remove PBXTargetDependency and entry in PBXNativeTarget + [_project removeTargetDependencies:[projectDefinition name]]; +} + +//------------------------------------------------------------------------------------------- +#pragma mark Members + +- (NSArray> *)members { + if (_members == nil) { + _members = [[NSMutableArray alloc] init]; + for (NSString *childKey in _children) { + XcodeMemberType type = [self typeForKey:childKey]; + + @autoreleasepool { + if (type == PBXGroupType || type == PBXVariantGroupType) { + [_members addObject:[_project groupWithKey:childKey]]; + } else if (type == PBXFileReferenceType) { + [_members addObject:[_project fileWithKey:childKey]]; + } else if (type == XCVersionGroupType) { + [_members addObject:[_project versionGroupWithKey:childKey]]; + } + } + } + } + return _members; +} + +- (NSArray *)recursiveMembers { + NSMutableArray *recursiveMembers = [NSMutableArray array]; + for (NSString *childKey in _children) { + XcodeMemberType type = [self typeForKey:childKey]; + if (type == PBXGroupType || type == PBXVariantGroupType) { + XCGroup *group = [_project groupWithKey:childKey]; + NSArray *groupChildren = [group recursiveMembers]; + [recursiveMembers addObjectsFromArray:groupChildren]; + } else if (type == PBXFileReferenceType) { + [recursiveMembers addObject:childKey]; + } + } + [recursiveMembers addObject:_key]; + return [recursiveMembers arrayByAddingObjectsFromArray:recursiveMembers]; +} + +- (NSArray *)buildFileKeys { + + NSMutableArray *arrayOfBuildFileKeys = [NSMutableArray array]; + for (id groupMember in [self members]) { + + if ([groupMember groupMemberType] == PBXGroupType || + [groupMember groupMemberType] == PBXVariantGroupType) { + XCGroup *group = (XCGroup *)groupMember; + [arrayOfBuildFileKeys addObjectsFromArray:[group buildFileKeys]]; + } else if ([groupMember groupMemberType] == PBXFileReferenceType) { + [arrayOfBuildFileKeys addObject:[groupMember key]]; + } + } + return arrayOfBuildFileKeys; +} + +- (id)memberWithKey:(NSString *)key { + id groupMember = nil; + + if ([_children containsObject:key]) { + XcodeMemberType type = [self typeForKey:key]; + if (type == PBXGroupType || type == PBXVariantGroupType) { + groupMember = [_project groupWithKey:key]; + } else if (type == PBXFileReferenceType) { + groupMember = [_project fileWithKey:key]; + } else if (type == XCVersionGroupType) { + groupMember = [_project versionGroupWithKey:key]; + } + } + return groupMember; +} + +- (id)memberWithDisplayName:(NSString *)name { + for (id member in [self members]) { + if ([[member displayName] isEqualToString:name]) { + return member; + } + } + return nil; +} + +- (void)removeMemberWithKey:(NSString *)key { + if ([_children containsObject:key]) { + [_children removeObject:key]; + [self flagMembersAsDirty]; + } +} + +//------------------------------------------------------------------------------------------- +#pragma mark - Protocol Methods + +- (XcodeMemberType)groupMemberType { + return [self typeForKey:self.key]; +} + +- (NSString *)displayName { + if (_alias) { + return _alias; + } + return [_pathRelativeToParent lastPathComponent]; +} + +- (NSString *)pathRelativeToProjectRoot { + if (_pathRelativeToProjectRoot == nil) { + NSMutableArray *pathComponents = [[NSMutableArray alloc] init]; + XCGroup *group = nil; + NSString *key = [_key copy]; + + while ((group = [_project groupForGroupMemberWithKey:key]) != nil && + [group pathRelativeToParent] != nil) { + [pathComponents addObject:[group pathRelativeToParent]]; + key = [[group key] copy]; + } + + NSMutableString *fullPath = [[NSMutableString alloc] init]; + for (NSInteger i = (NSInteger)[pathComponents count] - 1; i >= 0; i--) { + [fullPath appendFormat:@"%@/", pathComponents[i]]; + } + _pathRelativeToProjectRoot = + [[fullPath stringByAppendingPathComponent:_pathRelativeToParent] copy]; + } + return _pathRelativeToProjectRoot; +} + +//------------------------------------------------------------------------------------------- +#pragma mark - XCBuildFile Methods + +- (BOOL)canBecomeBuildFile { + return _memberType == PBXVariantGroupType; +} + +- (XcodeMemberType)buildPhase { + if (_memberType == PBXVariantGroupType) + return PBXResourcesBuildPhaseType; + + return PBXNilType; +} + +- (NSString *)buildFileKey { + if (_buildFileKey == nil) { + [[_project objects] enumerateKeysAndObjectsUsingBlock:^( + NSString *key, NSDictionary *obj, BOOL *stop) { + if ([[obj valueForKey:@"isa"] xce_hasBuildFileType]) { + if ([[obj valueForKey:@"fileRef"] isEqualToString:_key]) { + _buildFileKey = [key copy]; + } + } + }]; + } + return [_buildFileKey copy]; +} + +- (void)becomeBuildFile { + if (![self isBuildFile]) { + if ([self canBecomeBuildFile]) { + NSMutableDictionary *sourceBuildFile = [NSMutableDictionary dictionary]; + sourceBuildFile[@"isa"] = + [NSString xce_stringFromMemberType:PBXBuildFileType]; + sourceBuildFile[@"fileRef"] = _key; + NSString *buildFileKey = [[XCKeyBuilder + forItemNamed:[self.displayName stringByAppendingString:@".buildFile"]] + build]; + [_project objects][buildFileKey] = sourceBuildFile; + } + } +} + +- (BOOL)isBuildFile { + if ([self canBecomeBuildFile] && _isBuildFile == nil) { + _isBuildFile = @NO; + [[_project objects] enumerateKeysAndObjectsUsingBlock:^( + NSString *key, NSDictionary *obj, BOOL *stop) { + if ([[obj valueForKey:@"isa"] xce_hasBuildFileType]) { + if ([[obj valueForKey:@"fileRef"] isEqualToString:_key]) { + _isBuildFile = nil; + + _isBuildFile = @YES; + } + } + }]; + } + return [_isBuildFile boolValue]; +} + +//------------------------------------------------------------------------------------------- +#pragma mark - Utility Methods + +- (NSString *)description { + return [NSString stringWithFormat:@"Group: displayName = %@, key=%@", + [self displayName], _key]; +} + +//------------------------------------------------------------------------------------------- +#pragma mark - Private Methods +//------------------------------------------------------------------------------------------- + +- (void)addMemberWithKey:(NSString *)key { + + for (NSString *childKey in _children) { + if ([childKey isEqualToString:key]) { + [self flagMembersAsDirty]; + return; + } + } + [_children addObject:key]; + [self flagMembersAsDirty]; +} + +- (void)flagMembersAsDirty { + _members = nil; +} + +//------------------------------------------------------------------------------------------- + +- (void)makeGroupMemberWithName:(NSString *)name + contents:(id)contents + type:(XcodeSourceFileType)type + fileOperationStyle:(XCFileOperationType)fileOperationStyle { + + NSString *filePath; + XCSourceFile *currentSourceFile = + (XCSourceFile *)[self memberWithDisplayName:name]; + if ((currentSourceFile) == nil) { + NSString *refName = nil; + if (type == AssetCatalog) { + refName = [name lastPathComponent]; + } + NSDictionary *reference = [self makeFileReferenceWithPath:name + name:refName + type:type + sourceTree:SourceTreeGroup]; + NSString *fileKey = [[XCKeyBuilder forItemNamed:name] build]; + [_project objects][fileKey] = reference; + [self addMemberWithKey:fileKey]; + filePath = [self pathRelativeToProjectRoot]; + } else { + filePath = [[currentSourceFile pathRelativeToProjectRoot] + stringByDeletingLastPathComponent]; + } + + BOOL writeFile = NO; + if (fileOperationStyle == XCFileOperationTypeOverwrite) { + writeFile = YES; + [_fileOperationQueue fileWithName:name existsInProjectDirectory:filePath]; + } else if (fileOperationStyle == XCFileOperationTypeAcceptExisting && + ![_fileOperationQueue fileWithName:name + existsInProjectDirectory:filePath]) { + writeFile = YES; + } + if (writeFile) { + if (![self fileTypeRequiresSpecialManagement:type]) { + if ([contents isKindOfClass:[NSString class]]) { + [_fileOperationQueue queueTextFile:name + inDirectory:filePath + withContents:contents]; + } else { + [_fileOperationQueue queueDataFile:name + inDirectory:filePath + withContents:contents]; + } + } else { + [self writeSpecialGroupMemberWithName:name + filePath:filePath + contents:contents + type:type]; + } + } +} + +- (BOOL)fileTypeRequiresSpecialManagement:(XcodeSourceFileType)type { + return type == XCDataModel || type == AssetCatalog; +} + +- (void)writeSpecialGroupMemberWithName:(NSString *)name + filePath:(NSString *)filePath + contents:(id)contents + type:(XcodeSourceFileType)type { + if (type == XCDataModel) { + [_fileOperationQueue queueDirectory:name inDirectory:filePath]; + [_fileOperationQueue commitFileOperations]; + filePath = [filePath stringByAppendingPathComponent:name]; + name = @"contents"; + if ([contents isKindOfClass:[NSString class]]) + [_fileOperationQueue queueTextFile:name + inDirectory:filePath + withContents:contents]; + else + [_fileOperationQueue queueDataFile:name + inDirectory:filePath + withContents:contents]; + } else if (type == AssetCatalog) { + [_fileOperationQueue queueDirectory:name inDirectory:filePath]; + [_fileOperationQueue commitFileOperations]; + filePath = [filePath stringByAppendingPathComponent:name]; + name = @"Contents.json"; + [_fileOperationQueue + queueTextFile:name + inDirectory:filePath + withContents:@"{\"info\" : {\"version\" : 1,\"author\" : \"xcode\"}}"]; + } +} + +//------------------------------------------------------------------------------------------- + +#pragma mark Xcodeproj methods + +// creates PBXFileReference and adds to _group if not already there; returns +// key for file reference. Locates +// member via path rather than name, because that is how subprojects are stored +// by Xcode +- (void)makeGroupMemberWithName:(NSString *)name + path:(NSString *)path + type:(XcodeSourceFileType)type + fileOperationStyle:(XCFileOperationType)fileOperationStyle { + XCSourceFile *currentSourceFile = + (XCSourceFile *)[self memberWithDisplayName:name]; + if ((currentSourceFile) == nil) { + NSDictionary *reference = [self makeFileReferenceWithPath:path + name:name + type:type + sourceTree:SourceTreeGroup]; + NSString *fileKey = [[XCKeyBuilder forItemNamed:name] build]; + [_project objects][fileKey] = reference; + [self addMemberWithKey:fileKey]; + } +} + +// makes a new _group called Products and returns its key +- (NSString *)makeProductsGroup:(XCSubProjectDefinition *)xcodeprojDefinition { + NSMutableArray *children = [NSMutableArray array]; + NSString *uniquer = @""; + for (NSString *productName in [xcodeprojDefinition buildProductNames]) { + [children addObject:[_project referenceProxyKeyForName:productName]]; + uniquer = [uniquer stringByAppendingString:productName]; + } + NSString *productKey = [[XCKeyBuilder + forItemNamed:[NSString stringWithFormat:@"%@-Products", uniquer]] build]; + XCGroup *productsGroup = [XCGroup groupWithProject:_project + key:productKey + alias:@"Products" + path:nil + children:children]; + [_project objects][productKey] = [productsGroup asDictionary]; + return productKey; +} + +// makes a new Products _group (by calling the method above), makes a new +// projectReferences array for it and +// then adds it to the PBXProject object +- (void)addProductsGroupToProject: + (XCSubProjectDefinition *)xcodeprojDefinition { + NSString *productKey = [self makeProductsGroup:xcodeprojDefinition]; + + NSMutableDictionary *PBXProjectDict = [_project PBXProjectDict]; + NSMutableArray *projectReferences = + [PBXProjectDict valueForKey:@"projectReferences"]; + + NSMutableDictionary *newProjectReference = [NSMutableDictionary dictionary]; + newProjectReference[@"ProductGroup"] = productKey; + NSString *projectFileKey = [[_project + fileWithName:[xcodeprojDefinition pathRelativeToProjectRoot]] key]; + newProjectReference[@"ProjectRef"] = projectFileKey; + + if (projectReferences == nil) { + projectReferences = [NSMutableArray array]; + } + [projectReferences addObject:newProjectReference]; + PBXProjectDict[@"projectReferences"] = projectReferences; +} + +// removes PBXFileReference from _group and project +- (void)removeGroupMemberWithKey:(NSString *)key { + NSMutableArray *children = [self valueForKey:@"children"]; + [children removeObject:key]; + _project.objects[_key] = [self asDictionary]; + // remove PBXFileReference + [_project.objects removeObjectForKey:key]; +} + +// removes the given key from the files arrays of the given section, if found +// (intended to be used with +// PBXFrameworksBuildPhase and PBXResourcesBuildPhase) +// they are not required because we are currently not adding these entries; +// Xcode is doing it for us. The existing +// code for adding to a target doesn't do it, and I didn't add it since Xcode +// will take care of it for me and I was +// avoiding modifying existing code as much as possible) +- (void)removeBuildPhaseFileKey:(NSString *)key + forType:(XcodeMemberType)memberType { + NSArray *buildPhases = [_project keysForProjectObjectsOfType:memberType + withIdentifier:nil + singleton:NO + required:NO]; + for (NSString *buildPhaseKey in buildPhases) { + NSDictionary *buildPhaseDict = + [[_project objects] valueForKey:buildPhaseKey]; + NSMutableArray *fileKeys = [buildPhaseDict valueForKey:@"files"]; + for (NSString *fileKey in fileKeys) { + if ([fileKey isEqualToString:key]) { + [fileKeys removeObject:fileKey]; + } + } + } +} + +// removes entries from PBXBuildFiles, PBXFrameworksBuildPhase and +// PBXResourcesBuildPhase +- (void)removeProductsGroupFromProject:(NSString *)key { + // remove product _group's build products from PDXBuildFiles + NSDictionary *productsGroup = _project.objects[key]; + for (NSString *childKey in [productsGroup valueForKey:@"children"]) { + NSArray *buildFileKeys = + [_project keysForProjectObjectsOfType:PBXBuildFileType + withIdentifier:childKey + singleton:NO + required:NO]; + // could be zero - we didn't add the test bundle as a build product + if ([buildFileKeys count] == 1) { + NSString *buildFileKey = buildFileKeys[0]; + [[_project objects] removeObjectForKey:buildFileKey]; + [self removeBuildPhaseFileKey:buildFileKey + forType:PBXFrameworksBuildPhaseType]; + [self removeBuildPhaseFileKey:buildFileKey + forType:PBXResourcesBuildPhaseType]; + } + } +} + +//------------------------------------------------------------------------------------------- + +#pragma mark Dictionary Representations + +- (NSDictionary *)makeFileReferenceWithPath:(NSString *)path + name:(NSString *)name + type:(XcodeSourceFileType)type + sourceTree:(XcodeSourceTreeType)sourceTree { + NSMutableDictionary *reference = [NSMutableDictionary dictionary]; + reference[@"isa"] = [NSString xce_stringFromMemberType:PBXFileReferenceType]; + reference[@"fileEncoding"] = @"4"; + reference[@"lastKnownFileType"] = NSStringFromXCSourceFileType(type); + if (name != nil) { + reference[@"name"] = [name lastPathComponent]; + } + if (path != nil) { + reference[@"path"] = path; + } + reference[@"sourceTree"] = [NSString xce_stringFromSourceTreeType:sourceTree]; + return reference; +} + +- (NSDictionary *)asDictionary { + NSMutableDictionary *groupData = [NSMutableDictionary dictionary]; + groupData[@"isa"] = [NSString xce_stringFromMemberType:_memberType]; + groupData[@"sourceTree"] = @""; + + if (_alias != nil) { + groupData[@"name"] = _alias; + } + + if (_pathRelativeToParent) { + groupData[@"path"] = _pathRelativeToParent; + } + + if (_children) { + groupData[@"children"] = _children; + } + + return groupData; +} + +- (XcodeMemberType)typeForKey:(NSString *)key { + NSDictionary *obj = [[_project objects] valueForKey:key]; + return [[obj valueForKey:@"isa"] xce_asMemberType]; +} + +- (void)addSourceFile:(XCSourceFile *)sourceFile toTargets:(NSArray *)targets { + for (XCTarget *target in targets) { + [target addMember:sourceFile]; + } +} + +@end diff --git a/XcodeZombieCodeAnalyzer/XcodeZombieCodeAnalyzer/Vendor/XcodeEditor/XCProject+SubProject.h b/XcodeZombieCodeAnalyzer/XcodeZombieCodeAnalyzer/Vendor/XcodeEditor/XCProject+SubProject.h new file mode 100644 index 0000000..7a2bbe2 --- /dev/null +++ b/XcodeZombieCodeAnalyzer/XcodeZombieCodeAnalyzer/Vendor/XcodeEditor/XCProject+SubProject.h @@ -0,0 +1,45 @@ +//////////////////////////////////////////////////////////////////////////////// +// +// JASPER BLUES +// Copyright 2012 Jasper Blues +// All Rights Reserved. +// +// NOTICE: Jasper Blues permits you to use, modify, and distribute this file +// in accordance with the terms of the license agreement accompanying it. +// +//////////////////////////////////////////////////////////////////////////////// + +#import "XCProject.h" +#import + +@interface XCProject (SubProject) + +- (NSString *)referenceProxyKeyForName:(NSString *)name; + +- (NSArray *)buildProductsForTargets:(NSString *)xcodeprojKey; + +- (void)addAsTargetDependency:(XCSubProjectDefinition *)xcodeprojDefinition + toTargets:(NSArray *)targets; + +- (NSArray *)keysForProjectObjectsOfType:(XcodeMemberType)memberType + withIdentifier:(NSString *)identifier + singleton:(BOOL)singleton + required:(BOOL)required; + +- (NSMutableDictionary *)PBXProjectDict; + +- (void)removeProxies:(NSString *)xcodeprojKey; + +- (void)addProxies:(XCSubProjectDefinition *)xcodeproj; + +- (void)removeFromProjectReferences:(NSString *)key + forProductsGroup:(NSString *)productsGroupKey; + +- (void)removeTargetDependencies:(NSString *)name; + +- (NSString *)containerItemProxyKeyForName:(NSString *)name + proxyType:(NSString *)proxyType; + +- (NSString *)productsGroupKeyForKey:(NSString *)key; + +@end \ No newline at end of file diff --git a/XcodeZombieCodeAnalyzer/XcodeZombieCodeAnalyzer/Vendor/XcodeEditor/XCProject+SubProject.m b/XcodeZombieCodeAnalyzer/XcodeZombieCodeAnalyzer/Vendor/XcodeEditor/XCProject+SubProject.m new file mode 100644 index 0000000..54656ce --- /dev/null +++ b/XcodeZombieCodeAnalyzer/XcodeZombieCodeAnalyzer/Vendor/XcodeEditor/XCProject+SubProject.m @@ -0,0 +1,452 @@ +//////////////////////////////////////////////////////////////////////////////// +// +// JASPER BLUES +// Copyright 2012 Jasper Blues +// All Rights Reserved. +// +// NOTICE: Jasper Blues permits you to use, modify, and distribute this file +// in accordance with the terms of the license agreement accompanying it. +// +//////////////////////////////////////////////////////////////////////////////// + +#import "Utils/XCKeyBuilder.h" +#import "XCProject+SubProject.h" +#import "XCSourceFile.h" +#import "XCSubProjectDefinition.h" +#import "XCTarget.h" +#import + +@implementation XCProject (SubProject) + +#pragma mark sub-project related public methods + +// returns the key for the reference proxy with the given path (nil if not +// found) +// does not use keysForProjectObjectsOfType:withIdentifier: because the +// identifier it uses for +// PBXReferenceProxy is different. +- (NSString *)referenceProxyKeyForName:(NSString *)name { + __block NSString *result = nil; + [[self objects] enumerateKeysAndObjectsUsingBlock:^( + NSString *key, NSDictionary *obj, BOOL *stop) { + if ([[obj valueForKey:@"isa"] xce_hasReferenceProxyType]) { + NSString *candidate = [obj valueForKey:@"path"]; + if ([candidate isEqualToString:name]) { + result = key; + *stop = YES; + } + } + }]; + return result; +} + +// returns an array of build products, excluding bundles with extensions other +// than ".bundle" (which is kind +// of gross, but I didn't see a better way to exclude test bundles without +// giving them their own XcodeSourceFileType) +- (NSArray *)buildProductsForTargets:(NSString *)xcodeprojKey { + NSMutableArray *results = [[NSMutableArray alloc] init]; + [[self objects] enumerateKeysAndObjectsUsingBlock:^( + NSString *key, NSDictionary *obj, BOOL *stop) { + if ([[obj valueForKey:@"isa"] xce_hasReferenceProxyType]) { + // make sure it belongs to the xcodeproj we're adding + NSString *remoteRef = [obj valueForKey:@"remoteRef"]; + NSDictionary *containerProxy = [[self objects] valueForKey:remoteRef]; + NSString *containerPortal = + [containerProxy valueForKey:@"containerPortal"]; + if ([containerPortal isEqualToString:xcodeprojKey]) { + XcodeSourceFileType type = XCSourceFileTypeFromStringRepresentation( + [obj valueForKey:@"fileType"]); + NSString *path = (NSString *)[obj valueForKey:@"path"]; + if (type != Bundle || + [[path pathExtension] isEqualToString:@"bundle"]) { + [results addObject:[XCSourceFile sourceFileWithProject:self + key:key + type:type + name:path + sourceTree:nil + path:nil]]; + } + } + } + }]; + return results; +} + +// makes PBXContainerItemProxy and PBXTargetDependency objects for the +// xcodeproj, and adds the dependency key +// to all the specified targets +- (void)addAsTargetDependency:(XCSubProjectDefinition *)xcodeprojDefinition + toTargets:(NSArray *)targets { + for (XCTarget *target in targets) { + // make a new PBXContainerItemProxy + NSString *key = [[self + fileWithName:[xcodeprojDefinition pathRelativeToProjectRoot]] key]; + NSString *containerItemProxyKey = + [self makeContainerItemProxyForName:[xcodeprojDefinition name] + fileRef:key + proxyType:@"1" + uniqueName:[target name]]; + // make a PBXTargetDependency + NSString *targetDependencyKey = + [self makeTargetDependency:[xcodeprojDefinition name] + forContainerItemProxyKey:containerItemProxyKey + uniqueName:[target name]]; + // add entry in each targets dependencies list + [target addDependency:targetDependencyKey]; + } +} + +// returns an array of keys for all project objects (not just files) that match +// the given criteria. Since this is +// a convenience method intended to save typing elsewhere, each type has its own +// field to match to rather than each +// matching on name or path as you might expect. +- (NSArray *)keysForProjectObjectsOfType:(XcodeMemberType)memberType + withIdentifier:(NSString *)identifier + singleton:(BOOL)singleton + required:(BOOL)required { + __block NSMutableArray *returnValue = [[NSMutableArray alloc] init]; + [[self objects] enumerateKeysAndObjectsUsingBlock:^( + NSString *key, NSDictionary *obj, BOOL *stop) { + if ([[obj valueForKey:@"isa"] xce_asMemberType] == memberType) { + if (memberType == PBXContainerItemProxyType) { + if ([[obj valueForKey:@"containerPortal"] isEqualToString:identifier]) { + [returnValue addObject:key]; + } + } else if (memberType == PBXReferenceProxyType) { + if ([[obj valueForKey:@"remoteRef"] isEqualToString:identifier]) { + [returnValue addObject:key]; + } + } else if (memberType == PBXTargetDependencyType || + memberType == PBXGroupType || + memberType == PBXVariantGroupType) { + if ([[obj valueForKey:@"name"] isEqualToString:identifier]) { + [returnValue addObject:key]; + } + } else if (memberType == PBXNativeTargetType) { + for (NSString *dependencyKey in [obj valueForKey:@"dependencies"]) { + if ([dependencyKey isEqualToString:identifier]) { + [returnValue addObject:key]; + } + } + } else if (memberType == PBXBuildFileType) { + if ([[obj valueForKey:@"fileRef"] isEqualToString:identifier]) { + [returnValue addObject:key]; + } + } else if (memberType == PBXProjectType) { + [returnValue addObject:key]; + } else if (memberType == PBXFileReferenceType) { + if ([[obj valueForKey:@"path"] isEqualToString:identifier]) { + [returnValue addObject:key]; + } + } else if (memberType == PBXFrameworksBuildPhaseType || + memberType == PBXResourcesBuildPhaseType) { + [returnValue addObject:key]; + } else { + [NSException raise:NSInvalidArgumentException + format:@"Unrecognized member type %@", + [NSString xce_stringFromMemberType:memberType]]; + } + } + }]; + if (singleton && [returnValue count] > 1) { + [NSException raise:NSGenericException + format:@"Searched for one instance of member type %@ with " + @"value %@, but found %ld", + [NSString xce_stringFromMemberType:memberType], + identifier, (unsigned long)[returnValue count]]; + } + if (required && [returnValue count] == 0) { + [NSException + raise:NSGenericException + format:@"Searched for instances of member type %@ with value %@, but " + @"did not find any", + [NSString xce_stringFromMemberType:memberType], identifier]; + } + return returnValue; +} + +// returns the dictionary for the PBXProject. Raises an exception if more or +// less than 1 are found. +- (NSMutableDictionary *)PBXProjectDict { + NSString *PBXProjectKey; + NSArray *PBXProjectKeys = [self keysForProjectObjectsOfType:PBXProjectType + withIdentifier:nil + singleton:YES + required:YES]; + PBXProjectKey = [PBXProjectKeys objectAtIndex:0]; + NSMutableDictionary *PBXProjectDict = + [[self objects] valueForKey:PBXProjectKey]; + return PBXProjectDict; +} + +// returns the key of the PBXContainerItemProxy for the given name and proxy +// type. nil if not found. +- (NSString *)containerItemProxyKeyForName:(NSString *)name + proxyType:(NSString *)proxyType { + NSMutableArray *results = [[NSMutableArray alloc] init]; + [[self objects] enumerateKeysAndObjectsUsingBlock:^( + NSString *key, NSDictionary *obj, BOOL *stop) { + if ([[obj valueForKey:@"isa"] xce_hasContainerItemProxyType]) { + NSString *remoteInfo = [obj valueForKey:@"remoteInfo"]; + NSString *proxy = [obj valueForKey:@"proxyType"]; + if ([remoteInfo isEqualToString:name] && + [proxy isEqualToString:proxyType]) { + [results addObject:key]; + } + } + }]; + if ([results count] > 1) { + [NSException raise:NSGenericException + format:@"Searched for one instance of member type %@ with " + @"value %@, but found %ld", + @"PBXContainerItemProxy", + [NSString stringWithFormat:@"%@ and proxyType of %@", + name, proxyType], + (unsigned long)[results count]]; + } + if ([results count] == 0) { + return nil; + } + return results[0]; +} + +//------------------------------------------------------------------------------------------- +#pragma mark - Private Methods +//------------------------------------------------------------------------------------------- + +// makes a PBXContainerItemProxy object for a given PBXFileReference object. +// Replaces pre-existing objects. +- (NSString *)makeContainerItemProxyForName:(NSString *)name + fileRef:(NSString *)fileRef + proxyType:(NSString *)proxyType + uniqueName:(NSString *)uniqueName { + NSString *keyName; + if (uniqueName != nil) { + keyName = [NSString stringWithFormat:@"%@-%@", name, uniqueName]; + } else { + keyName = name; + } + // remove old if it exists + NSString *existingProxyKey = [self containerItemProxyKeyForName:keyName + proxyType:proxyType]; + if (existingProxyKey) { + [[self objects] removeObjectForKey:existingProxyKey]; + } + // make new one + NSMutableDictionary *proxy = [NSMutableDictionary dictionary]; + proxy[@"isa"] = [NSString xce_stringFromMemberType:PBXContainerItemProxyType]; + proxy[@"containerPortal"] = fileRef; + proxy[@"proxyType"] = proxyType; + // give it a random key - the keys xcode puts here are not in the project file + // anywhere else + NSString *key = [[XCKeyBuilder + forItemNamed:[NSString stringWithFormat:@"%@-junk", keyName]] build]; + proxy[@"remoteGlobalIDString"] = key; + proxy[@"remoteInfo"] = name; + // add to project. use proxyType to generate key, so that multiple keys for + // the same name don't overwrite each other + key = [[XCKeyBuilder + forItemNamed:[NSString stringWithFormat:@"%@-containerProxy-%@", keyName, + proxyType]] build]; + [self objects][key] = proxy; + + return key; +} + +// makes a PBXReferenceProxy object for a given PBXContainerProxy object. +// Replaces pre-existing objects. +- (void)makeReferenceProxyForContainerItemProxy: + (NSString *)containerItemProxyKey + buildProductReference: + (NSDictionary *)buildProductReference { + NSString *path = [buildProductReference valueForKey:@"path"]; + // remove old if any exists + NSArray *existingProxyKeys = + [self keysForProjectObjectsOfType:PBXReferenceProxyType + withIdentifier:path + singleton:NO + required:NO]; + if ([existingProxyKeys count] > 0) { + for (NSString *existingProxyKey in existingProxyKeys) { + [[self objects] removeObjectForKey:existingProxyKey]; + } + } + // make new one + NSMutableDictionary *proxy = [NSMutableDictionary dictionary]; + proxy[@"isa"] = [NSString xce_stringFromMemberType:PBXReferenceProxyType]; + proxy[@"fileType"] = [buildProductReference valueForKey:@"explicitFileType"]; + proxy[@"path"] = path; + proxy[@"remoteRef"] = containerItemProxyKey; + proxy[@"sourceTree"] = [buildProductReference valueForKey:@"sourceTree"]; + // add to project + NSString *key = [[XCKeyBuilder + forItemNamed:[NSString stringWithFormat:@"%@-referenceProxy", path]] + build]; + [self objects][key] = proxy; +} + +// makes a PBXTargetDependency object for a given PBXContainerItemProxy. +// Replaces pre-existing objects. +- (NSString *)makeTargetDependency:(NSString *)name + forContainerItemProxyKey:(NSString *)containerItemProxyKey + uniqueName:(NSString *)uniqueName { + NSString *keyName; + if (uniqueName != nil) { + keyName = [NSString stringWithFormat:@"%@-%@", name, uniqueName]; + } else { + keyName = name; + } + // remove old if it exists + NSArray *existingDependencyKeys = + [self keysForProjectObjectsOfType:PBXTargetDependencyType + withIdentifier:keyName + singleton:NO + required:NO]; + if ([existingDependencyKeys count] > 0) { + for (NSString *existingDependencyKey in existingDependencyKeys) { + [[self objects] removeObjectForKey:existingDependencyKey]; + } + } + // make new one + NSMutableDictionary *targetDependency = [NSMutableDictionary dictionary]; + targetDependency[@"isa"] = + [NSString xce_stringFromMemberType:PBXTargetDependencyType]; + targetDependency[@"name"] = name; + targetDependency[@"targetProxy"] = containerItemProxyKey; + NSString *targetDependencyKey = [[XCKeyBuilder + forItemNamed:[NSString stringWithFormat:@"%@-targetProxy", keyName]] + build]; + [self objects][targetDependencyKey] = targetDependency; + return targetDependencyKey; +} + +// make a PBXContainerItemProxy and PBXReferenceProxy for each target in the +// subProject +- (void)addProxies:(XCSubProjectDefinition *)xcodeproj { + NSString *fileRef = + [[self fileWithName:[xcodeproj pathRelativeToProjectRoot]] key]; + for (NSDictionary *target in [xcodeproj.subProject targets]) { + NSString *containerItemProxyKey = + [self makeContainerItemProxyForName:[target valueForKey:@"name"] + fileRef:fileRef + proxyType:@"2" + uniqueName:nil]; + NSString *productFileReferenceKey = + [target valueForKey:@"productReference"]; + NSDictionary *productFileReference = + [[xcodeproj.subProject objects] valueForKey:productFileReferenceKey]; + [self makeReferenceProxyForContainerItemProxy:containerItemProxyKey + buildProductReference:productFileReference]; + } +} + +// remove the PBXContainerItemProxy and PBXReferenceProxy objects for the given +// object key (which is the PBXFilereference +// for the xcodeproj file) +- (void)removeProxies:(NSString *)xcodeprojKey { + NSMutableArray *keysToDelete = [NSMutableArray array]; + // use the xcodeproj's PBXFileReference key to get the PBXContainerItemProxy + // keys + NSArray *containerItemProxyKeys = + [self keysForProjectObjectsOfType:PBXContainerItemProxyType + withIdentifier:xcodeprojKey + singleton:NO + required:YES]; + // use the PBXContainerItemProxy keys to get the PBXReferenceProxy keys + for (NSString *key in containerItemProxyKeys) { + [keysToDelete addObjectsFromArray: + [self keysForProjectObjectsOfType:PBXReferenceProxyType + withIdentifier:key + singleton:NO + required:NO]]; + [keysToDelete addObject:key]; + } + // remove all objects located above + [keysToDelete + enumerateObjectsUsingBlock:^(id obj, NSUInteger idx, BOOL *stop) { + [[self objects] removeObjectForKey:obj]; + }]; +} + +// returns the Products _group key for the given PBXFileReference key, nil if +// not found. +- (NSString *)productsGroupKeyForKey:(NSString *)key { + NSMutableArray *projectReferences = + [[self PBXProjectDict] valueForKey:@"projectReferences"]; + NSString *productsGroupKey = nil; + for (NSDictionary *projectRef in projectReferences) { + if ([[projectRef valueForKey:@"ProjectRef"] isEqualToString:key]) { + // it's an error if we find more than one + if (productsGroupKey != nil) { + [NSException + raise:NSGenericException + format:@"Found more than one project reference for key %@", key]; + } + productsGroupKey = [projectRef valueForKey:@"ProductGroup"]; + } + } + return productsGroupKey; +} + +// removes a file reference from the projectReferences array in PBXProject +// (removing the array itself if this action +// leaves it empty). +- (void)removeFromProjectReferences:(NSString *)key + forProductsGroup:(NSString *)productsGroupKey { + NSMutableArray *projectReferences = + [[self PBXProjectDict] valueForKey:@"projectReferences"]; + // remove entry from PBXProject's projectReferences + NSMutableArray *referencesToRemove = [NSMutableArray array]; + for (NSDictionary *projectRef in projectReferences) { + if ([[projectRef valueForKey:@"ProjectRef"] isEqualToString:key]) { + [referencesToRemove addObject:projectRef]; + } + } + for (NSDictionary *projectRef in referencesToRemove) { + [projectReferences removeObject:projectRef]; + } + // if that was the last project reference, remove the array from the project + if ([projectReferences count] == 0) { + [[self PBXProjectDict] removeObjectForKey:@"projectReferences"]; + } +} + +// removes a specific xcodeproj file from any targets (by name). It's not an +// error if no entries are found, +// because we support adding a project file without adding it to any targets. +- (void)removeTargetDependencies:(NSString *)name { + // get the key for the PBXTargetDependency with name = xcodeproj file name + // (without extension) + NSArray *targetDependencyKeys = + [self keysForProjectObjectsOfType:PBXTargetDependencyType + withIdentifier:name + singleton:NO + required:NO]; + // we might not find any if the project wasn't added to targets in the first + // place + if ([targetDependencyKeys count] == 0) { + return; + } + NSString *targetDependencyKey = targetDependencyKeys[0]; + // use the key for the PBXTargetDependency to get the key for any + // PBXNativeTargets that depend on it + NSArray *nativeTargetKeys = + [self keysForProjectObjectsOfType:PBXNativeTargetType + withIdentifier:targetDependencyKey + singleton:NO + required:NO]; + // remove the key for the PBXTargetDependency from the PBXNativeTarget's + // dependencies arrays (leave in place even if empty) + for (NSString *nativeTargetKey in nativeTargetKeys) { + NSMutableDictionary *nativeTarget = [self objects][nativeTargetKey]; + NSMutableArray *dependencies = [nativeTarget valueForKey:@"dependencies"]; + [dependencies removeObject:targetDependencyKey]; + nativeTarget[@"dependencies"] = dependencies; + } + // remove the PBXTargetDependency + [[self objects] removeObjectForKey:targetDependencyKey]; +} + +@end \ No newline at end of file diff --git a/XcodeZombieCodeAnalyzer/XcodeZombieCodeAnalyzer/Vendor/XcodeEditor/XCProject.h b/XcodeZombieCodeAnalyzer/XcodeZombieCodeAnalyzer/Vendor/XcodeEditor/XCProject.h new file mode 100644 index 0000000..c5ab22b --- /dev/null +++ b/XcodeZombieCodeAnalyzer/XcodeZombieCodeAnalyzer/Vendor/XcodeEditor/XCProject.h @@ -0,0 +1,233 @@ +//////////////////////////////////////////////////////////////////////////////// +// +// JASPER BLUES +// Copyright 2012 - 2013 Jasper Blues +// All Rights Reserved. +// +// NOTICE: Jasper Blues permits you to use, modify, and distribute this file +// in accordance with the terms of the license agreement accompanying it. +// +//////////////////////////////////////////////////////////////////////////////// + +#import "XcodeGroupMember.h" +#import "XcodeMemberType.h" +#import "XcodeSourceFileType.h" +#import + +@class XCClassDefinition; +@class XCGroup; +@class XCFileOperationQueue; +@class XCSourceFile; +@class XCTarget; +@class XCSubProjectDefinition; +@class XCProjectBuildConfig; +@class XCVersionGroup; + +@interface XCProject : NSObject { +@protected + XCFileOperationQueue *_fileOperationQueue; + + NSString *_filePath; + NSMutableDictionary *_dataStore; + NSMutableArray *_targets; + + NSMutableDictionary *_groups; + NSMutableDictionary *_versionGroups; + NSMutableDictionary *_configurations; + + NSString *_defaultConfigurationName; + NSString *_rootObjectKey; +} + +@property(nonatomic, strong, readonly) XCFileOperationQueue *fileOperationQueue; + +//------------------------------------------------------------------------------------------- +#pragma mark - Initialization & Destruction +//------------------------------------------------------------------------------------------- + ++ (XCProject *)projectWithFilePath:(NSString *)filePath; + +/** + * Creates a new project editor instance with the specified Project.xcodeproj + * file. + */ +- (id)initWithFilePath:(NSString *)filePath; + +//------------------------------------------------------------------------------------------- +#pragma mark Groupd Member +- (id)groupMemberWithKey:(NSString *)key; + +//------------------------------------------------------------------------------------------- +#pragma mark Files +/** + * Returns all file resources in the project, as an array of `XCSourceFile` + * objects. + */ +- (NSArray *)files; + +/** + * Returns the project file with the specified key, or nil. + */ +- (XCSourceFile *)fileWithKey:(NSString *)key; + +/** + * Returns the project file with the specified name, or nil. If more than one + * project file matches the specified name, + * which one is returned is undefined. + */ +- (XCSourceFile *)fileWithName:(NSString *)name; + +/** + * Returns all header files in the project, as an array of `XCSourceFile` + * objects. + */ +- (NSArray *)headerFiles; + +/** + * Returns all implementation obj-c implementation files in the project, as an + * array of `XCSourceFile` objects. + */ +- (NSArray *)objectiveCFiles; + +/** + * Returns all implementation obj-c++ implementation files in the project, as an + * array of `XCSourceFile` objects. + */ +- (NSArray *)objectiveCPlusPlusFiles; + +/** + * Returns all the xib files in the project, as an array of `XCSourceFile` + * objects. + */ +- (NSArray *)xibFiles; + +- (NSArray *)imagePNGFiles; + +- (NSString *)filePath; + +//------------------------------------------------------------------------------------------- +#pragma mark Groups +/** + * Lists the groups in an xcode project, returning an array of `XCGroup` + * objects. + */ +- (NSArray *)groups; + +/** + * Returns the root (top-level) _group. + */ +- (XCGroup *)rootGroup; + +/** + * Returns the root (top-level) groups, if there are multiple. An array of + * rootGroup if there is only one. + */ +- (NSArray *)rootGroups; + +/** + * Returns the main group under root object + */ +- (XCGroup *)mainGroup; + +/** + * Returns the group with the given key, or nil. + */ +- (XCGroup *)groupWithKey:(NSString *)key; + +/** + * Returns the _first_ group in the project with the given name, or nil. + */ +- (XCGroup *)groupWithDisplayName:(NSString *)name; + +/** + * Returns the _group with the specified display name path - the directory + * relative to the root _group. Eg Source/Main + */ +- (XCGroup *)groupWithPathFromRoot:(NSString *)path; + +/** + * Returns the parent _group for the _group or file with the given key; + */ +- (XCGroup *)groupForGroupMemberWithKey:(NSString *)key; + +/** + * Returns the parent group for the group or file with the source file + */ +- (XCGroup *)groupWithSourceFile:(XCSourceFile *)sourceFile; + +/** + * Removes all empty groups from the project. + */ +- (void)pruneEmptyGroups; + +//------------------------------------------------------------------------------------------- +#pragma mark VersionGroups +/** + * Lists the version groups in an xcode project, returning an array of + * `XCVersionGroup` objects. + */ +- (NSArray *)versionGroups; + +/** + * Returns the version group with the given key, or nil. + */ +- (XCVersionGroup *)versionGroupWithKey:(NSString *)key; + +/** + * Returns the version group with the given file name, or nil. + */ +- (XCVersionGroup *)versionGroupWithName:(NSString *)name; + +//------------------------------------------------------------------------------------------- +#pragma mark Targets +/** + * Lists the targets in an xcode project, returning an array of `XCTarget` + * objects. + */ +- (NSArray *)targets; + +/** + * Returns the target with the specified name, or nil. + */ +- (XCTarget *)targetWithName:(NSString *)name; + +/** + * Lists the targets which are of application type. + */ +- (NSArray *)applicationTargets; + +#pragma mark Configurations +/** + * Lists the configurations in an xcode project. + */ +- (NSDictionary *)configurations; +/** + * Returns the configuration with the specified name, or nil. + */ +- (XCProjectBuildConfig *)configurationWithName:(NSString *)name; + +- (XCProjectBuildConfig *)defaultConfiguration; + +//------------------------------------------------------------------------------------------- +#pragma mark Deletion + +- (void)removeObjectWithKey:(NSString *)key; + +//------------------------------------------------------------------------------------------- +#pragma mark Saving +/** + * Saves a project after editing. + */ +- (void)save; + +//------------------------------------------------------------------------------------------- +/** + * Raw project data. + */ +- (NSMutableDictionary *)objects; + +- (NSMutableDictionary *)dataStore; + +- (void)dropCache; + +@end diff --git a/XcodeZombieCodeAnalyzer/XcodeZombieCodeAnalyzer/Vendor/XcodeEditor/XCProject.m b/XcodeZombieCodeAnalyzer/XcodeZombieCodeAnalyzer/Vendor/XcodeEditor/XCProject.m new file mode 100644 index 0000000..eddb605 --- /dev/null +++ b/XcodeZombieCodeAnalyzer/XcodeZombieCodeAnalyzer/Vendor/XcodeEditor/XCProject.m @@ -0,0 +1,525 @@ +//////////////////////////////////////////////////////////////////////////////// +// +// JASPER BLUES +// Copyright 2012 - 2013 Jasper Blues +// All Rights Reserved. +// +// NOTICE: Jasper Blues permits you to use, modify, and distribute this file +// in accordance with the terms of the license agreement accompanying it. +// +//////////////////////////////////////////////////////////////////////////////// + +#import "XCProject.h" +#import "XCFileOperationQueue.h" +#import "XCGroup.h" +#import "XCProjectBuildConfig.h" +#import "XCSourceFile.h" +#import "XCTarget.h" +#import "XCVersionGroup.h" + +NSString *const XCProjectNotFoundException; + +@implementation XCProject + +@synthesize fileOperationQueue = _fileOperationQueue; + +//------------------------------------------------------------------------------------------- +#pragma mark - Class Methods +//------------------------------------------------------------------------------------------- + ++ (XCProject *)projectWithFilePath:(NSString *)filePath { + return [[XCProject alloc] initWithFilePath:filePath]; +} + +//------------------------------------------------------------------------------------------- +#pragma mark - Initialization & Destruction +//------------------------------------------------------------------------------------------- + +- (id)initWithFilePath:(NSString *)filePath { + if ((self = [super init])) { + _filePath = [filePath copy]; + _dataStore = [[NSMutableDictionary alloc] + initWithContentsOfFile: + [_filePath stringByAppendingPathComponent:@"project.pbxproj"]]; + + if (!_dataStore) { + [NSException raise:XCProjectNotFoundException + format:@"Project file not found at file path %@", _filePath]; + } + + _fileOperationQueue = [[XCFileOperationQueue alloc] + initWithBaseDirectory:[_filePath stringByDeletingLastPathComponent]]; + } + return self; +} + +//------------------------------------------------------------------------------------------- +#pragma mark - Interface Methods +//------------------------------------------------------------------------------------------- + +#pragma mark General Group member + +- (id)groupMemberWithKey:(NSString *)key { + NSDictionary *obj = [[self objects] valueForKey:key]; + + if (obj) { + NSString *groupIsa = [obj valueForKey:@"isa"]; + if ([groupIsa xce_hasFileReferenceOrReferenceProxyType]) { + return [self fileWithKey:key]; + } else if ([groupIsa xce_hasVersionedGroupType]) { + return [self versionGroupWithKey:key]; + } else if ([groupIsa xce_hasGroupType]) { + return [self groupWithKey:key]; + } + } + return nil; +} + +#pragma mark Files + +- (NSArray *)files { + NSMutableArray *results = [NSMutableArray array]; + [[self objects] enumerateKeysAndObjectsUsingBlock:^( + NSString *key, NSDictionary *obj, BOOL *stop) { + if ([[obj valueForKey:@"isa"] xce_hasFileReferenceType]) { + XcodeSourceFileType fileType = XCSourceFileTypeFromStringRepresentation( + [obj valueForKey:@"lastKnownFileType"] + ?: [obj valueForKey:@"explicitFileType"]); + NSString *path = [obj valueForKey:@"path"]; + NSString *sourceTree = [obj valueForKey:@"sourceTree"]; + XCSourceFile *sourceFile = [XCSourceFile + sourceFileWithProject:self + key:key + type:fileType + name:path + sourceTree:(sourceTree ?: @"<_group>")path:nil]; + [results addObject:sourceFile]; + } + }]; + return results; +} + +- (XCSourceFile *)fileWithKey:(NSString *)key { + NSDictionary *obj = [[self objects] valueForKey:key]; + if (obj && + [[obj valueForKey:@"isa"] xce_hasFileReferenceOrReferenceProxyType]) { + XcodeSourceFileType fileType = XCSourceFileTypeFromStringRepresentation( + [obj valueForKey:@"lastKnownFileType"] + ?: [obj valueForKey:@"explicitFileType"]); + + NSString *name = [obj valueForKey:@"name"]; + NSString *sourceTree = [obj valueForKey:@"sourceTree"]; + NSString *path = [obj valueForKey:@"path"]; + + if (name == nil) { + name = path; + } + return [XCSourceFile + sourceFileWithProject:self + key:key + type:fileType + name:name + sourceTree:(sourceTree ?: @"<_group>")path:path]; + } + return nil; +} + +- (XCSourceFile *)fileWithName:(NSString *)name { + for (XCSourceFile *projectFile in [self files]) { + if ([[projectFile name] isEqualToString:name]) { + return projectFile; + } + } + return nil; +} + +- (NSArray *)headerFiles { + return [self projectFilesOfType:SourceCodeHeader]; +} + +- (NSArray *)objectiveCFiles { + return [self projectFilesOfType:SourceCodeObjC]; +} + +- (NSArray *)objectiveCPlusPlusFiles { + return [self projectFilesOfType:SourceCodeObjCPlusPlus]; +} + +- (NSArray *)xibFiles { + return [self projectFilesOfType:XibFile]; +} + +- (NSArray *)imagePNGFiles { + return [self projectFilesOfType:ImageResourcePNG]; +} + +// need this value to construct relative path in XcodeprojDefinition +- (NSString *)filePath { + return _filePath; +} + +//------------------------------------------------------------------------------------------- +#pragma mark Groups +//------------------------------------------------------------------------------------------- + +- (NSArray *)groups { + NSMutableArray *results = [[NSMutableArray alloc] init]; + [[self objects] enumerateKeysAndObjectsUsingBlock:^( + NSString *key, NSDictionary *obj, BOOL *stop) { + if ([[obj valueForKey:@"isa"] xce_hasGroupType]) { + XCGroup *group = _groups[key]; + if (group == nil) { + group = [self createGroupWithDictionary:obj forKey:key]; + _groups[key] = group; + } + [results addObject:group]; + } + }]; + return results; +} + +// TODO: Optimize this implementation. +- (XCGroup *)rootGroup { + for (XCGroup *group in [self groups]) { + if ([group isRootGroup]) { + return group; + } + } + return nil; +} + +- (NSArray *)rootGroups { + XCGroup *group = [self rootGroup]; + if (group) { + return [NSArray arrayWithObject:group]; + } + + NSMutableArray *results = [NSMutableArray array]; + for (XCGroup *group in [self groups]) { + if ([group parentGroup] == nil) { + [results addObject:group]; + } + } + + return [results copy]; +} + +- (XCGroup *)mainGroup { + NSString *rootObjectKey = [self rootObjectKey]; + NSDictionary *rootObject = [[self objects] objectForKey:rootObjectKey]; + NSString *mainGroupKey = [rootObject objectForKey:@"mainGroup"]; + for (XCGroup *group in [self groups]) { + if ([group.key isEqualToString:mainGroupKey]) + return group; + } + + return nil; +} + +- (XCGroup *)groupWithKey:(NSString *)key { + XCGroup *group = [_groups objectForKey:key]; + if (group) { + return group; + } + + NSDictionary *obj = [[self objects] objectForKey:key]; + if (obj && [[obj valueForKey:@"isa"] xce_hasGroupType]) { + XCGroup *group = [self createGroupWithDictionary:obj forKey:key]; + _groups[key] = group; + + return group; + } + return nil; +} + +- (XCGroup *)groupWithDisplayName:(NSString *)name { + for (XCGroup *group in [self groups]) { + if ([[group displayName] isEqualToString:name]) { + return group; + } + } + return nil; +} + +- (XCGroup *)groupForGroupMemberWithKey:(NSString *)key { + for (XCGroup *group in [self groups]) { + if ([group memberWithKey:key]) { + return group; + } + } + return nil; +} + +- (XCGroup *)groupWithSourceFile:(XCSourceFile *)sourceFile { + for (XCGroup *group in [self groups]) { + for (id member in [group members]) { + if ([member isKindOfClass:[XCSourceFile class]] && + [[sourceFile key] isEqualToString:[member key]]) { + return group; + } + } + } + return nil; +} + +- (void)pruneEmptyGroups { + [self doPruneEmptyGroups]; +} + +// TODO: This could fail if the path attribute on a given group is more than one +// directory. Start with candidates and +// TODO: search backwards. +- (XCGroup *)groupWithPathFromRoot:(NSString *)path { + NSArray *pathItems = [path pathComponents]; + XCGroup *currentGroup = [self rootGroup]; + for (NSString *pathItem in pathItems) { + id group = [currentGroup memberWithDisplayName:pathItem]; + if ([group isKindOfClass:[XCGroup class]]) { + currentGroup = group; + } else { + return nil; + } + } + return currentGroup; +} + +- (XCGroup *)createGroupWithDictionary:(NSDictionary *)dictionary + forKey:(NSString *)key { + return [XCGroup + groupWithProject:self + key:key + alias:[dictionary valueForKey:@"name"] + path:[dictionary valueForKey:@"path"] + children:[dictionary valueForKey:@"children"] + memberType:[[dictionary valueForKey:@"isa"] xce_asMemberType]]; +} + +//------------------------------------------------------------------------------------------- +#pragma mark version group +//------------------------------------------------------------------------------------------- + +- (NSArray *)versionGroups { + NSMutableArray *results = [[NSMutableArray alloc] init]; + [[self objects] enumerateKeysAndObjectsUsingBlock:^( + NSString *key, NSDictionary *obj, BOOL *stop) { + if ([[obj valueForKey:@"isa"] xce_hasVersionedGroupType]) { + XCVersionGroup *group = _versionGroups[key]; + if (group == nil) { + group = [self createVersionGroupWithDictionary:obj forKey:key]; + _versionGroups[key] = group; + } + [results addObject:group]; + } + }]; + return results; +} + +- (XCVersionGroup *)versionGroupWithKey:(NSString *)key { + XCVersionGroup *group = [_versionGroups objectForKey:key]; + if (group) { + return group; + } + + NSDictionary *obj = [[self objects] objectForKey:key]; + if (obj && [[obj valueForKey:@"isa"] xce_hasVersionedGroupType]) { + XCVersionGroup *group = [self createVersionGroupWithDictionary:obj + forKey:key]; + _versionGroups[key] = group; + + return group; + } + return nil; +} + +- (XCVersionGroup *)versionGroupWithName:(NSString *)name { + for (XCVersionGroup *group in [self versionGroups]) { + if ([[[group pathRelativeToParent] stringByDeletingPathExtension] + isEqualToString:name]) + return group; + } + return nil; +} + +- (XCVersionGroup *)createVersionGroupWithDictionary:(NSDictionary *)dictionary + forKey:(NSString *)key { + return [XCVersionGroup + versionGroupWithProject:self + key:key + path:[dictionary valueForKey:@"path"] + children:[dictionary valueForKey:@"children"] + currentVersion:[dictionary valueForKey:@"currentVersion"]]; +} + +//------------------------------------------------------------------------------------------- +#pragma mark targets +//------------------------------------------------------------------------------------------- + +- (NSArray *)targets { + if (_targets == nil) { + _targets = [[NSMutableArray alloc] init]; + [[self objects] enumerateKeysAndObjectsUsingBlock:^( + NSString *key, NSDictionary *obj, BOOL *stop) { + if ([[obj valueForKey:@"isa"] xce_hasNativeTargetType]) { + XCTarget *target = + [XCTarget targetWithProject:self + key:key + name:[obj valueForKey:@"name"] + productName:[obj valueForKey:@"productName"] + productReference:[obj valueForKey:@"productReference"] + productType:[obj valueForKey:@"productType"]]; + [_targets addObject:target]; + } + }]; + } + return _targets; +} + +- (XCTarget *)targetWithName:(NSString *)name { + for (XCTarget *target in [self targets]) { + if ([[target name] isEqualToString:name]) { + return target; + } + } + return nil; +} + +- (NSArray *)applicationTargets { + NSArray *targets = [self targets]; + NSArray *filteredTargets = [targets + filteredArrayUsingPredicate:[NSPredicate predicateWithBlock:^BOOL( + id _Nonnull evaluatedObject, + NSDictionary + *_Nullable bindings) { + return [evaluatedObject isApplicationType]; + }]]; + + return filteredTargets; +} + +- (NSData *)_fixEncodingInData:(NSData *)data { + NSString *source = [[NSString alloc] initWithData:data + encoding:NSUTF8StringEncoding]; + NSMutableString *destination = @"".mutableCopy; + for (int i = 0; i < source.length; i++) { + unichar c = [source characterAtIndex:i]; + if (c < 128) { + [destination appendFormat:@"%c", c]; + } else { + [destination appendFormat:@"&#%u;", c]; + } + } + + return [destination dataUsingEncoding:NSUTF8StringEncoding]; +} + +- (void)save { + [_fileOperationQueue commitFileOperations]; + + NSData *data = [NSPropertyListSerialization + dataWithPropertyList:_dataStore + format:NSPropertyListXMLFormat_v1_0 + options:0 + error:NULL]; + data = [self _fixEncodingInData:data]; + [data + writeToFile:[_filePath stringByAppendingPathComponent:@"project.pbxproj"] + atomically:YES]; + + // Don't forget to reset the cache so that we'll always get the latest data. + [self dropCache]; + + NSLog(@"Saved project"); +} + +- (NSMutableDictionary *)objects { + return [_dataStore objectForKey:@"objects"]; +} + +- (NSMutableDictionary *)dataStore { + return _dataStore; +} + +- (void)dropCache { + _targets = nil; + _configurations = nil; + _rootObjectKey = nil; +} + +- (NSDictionary *)configurations { + if (_configurations == nil) { + NSString *buildConfigurationRootSectionKey = + [[[self objects] objectForKey:[self rootObjectKey]] + objectForKey:@"buildConfigurationList"]; + NSDictionary *buildConfigurationDictionary = + [[self objects] objectForKey:buildConfigurationRootSectionKey]; + _configurations = [[XCProjectBuildConfig + buildConfigurationsFromArray:[buildConfigurationDictionary + objectForKey:@"buildConfigurations"] + inProject:self] mutableCopy]; + _defaultConfigurationName = [[buildConfigurationDictionary + objectForKey:@"defaultConfigurationName"] copy]; + } + + return [_configurations copy]; +} + +- (XCProjectBuildConfig *)configurationWithName:(NSString *)name { + return [[self configurations] objectForKey:name]; +} + +- (XCProjectBuildConfig *)defaultConfiguration { + return [[self configurations] objectForKey:_defaultConfigurationName]; +} + +//------------------------------------------------------------------------------------------- +#pragma mark Deletion + +- (void)removeObjectWithKey:(NSString *)key { + if ([self.objects valueForKey:key]) { + XCGroup *group; + if ((group = [self groupForGroupMemberWithKey:key]) != nil) { + [group removeMemberWithKey:key]; + } + [self.objects removeObjectForKey:key]; + } +} + +//------------------------------------------------------------------------------------------- +#pragma mark Private +//------------------------------------------------------------------------------------------- + +- (NSString *)rootObjectKey { + if (_rootObjectKey == nil) { + _rootObjectKey = [[_dataStore objectForKey:@"rootObject"] copy]; + ; + } + + return _rootObjectKey; +} + +- (NSArray *)projectFilesOfType:(XcodeSourceFileType)projectFileType { + NSMutableArray *results = [NSMutableArray array]; + for (XCSourceFile *file in [self files]) { + if ([file type] == projectFileType) { + [results addObject:file]; + } + } + return results; +} + +- (BOOL)doPruneEmptyGroups { + BOOL hadEmptyGroups = NO; + for (XCGroup *group in [self groups]) { + if ([group isEmpty]) { + hadEmptyGroups = YES; + [group removeFromParentGroup]; + } + } + // Prune any groups made empty as a result of pruning a child group. + if (hadEmptyGroups) { + [self doPruneEmptyGroups]; + } + return hadEmptyGroups; +} + +@end diff --git a/XcodeZombieCodeAnalyzer/XcodeZombieCodeAnalyzer/Vendor/XcodeEditor/XCProjectBuildConfig.h b/XcodeZombieCodeAnalyzer/XcodeZombieCodeAnalyzer/Vendor/XcodeEditor/XCProjectBuildConfig.h new file mode 100644 index 0000000..948680c --- /dev/null +++ b/XcodeZombieCodeAnalyzer/XcodeZombieCodeAnalyzer/Vendor/XcodeEditor/XCProjectBuildConfig.h @@ -0,0 +1,48 @@ +//////////////////////////////////////////////////////////////////////////////// +// +// JASPER BLUES +// Copyright 2012 Jasper Blues +// All Rights Reserved. +// +// NOTICE: Jasper Blues permits you to use, modify, and distribute this file +// in accordance with the terms of the license agreement accompanying it. +// +//////////////////////////////////////////////////////////////////////////////// + +#import + +@class XCProject; + +@interface XCProjectBuildConfig : NSObject { +@private + __weak XCProject *_project; + NSString *_key; + + NSMutableDictionary *_buildSettings; + NSMutableDictionary *_xcconfigSettings; +} +@property(nonatomic, strong, readonly) NSString *key; +@property(nonatomic, readonly) NSDictionary *specifiedBuildSettings; + ++ (NSDictionary *) + buildConfigurationsFromArray:(NSArray *)array + inProject:(XCProject *)project; + +- (instancetype)initWithProject:(XCProject *)project key:(NSString *)key; + +- (void)addBuildSettings:(NSDictionary *)buildSettings; + +- (void)addOrReplaceSetting:(id)setting forKey:(NSString *)key; + +- (id)valueForKey:(NSString *)key; + +- (void)removeSettingByKey:(NSString *)key; + ++ (NSString *)duplicatedBuildConfigurationListWithKey: + (NSString *)buildConfigurationListKey + inProject:(XCProject *)project + withBuildConfigurationVisitor: + (void (^)(NSMutableDictionary *)) + buildConfigurationVisitor; + +@end diff --git a/XcodeZombieCodeAnalyzer/XcodeZombieCodeAnalyzer/Vendor/XcodeEditor/XCProjectBuildConfig.m b/XcodeZombieCodeAnalyzer/XcodeZombieCodeAnalyzer/Vendor/XcodeEditor/XCProjectBuildConfig.m new file mode 100644 index 0000000..3022ed7 --- /dev/null +++ b/XcodeZombieCodeAnalyzer/XcodeZombieCodeAnalyzer/Vendor/XcodeEditor/XCProjectBuildConfig.m @@ -0,0 +1,214 @@ +//////////////////////////////////////////////////////////////////////////////// +// +// JASPER BLUES +// Copyright 2012 Jasper Blues +// All Rights Reserved. +// +// NOTICE: Jasper Blues permits you to use, modify, and distribute this file +// in accordance with the terms of the license agreement accompanying it. +// +//////////////////////////////////////////////////////////////////////////////// + +#import "XCProjectBuildConfig.h" +#import "XCGroup.h" +#import "XCKeyBuilder.h" +#import "XCProject.h" +#import "XCSourceFile.h" + +@implementation XCProjectBuildConfig + +/* ====================================================================================================================================== + */ +#pragma mark - Class Methods + ++ (NSDictionary *)buildConfigurationsFromArray:(NSArray *)array + inProject:(XCProject *)project { + NSMutableDictionary *configurations = [NSMutableDictionary dictionary]; + + for (NSString *buildConfigurationKey in array) { + NSDictionary *buildConfiguration = + [[project objects] objectForKey:buildConfigurationKey]; + + if ([[buildConfiguration valueForKey:@"isa"] + xce_hasBuildConfigurationType]) { + XCProjectBuildConfig *configuration = [configurations + objectForKey:[buildConfiguration objectForKey:@"name"]]; + if (!configuration) { + configuration = [[XCProjectBuildConfig alloc] + initWithProject:project + key:buildConfigurationKey]; + + [configurations setObject:configuration + forKey:[buildConfiguration objectForKey:@"name"]]; + } + + XCSourceFile *configurationFile = + [project fileWithKey:[buildConfiguration + objectForKey:@"baseConfigurationReference"]]; + if (configurationFile) { + NSString *path = configurationFile.path; + + if (![[NSFileManager defaultManager] fileExistsAtPath:path]) { + XCGroup *group = [project groupWithSourceFile:configurationFile]; + do { + path = [[group pathRelativeToParent] + stringByAppendingPathComponent:path] + ?: path; + group = [group parentGroup]; + } while (group); + } + + if (![[NSFileManager defaultManager] fileExistsAtPath:path]) { + path = [[[project filePath] stringByDeletingLastPathComponent] + stringByAppendingPathComponent:path]; + } + + if (![[NSFileManager defaultManager] fileExistsAtPath:path]) { + path = [[[project filePath] stringByDeletingLastPathComponent] + stringByAppendingPathComponent:configurationFile.path]; + } + + if (![[NSFileManager defaultManager] fileExistsAtPath:path]) { + NSLog(@"XCConfig not found. Unable to find XCConfig file at %@", + path); + } + } + + [configuration + addBuildSettings:[buildConfiguration objectForKey:@"buildSettings"]]; + } + } + + return configurations; +} + ++ (NSString *)duplicatedBuildConfigurationListWithKey: + (NSString *)buildConfigurationListKey + inProject:(XCProject *)project + withBuildConfigurationVisitor: + (void (^)(NSMutableDictionary *)) + buildConfigurationVisitor { + + NSDictionary *buildConfigurationList = + project.objects[buildConfigurationListKey]; + NSMutableDictionary *dupBuildConfigurationList = + [buildConfigurationList mutableCopy]; + + NSMutableArray *dupBuildConfigurations = [NSMutableArray array]; + + for (NSString *buildConfigurationKey in + buildConfigurationList[@"buildConfigurations"]) { + [dupBuildConfigurations + addObject: + [self + duplicatedBuildConfigurationWithKey:buildConfigurationKey + inProject:project + withBuildConfigurationVisitor:buildConfigurationVisitor]]; + } + + dupBuildConfigurationList[@"buildConfigurations"] = dupBuildConfigurations; + + NSString *dupBuildConfigurationListKey = [[XCKeyBuilder createUnique] build]; + + project.objects[dupBuildConfigurationListKey] = dupBuildConfigurationList; + + return dupBuildConfigurationListKey; +} + +/* ====================================================================================================================================== + */ +#pragma mark - Initialization & Destruction + +- (instancetype)initWithProject:(XCProject *)project key:(NSString *)key { + self = [super init]; + if (self) { + _project = project; + _key = [key copy]; + + _buildSettings = [[NSMutableDictionary alloc] init]; + _xcconfigSettings = [[NSMutableDictionary alloc] init]; + } + return self; +} + +- (id)init { + return [self initWithProject:nil key:nil]; +} + +/* ====================================================================================================================================== + */ +#pragma mark - Interface Methods + +- (NSDictionary *)specifiedBuildSettings { + return [_buildSettings copy]; +} + +- (void)addBuildSettings:(NSDictionary *)buildSettings { + [_xcconfigSettings removeObjectsForKeys:[buildSettings allKeys]]; + [_buildSettings addEntriesFromDictionary:buildSettings]; +} + +- (void)addOrReplaceSetting:(id)setting forKey:(NSString *)key { + NSDictionary *settings = [NSDictionary dictionaryWithObject:setting + forKey:key]; + [self addBuildSettings:settings]; + + NSMutableDictionary *dict = + [[[_project objects] objectForKey:_key] mutableCopy]; + [dict setValue:_buildSettings forKey:@"buildSettings"]; + [_project.objects setValue:dict forKey:_key]; +} + +- (id)valueForKey:(NSString *)key { + id value = [_buildSettings objectForKey:key]; + if (!value) { + value = [_xcconfigSettings objectForKey:key]; + } + return value; +} + +- (void)removeSettingByKey:(NSString *)key { + [_xcconfigSettings removeObjectForKey:key]; + [_buildSettings removeObjectForKey:key]; + + NSMutableDictionary *dict = + [[[_project objects] objectForKey:_key] mutableCopy]; + [dict setValue:_buildSettings forKey:@"buildSettings"]; + [_project.objects setValue:dict forKey:_key]; +} + +/* ====================================================================================================================================== + */ +#pragma mark - Utility Methods + +- (NSString *)description { + NSMutableString *description = [[super description] mutableCopy]; + + [description appendFormat:@"build settings: %@, inherited: %@", + _buildSettings, _xcconfigSettings]; + + return description; +} + +/* ====================================================================================================================================== + */ +#pragma mark - Private Methods + ++ (NSString *) + duplicatedBuildConfigurationWithKey:(NSString *)buildConfigurationKey + inProject:(XCProject *)project + withBuildConfigurationVisitor: + (void (^)(NSMutableDictionary *))buildConfigurationVisitor { + NSDictionary *buildConfiguration = project.objects[buildConfigurationKey]; + NSMutableDictionary *dupBuildConfiguration = [buildConfiguration mutableCopy]; + + buildConfigurationVisitor(dupBuildConfiguration); + + NSString *dupBuildConfigurationKey = [[XCKeyBuilder createUnique] build]; + + project.objects[dupBuildConfigurationKey] = dupBuildConfiguration; + + return dupBuildConfigurationKey; +} + +@end diff --git a/XcodeZombieCodeAnalyzer/XcodeZombieCodeAnalyzer/Vendor/XcodeEditor/XCSourceFile.h b/XcodeZombieCodeAnalyzer/XcodeZombieCodeAnalyzer/Vendor/XcodeEditor/XCSourceFile.h new file mode 100644 index 0000000..4f83c13 --- /dev/null +++ b/XcodeZombieCodeAnalyzer/XcodeZombieCodeAnalyzer/Vendor/XcodeEditor/XCSourceFile.h @@ -0,0 +1,91 @@ +//////////////////////////////////////////////////////////////////////////////// +// +// JASPER BLUES +// Copyright 2012 Jasper Blues +// All Rights Reserved. +// +// NOTICE: Jasper Blues permits you to use, modify, and distribute this file +// in accordance with the terms of the license agreement accompanying it. +// +//////////////////////////////////////////////////////////////////////////////// + +#import "XCBuildFile.h" +#import "XcodeGroupMember.h" +#import "XcodeSourceFileType.h" +#import + +@class XCProject; + +/** + * Represents a file resource in an xcode project. + */ +@interface XCSourceFile : NSObject { + +@private + XCProject *_project; + + NSNumber *_isBuildFile; + NSString *_buildFileKey; + NSString *_name; + NSString *_sourceTree; + NSString *_key; + NSString *_path; + XcodeSourceFileType _type; +} + +@property(nonatomic, readonly) XcodeSourceFileType type; +@property(nonatomic, strong, readonly) NSString *key; +@property(nonatomic, strong) NSString *name; +@property(nonatomic, strong, readonly) NSString *sourceTree; +@property(nonatomic, strong) NSString *path; + ++ (XCSourceFile *)sourceFileWithProject:(XCProject *)project + key:(NSString *)key + type:(XcodeSourceFileType)type + name:(NSString *)name + sourceTree:(NSString *)tree + path:(NSString *)path; + +- (id)initWithProject:(XCProject *)project + key:(NSString *)key + type:(XcodeSourceFileType)type + name:(NSString *)name + sourceTree:(NSString *)tree + path:(NSString *)path; + +/** + * If yes, indicates the file is able to be included for compilation in an + * `XCTarget`. + */ +- (BOOL)isBuildFile; + +- (BOOL)canBecomeBuildFile; + +- (XcodeMemberType)buildPhase; + +- (NSString *)buildFileKey; + +/** + * Adds this file to the project as an `xcode_BuildFile`, ready to be included + * in targets. + */ +- (void)becomeBuildFile; + +/** + Removes this file as an `xcode_BuildFile` from the project. + */ +- (void)removeBuildFile; + +/** + * Method for setting Compiler Flags for individual build files + * + * @param value String value to set in Compiler Flags + */ +- (void)setCompilerFlags:(NSString *)value; + +/** + * Method for setting the build file is a weak reference + */ +- (void)setWeakReference; + +@end diff --git a/XcodeZombieCodeAnalyzer/XcodeZombieCodeAnalyzer/Vendor/XcodeEditor/XCSourceFile.m b/XcodeZombieCodeAnalyzer/XcodeZombieCodeAnalyzer/Vendor/XcodeEditor/XCSourceFile.m new file mode 100644 index 0000000..a76a375 --- /dev/null +++ b/XcodeZombieCodeAnalyzer/XcodeZombieCodeAnalyzer/Vendor/XcodeEditor/XCSourceFile.m @@ -0,0 +1,285 @@ +//////////////////////////////////////////////////////////////////////////////// +// +// JASPER BLUES +// Copyright 2012 Jasper Blues +// All Rights Reserved. +// +// NOTICE: Jasper Blues permits you to use, modify, and distribute this file +// in accordance with the terms of the license agreement accompanying it. +// +//////////////////////////////////////////////////////////////////////////////// + +#import "XCSourceFile.h" +#import "Utils/XCKeyBuilder.h" +#import "XCGroup.h" +#import "XCProject.h" +#import "XCTarget.h" + +@implementation XCSourceFile + +@synthesize type = _type; +@synthesize key = _key; +@synthesize sourceTree = _sourceTree; + +//------------------------------------------------------------------------------------------- +#pragma mark - Class Methods +//------------------------------------------------------------------------------------------- + ++ (XCSourceFile *)sourceFileWithProject:(XCProject *)project + key:(NSString *)key + type:(XcodeSourceFileType)type + name:(NSString *)name + sourceTree:(NSString *)_tree + path:(NSString *)path { + return [[XCSourceFile alloc] initWithProject:project + key:key + type:type + name:name + sourceTree:_tree + path:path]; +} + +//------------------------------------------------------------------------------------------- +#pragma mark - Initialization & Destruction +//------------------------------------------------------------------------------------------- + +- (id)initWithProject:(XCProject *)project + key:(NSString *)key + type:(XcodeSourceFileType)type + name:(NSString *)name + sourceTree:(NSString *)tree + path:(NSString *)path { + + self = [super init]; + if (self) { + _project = project; + _key = [key copy]; + _type = type; + _name = [name copy]; + _sourceTree = [tree copy]; + _path = [path copy]; + } + return self; +} + +//------------------------------------------------------------------------------------------- +#pragma mark - Interface Methods +//------------------------------------------------------------------------------------------- + +// Goes to the entry for this object in the project and sets a value for one of +// the keys, such as name, path, etc. +- (void)setValue:(id)val forProjectItemPropertyWithKey:(NSString *)key { + NSMutableDictionary *obj = + [[[_project objects] objectForKey:_key] mutableCopy]; + if (nil == obj) { + [NSException raise:@"Project item not found" + format:@"Project item with key %@ not found.", _key]; + } + [obj setValue:val forKey:key]; + [[_project objects] setValue:obj forKey:_key]; +} + +- (NSString *)name { + return _name; +} + +- (void)setName:(NSString *)name { + _name = [name copy]; + + [self setValue:name forProjectItemPropertyWithKey:@"name"]; +} + +- (NSString *)path { + return _path; +} + +- (void)setPath:(NSString *)path { + _path = [path copy]; + + [self setValue:path forProjectItemPropertyWithKey:@"path"]; +} + +- (BOOL)isBuildFile { + if ([self canBecomeBuildFile] && _isBuildFile == nil) { + _isBuildFile = @NO; + [[_project objects] enumerateKeysAndObjectsUsingBlock:^( + NSString *key, NSDictionary *obj, BOOL *stop) { + if ([[obj valueForKey:@"isa"] xce_hasBuildFileType]) { + if ([[obj valueForKey:@"fileRef"] isEqualToString:_key]) { + _isBuildFile = nil; + + _isBuildFile = @YES; + } + } + }]; + } + return [_isBuildFile boolValue]; +} + +- (BOOL)canBecomeBuildFile { + return _type == SourceCodeObjC || _type == SourceCodeObjCPlusPlus || + _type == SourceCodeCPlusPlus || _type == XibFile || + _type == Framework || _type == ImageResourcePNG || _type == HTML || + _type == Bundle || _type == Archive || _type == AssetCatalog || + _type == SourceCodeSwift || _type == PropertyList || + _type == LocalizableStrings; +} + +- (XcodeMemberType)buildPhase { + if (_type == SourceCodeObjC || _type == SourceCodeObjCPlusPlus || + _type == SourceCodeCPlusPlus || _type == XibFile || + _type == SourceCodeSwift) { + return PBXSourcesBuildPhaseType; + } else if (_type == Framework) { + return PBXFrameworksBuildPhaseType; + } else if (_type == ImageResourcePNG || _type == HTML || _type == Bundle || + _type == AssetCatalog || _type == PropertyList || + _type == LocalizableStrings) { + return PBXResourcesBuildPhaseType; + } else if (_type == Archive) { + return PBXFrameworksBuildPhaseType; + } + return PBXNilType; +} + +- (NSString *)buildFileKey { + if (_buildFileKey == nil) { + [[_project objects] enumerateKeysAndObjectsUsingBlock:^( + NSString *key, NSDictionary *obj, BOOL *stop) { + if ([[obj valueForKey:@"isa"] xce_hasBuildFileType]) { + if ([[obj valueForKey:@"fileRef"] isEqualToString:_key]) { + _buildFileKey = [key copy]; + } + } + }]; + } + return [_buildFileKey copy]; +} + +- (void)becomeBuildFile { + if (![self isBuildFile]) { + if ([self canBecomeBuildFile]) { + NSMutableDictionary *sourceBuildFile = [NSMutableDictionary dictionary]; + sourceBuildFile[@"isa"] = + [NSString xce_stringFromMemberType:PBXBuildFileType]; + sourceBuildFile[@"fileRef"] = _key; + NSString *buildFileKey = [[XCKeyBuilder + forItemNamed:[_name stringByAppendingString:@".buildFile"]] build]; + [_project objects][buildFileKey] = sourceBuildFile; + } else if (_type == Framework) { + [NSException raise:NSInvalidArgumentException + format:@"Add framework to target not implemented yet."]; + } else { + [NSException raise:NSInvalidArgumentException + format:@"Project file of type %@ can't become a build file.", + NSStringFromXCSourceFileType(_type)]; + } + } +} + +- (void)removeBuildFile { + if ([self isBuildFile]) { + XCSourceFile *file = [_project fileWithName:_path]; + [[_project objects] + enumerateKeysAndObjectsUsingBlock:^(id _Nonnull key, id _Nonnull obj, + BOOL *_Nonnull stop) { + if ([obj isKindOfClass:[NSDictionary class]]) { + id fileRef = [obj valueForKey:@"fileRef"]; + if (fileRef && [fileRef isKindOfClass:[NSString class]]) { + if ([fileRef isEqualToString:file.key]) { + [_project removeObjectWithKey:key]; + } + } + } + }]; + } +} + +- (void)setCompilerFlags:(NSString *)value { + NSMutableDictionary *objectArrayCopy = [[_project objects] mutableCopy]; + [objectArrayCopy enumerateKeysAndObjectsUsingBlock:^( + NSString *key, NSDictionary *obj, BOOL *stop) { + if ([[obj valueForKey:@"isa"] xce_hasBuildFileType]) { + if ([obj[@"fileRef"] isEqualToString:self.key]) { + NSMutableDictionary *replaceBuildFile = + [NSMutableDictionary dictionaryWithDictionary:obj]; + NSDictionary *compilerFlagsDict = @{@"COMPILER_FLAGS" : value}; + if ([replaceBuildFile[@"settings"] objectForKey:@"COMPILER_FLAGS"] != + nil) { + NSMutableDictionary *newSettings = [NSMutableDictionary + dictionaryWithDictionary:replaceBuildFile[@"settings"]]; + [newSettings removeObjectForKey:@"COMPILER_FLAGS"]; + replaceBuildFile[@"settings"] = compilerFlagsDict; + } else { + replaceBuildFile[@"settings"] = compilerFlagsDict; + } + [[_project objects] removeObjectForKey:key]; + [_project objects][key] = replaceBuildFile; + } + } + }]; +} +- (void)setWeakReference { + [self addBuildAttribute:@"Weak"]; +} + +- (void)addBuildAttribute:(NSString *)attribute { + NSMutableDictionary *objectArrayCopy = [[_project objects] mutableCopy]; + [objectArrayCopy enumerateKeysAndObjectsUsingBlock:^( + NSString *key, NSDictionary *obj, BOOL *stop) { + if ([[obj valueForKey:@"isa"] xce_hasBuildFileType]) { + if ([obj[@"fileRef"] isEqualToString:self.key]) { + NSMutableDictionary *replaceBuildFile = + [NSMutableDictionary dictionaryWithDictionary:obj]; + if (![replaceBuildFile objectForKey:@"settings"]) { + replaceBuildFile[@"settings"] = @{@"ATTRIBUTES" : @[ attribute ]}; + } else { + NSMutableDictionary *settingsDict = [NSMutableDictionary + dictionaryWithDictionary:replaceBuildFile[@"settings"]]; + if (![settingsDict objectForKey:@"ATTRIBUTES"]) { + settingsDict[@"ATTRIBUTES"] = @[ attribute ]; + } else { + NSMutableArray *attr = + [NSMutableArray arrayWithArray:settingsDict[@"ATTRIBUTES"]]; + if ([attr containsObject:attribute]) { + return; + } + [attr addObject:attribute]; + settingsDict[@"ATTRIBUTES"] = attr; + } + } + [[_project objects] removeObjectForKey:key]; + [_project objects][key] = replaceBuildFile; + } + } + }]; +} + +//------------------------------------------------------------------------------------------- +#pragma mark - Protocol Methods + +- (XcodeMemberType)groupMemberType { + return PBXFileReferenceType; +} + +- (NSString *)displayName { + return _name; +} + +- (NSString *)pathRelativeToProjectRoot { + NSString *parentPath = + [[_project groupForGroupMemberWithKey:_key] pathRelativeToProjectRoot]; + NSString *result = [parentPath stringByAppendingPathComponent:_name]; + return result; +} + +//------------------------------------------------------------------------------------------- +#pragma mark - Utility Methods + +- (NSString *)description { + return + [NSString stringWithFormat:@"Project file: key=%@, name=%@, fullPath=%@", + _key, _name, [self pathRelativeToProjectRoot]]; +} + +@end diff --git a/XcodeZombieCodeAnalyzer/XcodeZombieCodeAnalyzer/Vendor/XcodeEditor/XCSourceFileDefinition.h b/XcodeZombieCodeAnalyzer/XcodeZombieCodeAnalyzer/Vendor/XcodeEditor/XCSourceFileDefinition.h new file mode 100644 index 0000000..1720c79 --- /dev/null +++ b/XcodeZombieCodeAnalyzer/XcodeZombieCodeAnalyzer/Vendor/XcodeEditor/XCSourceFileDefinition.h @@ -0,0 +1,47 @@ +//////////////////////////////////////////////////////////////////////////////// +// +// JASPER BLUES +// Copyright 2012 Jasper Blues +// All Rights Reserved. +// +// NOTICE: Jasper Blues permits you to use, modify, and distribute this file +// in accordance with the terms of the license agreement accompanying it. +// +//////////////////////////////////////////////////////////////////////////////// + +#import "XCAbstractDefinition.h" +#import "XcodeSourceFileType.h" +#import + +@interface XCSourceFileDefinition : XCAbstractDefinition { + + NSString *_sourceFileName; + XcodeSourceFileType _type; + NSData *_data; +} + +@property(nonatomic, strong, readonly) NSString *sourceFileName; +@property(nonatomic, strong, readonly) NSData *data; +@property(nonatomic, readonly) XcodeSourceFileType type; + ++ (XCSourceFileDefinition *)sourceDefinitionWithName:(NSString *)name + text:(NSString *)text + type:(XcodeSourceFileType)type; + ++ (XCSourceFileDefinition *)sourceDefinitionWithName:(NSString *)name + data:(NSData *)data + type:(XcodeSourceFileType)type; + +// For now you can add Asset Catalog only as reference ++ (XCSourceFileDefinition *)sourceDefinitionWithAssetCatalogName: + (NSString *)name; + +- (id)initWithName:(NSString *)name + text:(NSString *)text + type:(XcodeSourceFileType)type; + +- (id)initWithName:(NSString *)name + data:(NSData *)data + type:(XcodeSourceFileType)type; + +@end diff --git a/XcodeZombieCodeAnalyzer/XcodeZombieCodeAnalyzer/Vendor/XcodeEditor/XCSourceFileDefinition.m b/XcodeZombieCodeAnalyzer/XcodeZombieCodeAnalyzer/Vendor/XcodeEditor/XCSourceFileDefinition.m new file mode 100644 index 0000000..28340a8 --- /dev/null +++ b/XcodeZombieCodeAnalyzer/XcodeZombieCodeAnalyzer/Vendor/XcodeEditor/XCSourceFileDefinition.m @@ -0,0 +1,83 @@ +//////////////////////////////////////////////////////////////////////////////// +// +// JASPER BLUES +// Copyright 2012 Jasper Blues +// All Rights Reserved. +// +// NOTICE: Jasper Blues permits you to use, modify, and distribute this file +// in accordance with the terms of the license agreement accompanying it. +// +//////////////////////////////////////////////////////////////////////////////// + +#import "XCSourceFileDefinition.h" + +@implementation XCSourceFileDefinition + +@synthesize sourceFileName = _sourceFileName; +@synthesize type = _type; +@synthesize data = _data; + +/* ====================================================================================================================================== + */ +#pragma mark - Class Methods + ++ (XCSourceFileDefinition *)sourceDefinitionWithName:(NSString *)name + text:(NSString *)text + type:(XcodeSourceFileType)type { + + return [[XCSourceFileDefinition alloc] initWithName:name text:text type:type]; +} + ++ (XCSourceFileDefinition *)sourceDefinitionWithName:(NSString *)name + data:(NSData *)data + type:(XcodeSourceFileType)type { + + return [[XCSourceFileDefinition alloc] initWithName:name data:data type:type]; +} + ++ (XCSourceFileDefinition *)sourceDefinitionWithAssetCatalogName: + (NSString *)name { + XCSourceFileDefinition *definition = + [[XCSourceFileDefinition alloc] initWithName:name type:AssetCatalog]; + definition.fileOperationType = XCFileOperationTypeAcceptExisting; + return definition; +} + +/* ====================================================================================================================================== + */ +#pragma mark - Initialization & Destruction + +- (id)initWithName:(NSString *)name + text:(NSString *)text + type:(XcodeSourceFileType)type { + self = [super init]; + if (self) { + _sourceFileName = [name copy]; + _data = [[text dataUsingEncoding:NSUTF8StringEncoding] copy]; + _type = type; + } + return self; +} + +- (id)initWithName:(NSString *)name + data:(NSData *)data + type:(XcodeSourceFileType)type { + self = [super init]; + if (self) { + _sourceFileName = [name copy]; + _data = [data copy]; + _type = type; + } + return self; +} + +- (id)initWithName:(NSString *)name type:(XcodeSourceFileType)type { + self = [super init]; + if (self) { + _sourceFileName = [name copy]; + _type = type; + } + return self; +} + +@end \ No newline at end of file diff --git a/XcodeZombieCodeAnalyzer/XcodeZombieCodeAnalyzer/Vendor/XcodeEditor/XCSubProjectDefinition.h b/XcodeZombieCodeAnalyzer/XcodeZombieCodeAnalyzer/Vendor/XcodeEditor/XCSubProjectDefinition.h new file mode 100644 index 0000000..24c5793 --- /dev/null +++ b/XcodeZombieCodeAnalyzer/XcodeZombieCodeAnalyzer/Vendor/XcodeEditor/XCSubProjectDefinition.h @@ -0,0 +1,59 @@ +//////////////////////////////////////////////////////////////////////////////// +// +// JASPER BLUES +// Copyright 2012 Jasper Blues +// All Rights Reserved. +// +// NOTICE: Jasper Blues permits you to use, modify, and distribute this file +// in accordance with the terms of the license agreement accompanying it. +// +//////////////////////////////////////////////////////////////////////////////// + +#import "XCAbstractDefinition.h" +#import "XcodeSourceFileType.h" +#import + +@class XCProject; + +@interface XCSubProjectDefinition : XCAbstractDefinition { + + NSString *_name; + NSString *_path; + XcodeSourceFileType _type; + XCProject *_subProject; + XCProject *_parentProject; + NSString *_key; + NSString *_fullProjectPath; + NSString *_relativePath; +} + +@property(nonatomic, strong, readonly) NSString *name; +@property(nonatomic, strong, readonly) NSString *path; +@property(nonatomic, readonly) XcodeSourceFileType type; +@property(nonatomic, strong, readonly) XCProject *subProject; +@property(nonatomic, strong, readonly) XCProject *parentProject; +@property(nonatomic, strong, readonly) NSString *key; +@property(nonatomic, strong, readwrite) NSString *fullProjectPath; + ++ (XCSubProjectDefinition *)withName:(NSString *)name + path:(NSString *)path + parentProject:(XCProject *)parentProject; + +- (id)initWithName:(NSString *)name + path:(NSString *)path + parentProject:(XCProject *)parentProject; + +- (NSString *)projectFileName; + +- (NSString *)fullPathName; + +- (NSArray *)buildProductNames; + +- (NSString *)projectKey; + +- (NSString *)pathRelativeToProjectRoot; + +- (void)initFullProjectPath:(NSString *)fullProjectPath + groupPath:(NSString *)groupPath; + +@end diff --git a/XcodeZombieCodeAnalyzer/XcodeZombieCodeAnalyzer/Vendor/XcodeEditor/XCSubProjectDefinition.m b/XcodeZombieCodeAnalyzer/XcodeZombieCodeAnalyzer/Vendor/XcodeEditor/XCSubProjectDefinition.m new file mode 100644 index 0000000..54e1c88 --- /dev/null +++ b/XcodeZombieCodeAnalyzer/XcodeZombieCodeAnalyzer/Vendor/XcodeEditor/XCSubProjectDefinition.m @@ -0,0 +1,182 @@ +//////////////////////////////////////////////////////////////////////////////// +// +// JASPER BLUES +// Copyright 2012 Jasper Blues +// All Rights Reserved. +// +// NOTICE: Jasper Blues permits you to use, modify, and distribute this file +// in accordance with the terms of the license agreement accompanying it. +// +//////////////////////////////////////////////////////////////////////////////// + +#import "XCSubProjectDefinition.h" +#import "XCProject+SubProject.h" +#import "XCProject.h" + +@interface XCSubProjectDefinition () +@property(nonatomic, strong, readwrite) NSString *relativePath; +@end + +@implementation XCSubProjectDefinition + +@synthesize name = _name; +@synthesize path = _path; +@synthesize type = _type; +@synthesize parentProject = _parentProject; +@synthesize subProject = _subProject; +@synthesize relativePath = _relativePath; +@synthesize key = _key; +@synthesize fullProjectPath = _fullProjectPath; + +//------------------------------------------------------------------------------------------- +#pragma mark - Class Methods +//------------------------------------------------------------------------------------------- + ++ (XCSubProjectDefinition *)withName:(NSString *)name + path:(NSString *)path + parentProject:(XCProject *)parentProject { + + return [[XCSubProjectDefinition alloc] initWithName:name + path:path + parentProject:parentProject]; +} + +//------------------------------------------------------------------------------------------- +#pragma mark - Initialization & Destruction +//------------------------------------------------------------------------------------------- + +// Note - _path is most often going to be an absolute path. The method +// pathRelativeToProjectRoot below should be +// used to get the form that's stored in the main project file. +- (id)initWithName:(NSString *)name + path:(NSString *)path + parentProject:(XCProject *)parentProject { + self = [super init]; + if (self) { + _name = [name copy]; + _path = [path copy]; + _type = XcodeProject; + _parentProject = parentProject; + _subProject = [[XCProject alloc] + initWithFilePath:[NSString + stringWithFormat:@"%@/%@.xcodeproj", path, name]]; + } + return self; +} + +//------------------------------------------------------------------------------------------- +#pragma mark - Interface Methods +//------------------------------------------------------------------------------------------- + +- (NSString *)projectFileName { + return [_name stringByAppendingString:@".xcodeproj"]; +} + +- (NSString *)fullPathName { + return + [NSString stringWithFormat:@"%@/%@", _path, + [_name stringByAppendingString:@".xcodeproj"]]; +} + +// returns an array of names of the build products of this project +- (NSArray *)buildProductNames { + NSMutableArray *results = [NSMutableArray array]; + NSDictionary *objects = [_subProject objects]; + [objects enumerateKeysAndObjectsUsingBlock:^(NSString *key, NSDictionary *obj, + BOOL *stop) { + if ([[obj valueForKey:@"isa"] xce_hasProjectType]) { + NSString *productRefGroupKey = [obj valueForKey:@"productRefGroup"]; + NSDictionary *products = [objects valueForKey:productRefGroupKey]; + NSArray *children = [products valueForKey:@"children"]; + for (NSString *childKey in children) { + NSDictionary *child = [objects valueForKey:childKey]; + [results addObject:[child valueForKey:@"path"]]; + } + } + }]; + return results; +} + +// returns the key of the PBXFileReference of the xcodeproj file +- (NSString *)projectKey { + if (_key == nil) { + NSArray *xcodeprojKeys = [_parentProject + keysForProjectObjectsOfType:PBXFileReferenceType + withIdentifier:[self pathRelativeToProjectRoot] + singleton:YES + required:YES]; + _key = [[xcodeprojKeys objectAtIndex:0] copy]; + } + return [_key copy]; +} + +- (void)initFullProjectPath:(NSString *)fullProjectPath + groupPath:(NSString *)groupPath { + if (groupPath != nil) { + NSMutableArray *fullPathComponents = + [[fullProjectPath pathComponents] mutableCopy]; + [fullPathComponents removeLastObject]; + fullProjectPath = [[NSString pathWithComponents:fullPathComponents] + stringByAppendingFormat:@"/%@", groupPath]; + } + _fullProjectPath = [fullProjectPath copy]; +} + +// compares the given path to the filePath of the project, and returns a +// relative version. _fullProjectPath, which has +// to hve been previously set, is the full path to the project *plus* the path +// to the xcodeproj's _group, if any. +- (NSString *)pathRelativeToProjectRoot { + if (_relativePath == nil) { + if (_fullProjectPath == nil) { + [NSException raise:NSInvalidArgumentException + format:@"fullProjectPath has not been set"]; + } + NSMutableArray *projectPathComponents = + [[_fullProjectPath pathComponents] mutableCopy]; + NSArray *objectPathComponents = [[self fullPathName] pathComponents]; + NSString *convertedPath = @""; + + // skip over path components from root that are equal + NSInteger limit = + ([projectPathComponents count] < [objectPathComponents count]) + ? [projectPathComponents count] + : [objectPathComponents count]; + NSInteger index1 = 0; + for (; index1 < limit; index1++) { + if ([[projectPathComponents objectAtIndex:index1] + isEqualToString:[objectPathComponents objectAtIndex:index1]]) { + continue; + } else { + break; + } + } + // insert "../" for each remaining path component in project's xcodeproj + // path + for (NSInteger index2 = 0; + index2 < ([projectPathComponents count] - index1); index2++) { + convertedPath = [convertedPath stringByAppendingString:@"../"]; + } + // tack on the unique part of the object's path + for (NSInteger index3 = index1; index3 < [objectPathComponents count] - 1; + index3++) { + convertedPath = [convertedPath + stringByAppendingFormat:@"%@/", objectPathComponents[index3]]; + } + _relativePath = [[convertedPath + stringByAppendingString:[objectPathComponents lastObject]] copy]; + } + return [_relativePath copy]; +} + +//------------------------------------------------------------------------------------------- +#pragma mark - Utility Methods + +- (NSString *)description { + return [NSString + stringWithFormat: + @"XcodeprojDefinition: sourceFileName = %@, path=%@, type=%li", _name, + _path, (long)_type]; +} + +@end \ No newline at end of file diff --git a/XcodeZombieCodeAnalyzer/XcodeZombieCodeAnalyzer/Vendor/XcodeEditor/XCTarget.h b/XcodeZombieCodeAnalyzer/XcodeZombieCodeAnalyzer/Vendor/XcodeEditor/XCTarget.h new file mode 100644 index 0000000..30e18f6 --- /dev/null +++ b/XcodeZombieCodeAnalyzer/XcodeZombieCodeAnalyzer/Vendor/XcodeEditor/XCTarget.h @@ -0,0 +1,94 @@ +//////////////////////////////////////////////////////////////////////////////// +// +// JASPER BLUES +// Copyright 2012 Jasper Blues +// All Rights Reserved. +// +// NOTICE: Jasper Blues permits you to use, modify, and distribute this file +// in accordance with the terms of the license agreement accompanying it. +// +//////////////////////////////////////////////////////////////////////////////// + +#import + +@class XCProject; +@class XCSourceFile; +@class XCProjectBuildConfig; +@class XCBuildShellScript; +@class XCBuildShellScriptDefinition; +@protocol XCBuildFile; + +/** + * Represents a target in an xcode project. + */ +@interface XCTarget : NSObject { + + XCProject *_project; + NSString *_key; + NSString *_name; + NSString *_productName; + NSString *_productReference; + NSString *_productType; + NSString *_defaultConfigurationName; + +@private + NSMutableArray *_members; + NSMutableArray *_resources; + NSMutableDictionary *_configurations; + NSMutableArray *_buildShellScripts; +} + +@property(nonatomic, strong, readonly) NSString *key; +@property(nonatomic, strong) NSString *name; +@property(nonatomic, strong) NSString *productName; +@property(nonatomic, strong, readonly) NSString *productReference; +@property(nonatomic, strong, readonly) NSString *productType; + ++ (XCTarget *)targetWithProject:(XCProject *)project + key:(NSString *)key + name:(NSString *)name + productName:(NSString *)productName + productReference:(NSString *)productReference + productType:(NSString *)productType; + +- (id)initWithProject:(XCProject *)project + key:(NSString *)key + name:(NSString *)name + productName:(NSString *)productName + productReference:(NSString *)productReference + productType:(NSString *)productType; + +- (NSArray *)resources; + +- (NSArray> *)members; + +- (NSArray *)buildShellScripts; + +- (NSDictionary *)configurations; + +- (XCProjectBuildConfig *)configurationWithName:(NSString *)name; + +- (XCProjectBuildConfig *)defaultConfiguration; + +- (void)addMember:(id)member; + +- (void)makeAndAddShellScript:(XCBuildShellScriptDefinition *)shellScript; + +- (void)removeShellScriptByName:(NSString *)name; + +- (void)removeMemberWithKey:(NSString *)key; + +- (void)removeMembersWithKeys:(NSArray *)keys; + +- (void)removeResourceWithKey:(NSString *)key; + +- (void)removeResourcesWithKeys:(NSArray *)keys; + +- (void)addDependency:(NSString *)key; + +- (instancetype)duplicateWithTargetName:(NSString *)targetName + productName:(NSString *)productName; + +- (BOOL)isApplicationType; + +@end diff --git a/XcodeZombieCodeAnalyzer/XcodeZombieCodeAnalyzer/Vendor/XcodeEditor/XCTarget.m b/XcodeZombieCodeAnalyzer/XcodeZombieCodeAnalyzer/Vendor/XcodeEditor/XCTarget.m new file mode 100644 index 0000000..5f600f8 --- /dev/null +++ b/XcodeZombieCodeAnalyzer/XcodeZombieCodeAnalyzer/Vendor/XcodeEditor/XCTarget.m @@ -0,0 +1,502 @@ +//////////////////////////////////////////////////////////////////////////////// +// +// JASPER BLUES +// Copyright 2012 Jasper Blues +// All Rights Reserved. +// +// NOTICE: Jasper Blues permits you to use, modify, and distribute this file +// in accordance with the terms of the license agreement accompanying it. +// +//////////////////////////////////////////////////////////////////////////////// + +#import "XCTarget.h" +#import "XCBuildShellScript.h" +#import "XCBuildShellScriptDefinition.h" +#import "XCGroup.h" +#import "XCKeyBuilder.h" +#import "XCProject.h" +#import "XCProjectBuildConfig.h" +#import "XCSourceFile.h" +#import "XCVersionGroup.h" +#import "XcodeGroupMember.h" + +@implementation XCTarget + +/* ====================================================================================================================================== + */ +#pragma mark - Class Methods + ++ (XCTarget *)targetWithProject:(XCProject *)project + key:(NSString *)key + name:(NSString *)name + productName:(NSString *)productName + productReference:(NSString *)productReference + productType:(NSString *)productType { + return [[XCTarget alloc] initWithProject:project + key:key + name:name + productName:productName + productReference:productReference + productType:productType]; +} + +/* ====================================================================================================================================== + */ +#pragma mark - Initialization & Destruction + +- (id)initWithProject:(XCProject *)project + key:(NSString *)key + name:(NSString *)name + productName:(NSString *)productName + productReference:(NSString *)productReference + productType:(NSString *)productType { + self = [super init]; + if (self) { + _project = project; + _key = [key copy]; + _name = [name copy]; + _productName = [productName copy]; + _productReference = [productReference copy]; + _productType = [productType copy]; + } + return self; +} + +/* ====================================================================================================================================== + */ +#pragma mark - Interface Methods + +- (NSArray *)resources { + if (_resources == nil) { + _resources = [[NSMutableArray alloc] init]; + for (NSString *buildPhaseKey in + [[[_project objects] objectForKey:_key] objectForKey:@"buildPhases"]) { + NSDictionary *buildPhase = + [[_project objects] objectForKey:buildPhaseKey]; + if ([[buildPhase valueForKey:@"isa"] xce_hasResourcesBuildPhaseType]) { + for (NSString *buildFileKey in [buildPhase objectForKey:@"files"]) { + id targetMember = [self buildFileWithKey:buildFileKey]; + if (targetMember) { + [_resources addObject:[self buildFileWithKey:buildFileKey]]; + } + } + } + } + } + + return _resources; +} + +- (NSDictionary *)configurations { + if (_configurations == nil) { + NSString *buildConfigurationRootSectionKey = [[[_project objects] + objectForKey:_key] objectForKey:@"buildConfigurationList"]; + NSDictionary *buildConfigurationDictionary = + [[_project objects] objectForKey:buildConfigurationRootSectionKey]; + _configurations = [[XCProjectBuildConfig + buildConfigurationsFromArray:[buildConfigurationDictionary + objectForKey:@"buildConfigurations"] + inProject:_project] mutableCopy]; + _defaultConfigurationName = [[buildConfigurationDictionary + objectForKey:@"defaultConfigurationName"] copy]; + } + + return _configurations; +} + +- (XCProjectBuildConfig *)defaultConfiguration { + return [[self configurations] objectForKey:_defaultConfigurationName]; +} + +- (XCProjectBuildConfig *)configurationWithName:(NSString *)name { + return [[self configurations] objectForKey:name]; +} + +- (NSArray *)members { + if (_members == nil) { + _members = [[NSMutableArray alloc] init]; + for (NSString *buildPhaseKey in + [[[_project objects] objectForKey:_key] objectForKey:@"buildPhases"]) { + NSDictionary *buildPhase = + [[_project objects] objectForKey:buildPhaseKey]; + if ([[buildPhase valueForKey:@"isa"] + xce_hasSourcesOrFrameworksBuildPhaseType]) { + for (NSString *buildFileKey in [buildPhase objectForKey:@"files"]) { + id targetMember = + [self buildFileWithKey:buildFileKey]; + if (targetMember) { + [_members addObject:targetMember]; + } + } + } + } + } + return _members; +} + +- (NSArray *)buildShellScripts { + if (_buildShellScripts == nil) { + _buildShellScripts = [[NSMutableArray alloc] init]; + for (NSString *buildPhaseKey in + [[[_project objects] objectForKey:_key] objectForKey:@"buildPhases"]) { + NSDictionary *buildPhase = + [[_project objects] objectForKey:buildPhaseKey]; + if ([[buildPhase valueForKey:@"isa"] xce_hasShellScriptBuildPhase]) { + + XCBuildShellScript *targetMember = [[XCBuildShellScript alloc] + initWithProject:_project + key:buildPhaseKey + name:buildPhase[@"name"] + files:buildPhase[@"files"] + inputPaths:buildPhase[@"inputPaths"] + outputPaths:buildPhase[@"outputPaths"] + runOnlyForDeploymentPostprocessing: + [buildPhase[@"runOnlyForDeploymentPostprocessing"] boolValue] + shellPath:buildPhase[@"shellPath"] + shellScript:buildPhase[@"shellScript"]]; + if (targetMember) { + [_buildShellScripts addObject:targetMember]; + } + } + } + } + + return _buildShellScripts; +} + +- (void)addMember:(id)member { + [member becomeBuildFile]; + NSDictionary *target = [[_project objects] objectForKey:_key]; + + for (NSString *buildPhaseKey in [target objectForKey:@"buildPhases"]) { + NSMutableDictionary *buildPhase = + [[_project objects] objectForKey:buildPhaseKey]; + if ([[buildPhase valueForKey:@"isa"] xce_asMemberType] == + [member buildPhase]) { + + NSMutableArray *files = [buildPhase objectForKey:@"files"]; + if (![files containsObject:[member buildFileKey]]) { + [files addObject:[member buildFileKey]]; + } + + [buildPhase setObject:files forKey:@"files"]; + } + } + [self flagMembersAsDirty]; +} + +- (void)makeAndAddShellScript:(XCBuildShellScriptDefinition *)shellScript { + NSDictionary *target = [[_project objects] objectForKey:_key]; + + NSDictionary *reference = @{ + @"isa" : [NSString xce_stringFromMemberType:PBXShellScriptBuildPhase], + @"buildActionMask" : @"2147483647", + @"files" : shellScript.files, + @"inputPaths" : shellScript.inputPaths, + @"outputPaths" : shellScript.outputPaths, + @"runOnlyForDeploymentPostprocessing" : + @(shellScript.runOnlyForDeploymentPostprocessing), + @"shellPath" : shellScript.shellPath, + @"shellScript" : shellScript.shellScript, + @"name" : shellScript.name + + }; + NSString *fileKey = [[XCKeyBuilder forItemNamed:shellScript.name] build]; + [_project objects][fileKey] = reference; + + NSMutableArray *buildPhases = [target objectForKey:@"buildPhases"]; + [buildPhases addObject:fileKey]; + [target setValue:buildPhases forKey:@"buildPhases"]; + + [self flagMembersAsDirty]; +} + +- (void)removeShellScriptByName:(NSString *)name { + NSDictionary *target = [[_project objects] objectForKey:_key]; + NSMutableArray *removedPhases = [NSMutableArray array]; + for (NSString *buildPhaseKey in [target objectForKey:@"buildPhases"]) { + NSMutableDictionary *buildPhase = + [[_project objects] objectForKey:buildPhaseKey]; + NSString *type = [buildPhase objectForKey:@"isa"]; + if (type && [type xce_hasShellScriptBuildPhase]) { + NSString *currentName = [buildPhase objectForKey:@"name"]; + if ([currentName isEqualToString:name]) { + [_project removeObjectWithKey:buildPhaseKey]; + [removedPhases addObject:buildPhaseKey]; + } + } + } + + NSMutableArray *buildPhases = [target objectForKey:@"buildPhases"]; + [buildPhases removeObjectsInArray:removedPhases]; + [target setValue:buildPhases forKey:@"buildPhases"]; +} + +- (NSDictionary *)buildRefWithFileRefKey { + NSMutableDictionary *buildRefWithFileRefDict = + [NSMutableDictionary dictionary]; + NSDictionary *allObjects = [_project objects]; + NSArray *keys = [allObjects allKeys]; + + for (NSString *key in keys) { + NSDictionary *dictionaryInfo = [allObjects objectForKey:key]; + + NSString *type = [dictionaryInfo objectForKey:@"isa"]; + if (type) { + if ([type isEqualToString:@"PBXBuildFile"]) { + NSString *fileRef = [dictionaryInfo objectForKey:@"fileRef"]; + + if (fileRef) { + [buildRefWithFileRefDict setObject:key forKey:fileRef]; + } + } + } + } + return buildRefWithFileRefDict; +} + +- (void)removeResourceWithKey:(NSString *)key { + [self removeResourcesWithKeys:[NSArray arrayWithObject:key]]; +} + +- (void)removeResourcesWithKeys:(NSArray *)keys { + NSDictionary *target = [[_project objects] objectForKey:_key]; + for (NSString *buildPhaseKey in [target objectForKey:@"buildPhases"]) { + NSMutableDictionary *buildPhase = + [[_project objects] objectForKey:buildPhaseKey]; + NSMutableArray *files = [buildPhase objectForKey:@"files"]; + NSMutableIndexSet *toRemoveIndexes = [NSMutableIndexSet indexSet]; + + for (int i = 0; i < files.count; i++) { + XCSourceFile *resource = [self buildFileWithKey:files[i]]; + if ([keys containsObject:resource.key]) { + [toRemoveIndexes addIndex:i]; + } + } + + [files removeObjectsAtIndexes:toRemoveIndexes]; + [buildPhase setObject:files forKey:@"files"]; + } + [self flagMembersAsDirty]; +} + +- (void)removeMemberWithKey:(NSString *)key { + + NSDictionary *buildRefWithFileRef = [self buildRefWithFileRefKey]; + NSDictionary *target = [[_project objects] objectForKey:_key]; + NSString *buildRef = [buildRefWithFileRef objectForKey:key]; + + if (!buildRef) { + return; + } + + for (NSString *buildPhaseKey in [target objectForKey:@"buildPhases"]) { + NSMutableDictionary *buildPhase = + [[_project objects] objectForKey:buildPhaseKey]; + NSMutableArray *files = [buildPhase objectForKey:@"files"]; + + [files removeObjectIdenticalTo:buildRef]; + [buildPhase setObject:files forKey:@"files"]; + } + [self flagMembersAsDirty]; +} + +- (void)removeMembersWithKeys:(NSArray *)keys { + for (NSString *key in keys) { + [self removeMemberWithKey:key]; + } +} + +- (void)addDependency:(NSString *)key { + NSDictionary *targetObj = [[_project objects] objectForKey:_key]; + NSMutableArray *dependencies = [targetObj valueForKey:@"dependencies"]; + // add only if not already there + BOOL found = NO; + for (NSString *dependency in dependencies) { + if ([dependency isEqualToString:key]) { + found = YES; + break; + } + } + if (!found) { + [dependencies addObject:key]; + } +} + +- (instancetype)duplicateWithTargetName:(NSString *)targetName + productName:(NSString *)productName { + + NSDictionary *targetObj = _project.objects[_key]; + NSMutableDictionary *dupTargetObj = [targetObj mutableCopy]; + + dupTargetObj[@"name"] = targetName; + dupTargetObj[@"productName"] = productName; + + NSString *buildConfigurationListKey = dupTargetObj[@"buildConfigurationList"]; + + void (^visitor)(NSMutableDictionary *) = + ^(NSMutableDictionary *buildConfiguration) { + buildConfiguration[@"buildSettings"][@"PRODUCT_NAME"] = productName; + }; + + dupTargetObj[@"buildConfigurationList"] = [XCProjectBuildConfig + duplicatedBuildConfigurationListWithKey:buildConfigurationListKey + inProject:_project + withBuildConfigurationVisitor:visitor]; + + [self duplicateProductReferenceForTargetObject:dupTargetObj + withProductName:productName]; + + [self duplicateBuildPhasesForTargetObject:dupTargetObj]; + + [self addReferenceToProductsGroupForTargetObject:dupTargetObj]; + + NSString *dupTargetObjKey = [self addTargetToRootObjectTargets:dupTargetObj]; + + [_project dropCache]; + + return [[XCTarget alloc] initWithProject:_project + key:dupTargetObjKey + name:targetName + productName:productName + productReference:dupTargetObj[@"productReference"] + productType:dupTargetObj[@"productType"]]; +} + +- (BOOL)isApplicationType { + return [_productType isEqualToString:@"com.apple.product-type.application"]; +} + +/* ====================================================================================================================================== + */ +#pragma mark - Overridden Methods + +- (void)setName:(NSString *)name { + _name = name; + NSDictionary *dictionary = [_project.objects objectForKey:_key]; + [dictionary setValue:_name forKey:@"name"]; +} + +- (void)setProductName:(NSString *)productName { + _productName = productName; + NSDictionary *dictionary = [_project.objects objectForKey:_key]; + [dictionary setValue:_productName forKey:@"productName"]; +} + +/* ====================================================================================================================================== + */ +#pragma mark - Utility Methods + +- (NSString *)description { + return + [NSString stringWithFormat:@"Target: name=%@, files=%@", _name, _members]; +} + +/* ====================================================================================================================================== + */ +#pragma mark - Private Methods + +- (id)buildFileWithKey:(NSString *)theKey { + NSDictionary *obj = [[_project objects] valueForKey:theKey]; + if (obj) { + if ([[obj valueForKey:@"isa"] xce_hasBuildFileType]) { + id targetFile = + [_project groupMemberWithKey:[obj valueForKey:@"fileRef"]]; + if ([targetFile conformsToProtocol:@protocol(XCBuildFile)]) + return (id)targetFile; + } + } + return nil; +} + +- (void)flagMembersAsDirty { + _members = nil; + _resources = nil; + _buildShellScripts = nil; +} + +- (void)duplicateProductReferenceForTargetObject: + (NSMutableDictionary *)dupTargetObj + withProductName:(NSString *)productName { + + NSString *productReferenceKey = dupTargetObj[@"productReference"]; + NSMutableDictionary *dupProductReference = + [_project.objects[productReferenceKey] mutableCopy]; + + NSString *path = dupProductReference[@"path"]; + NSString *dupPath = [path stringByDeletingLastPathComponent]; + dupPath = [dupPath stringByAppendingPathComponent:productName]; + dupPath = [dupPath stringByAppendingPathExtension:@"app"]; + dupProductReference[@"path"] = dupPath; + + NSString *dupProductReferenceKey = [[XCKeyBuilder createUnique] build]; + + _project.objects[dupProductReferenceKey] = dupProductReference; + dupTargetObj[@"productReference"] = dupProductReferenceKey; +} + +- (void)duplicateBuildPhasesForTargetObject: + (NSMutableDictionary *)dupTargetObj { + + NSMutableArray *buildPhases = [NSMutableArray array]; + + for (NSString *buildPhaseKey in dupTargetObj[@"buildPhases"]) { + + NSMutableDictionary *dupBuildPhase = + [_project.objects[buildPhaseKey] mutableCopy]; + NSMutableArray *dupFiles = [NSMutableArray array]; + + for (NSString *fileKey in dupBuildPhase[@"files"]) { + + NSMutableDictionary *dupFile = [_project.objects[fileKey] mutableCopy]; + NSString *dupFileKey = [[XCKeyBuilder createUnique] build]; + + _project.objects[dupFileKey] = dupFile; + [dupFiles addObject:dupFileKey]; + } + + dupBuildPhase[@"files"] = dupFiles; + + NSString *dupBuildPhaseKey = [[XCKeyBuilder createUnique] build]; + _project.objects[dupBuildPhaseKey] = dupBuildPhase; + [buildPhases addObject:dupBuildPhaseKey]; + } + + dupTargetObj[@"buildPhases"] = buildPhases; +} + +- (void)addReferenceToProductsGroupForTargetObject: + (NSMutableDictionary *)dupTargetObj { + + XCGroup *mainGroup = nil; + NSPredicate *productsPredicate = + [NSPredicate predicateWithFormat:@"displayName == 'Products'"]; + NSArray *filteredGroups = + [_project.groups filteredArrayUsingPredicate:productsPredicate]; + + if (filteredGroups.count > 0) { + mainGroup = filteredGroups[0]; + NSMutableArray *children = + [_project.objects[mainGroup.key][@"children"] mutableCopy]; + [children addObject:dupTargetObj[@"productReference"]]; + _project.objects[mainGroup.key][@"children"] = children; + } +} + +- (NSString *)addTargetToRootObjectTargets:(NSMutableDictionary *)dupTargetObj { + NSString *dupTargetObjKey = [[XCKeyBuilder createUnique] build]; + + _project.objects[dupTargetObjKey] = dupTargetObj; + + NSString *rootObjKey = _project.dataStore[@"rootObject"]; + NSMutableDictionary *rootObj = [_project.objects[rootObjKey] mutableCopy]; + NSMutableArray *rootObjTargets = [rootObj[@"targets"] mutableCopy]; + [rootObjTargets addObject:dupTargetObjKey]; + + rootObj[@"targets"] = rootObjTargets; + _project.objects[rootObjKey] = rootObj; + + return dupTargetObjKey; +} + +@end diff --git a/XcodeZombieCodeAnalyzer/XcodeZombieCodeAnalyzer/Vendor/XcodeEditor/XCVersionGroup.h b/XcodeZombieCodeAnalyzer/XcodeZombieCodeAnalyzer/Vendor/XcodeEditor/XCVersionGroup.h new file mode 100644 index 0000000..9990b4e --- /dev/null +++ b/XcodeZombieCodeAnalyzer/XcodeZombieCodeAnalyzer/Vendor/XcodeEditor/XCVersionGroup.h @@ -0,0 +1,115 @@ +// +// XCCoreDataModelVersioned.h +// xcode-editor +// +// Created by joel on 04/09/15. +// +// + +#import "XCBuildFile.h" +#import "XCGroup.h" +#import "XcodeGroupMember.h" +#import + +@class XCProject; +@class XCClassDefinition; +@class XCSourceFile; +@class XCXibDefinition; +@class XCFileOperationQueue; +@class XCFrameworkDefinition; +@class XCSourceFileDefinition; +@class XCSubProjectDefinition; + +@interface XCVersionGroup : NSObject { + NSString *_pathRelativeToParent; + NSString *_key; + +@private + NSString *_pathRelativeToProjectRoot; + NSMutableArray *_children; + NSMutableArray *_members; + NSString *_currentVersion; + NSString *_versionGroupType; + + NSNumber *_isBuildFile; + NSString *_buildFileKey; + + XCFileOperationQueue *_fileOperationQueue; // weak + XCProject *_project; +} + +/** + * The alias of the group, which can be used to give the group a name other than + * the last path component. + * + * See: [XcodeGroupMember displayName] + */ +@property(nonatomic, strong, readonly) NSString *alias; + +/** + * The path of the group relative to the group's parent. + * + * See: [XcodeGroupMember displayName] + */ +@property(nonatomic, strong, readonly) NSString *pathRelativeToParent; + +/** + * The group's unique key. + */ +@property(nonatomic, strong, readonly) NSString *key; + +/** + * An array containing the groups members as `XcodeGroupMember` types. + */ +@property(nonatomic, strong, readonly) NSMutableArray *versions; + +@property(nonatomic, strong) NSString *currentVersion; + +#pragma mark Initializers + ++ (XCVersionGroup *)versionGroupWithProject:(XCProject *)project + key:(NSString *)key + path:(NSString *)path + children:(NSArray *)children + currentVersion:(NSString *)currentVersion; + +- (id)initWithProject:(XCProject *)project + key:(NSString *)key + path:(NSString *)path + children:(NSArray *)children + currentVersion:(NSString *)currentVersion; + +#pragma mark Parent group + +- (void)removeFromParentGroup; + +- (void)removeFromParentDeletingChildren:(BOOL)deleteChildren; + +- (XCGroup *)parentGroup; + +#pragma mark Adding children + +/** + * Adds a source file. The only valid file type is XCDataModel + */ +- (void)addDataModelSource:(XCSourceFileDefinition *)sourceFileDefinition; + +#pragma mark Locating children + +- (NSArray *)members; + +- (NSArray *)buildFileKeys; + +/** + * Returns the child with the specified key, or nil. + */ +- (XCSourceFile *)memberWithKey:(NSString *)key; + +/** + * Returns the child with the specified name, or nil. + */ +- (XCSourceFile *)memberWithDisplayName:(NSString *)name; + +- (NSDictionary *)asDictionary; + +@end diff --git a/XcodeZombieCodeAnalyzer/XcodeZombieCodeAnalyzer/Vendor/XcodeEditor/XCVersionGroup.m b/XcodeZombieCodeAnalyzer/XcodeZombieCodeAnalyzer/Vendor/XcodeEditor/XCVersionGroup.m new file mode 100644 index 0000000..e884cac --- /dev/null +++ b/XcodeZombieCodeAnalyzer/XcodeZombieCodeAnalyzer/Vendor/XcodeEditor/XCVersionGroup.m @@ -0,0 +1,492 @@ +// +// XCCoreDataModelVersioned.m +// xcode-editor +// +// Created by joel on 04/09/15. +// +// + +#import "XCVersionGroup.h" +#import "Utils/XCKeyBuilder.h" +#import "XCFileOperationQueue.h" +#import "XCProject+SubProject.h" +#import "XCProject.h" +#import "XCSourceFile.h" +#import "XCSourceFileDefinition.h" +#import "XCTarget.h" + +@implementation XCVersionGroup + +//------------------------------------------------------------------------------------------- +#pragma mark - Class Methods +//------------------------------------------------------------------------------------------- + ++ (XCVersionGroup *)versionGroupWithProject:(XCProject *)project + key:(NSString *)key + path:(NSString *)path + children:(NSArray *)children + currentVersion:(NSString *)currentVersion { + return [[XCVersionGroup alloc] initWithProject:project + key:key + path:path + children:children + currentVersion:currentVersion]; +} + +//------------------------------------------------------------------------------------------- +#pragma mark - Initialization & Destruction +//------------------------------------------------------------------------------------------- + +- (id)initWithProject:(XCProject *)project + key:(NSString *)key + path:(NSString *)path + children:(NSArray *)children + currentVersion:(NSString *)currentVersion { + self = [super init]; + if (self) { + _project = project; + _fileOperationQueue = [_project fileOperationQueue]; + _key = [key copy]; + _currentVersion = [currentVersion copy]; + _pathRelativeToParent = [path copy]; + + _children = [children mutableCopy]; + if (!_children) { + _children = [[NSMutableArray alloc] init]; + } + } + return self; +} + +//------------------------------------------------------------------------------------------- +#pragma mark - Interface Methods +//------------------------------------------------------------------------------------------- + +#pragma mark Parent group + +- (void)removeFromParentGroup { + [self removeFromParentDeletingChildren:NO]; +} + +- (void)removeFromParentDeletingChildren:(BOOL)deleteChildren { + if (deleteChildren) { + [_fileOperationQueue queueDeletion:[self pathRelativeToProjectRoot]]; + } + NSDictionary *dictionary = [_project objects][_key]; + NSLog(@"Here's the dictionary: %@", dictionary); + + [[_project objects] removeObjectForKey:_key]; + + dictionary = [_project objects][_key]; + NSLog(@"Here's the dictionary: %@", dictionary); + + for (XCTarget *target in [_project targets]) { + for (XCSourceFile *source in [self members]) { + [target removeMemberWithKey:source.key]; + } + } + NSLog(@"Done!!!"); +} + +- (XCGroup *)parentGroup { + return [_project groupForGroupMemberWithKey:_key]; +} + +- (BOOL)isRootGroup { + return [self pathRelativeToParent] == nil && [self displayName] == nil; +} + +//------------------------------------------------------------------------------------------- +#pragma mark Adding children + +- (void)addDataModelSource:(XCSourceFileDefinition *)sourceFileDefinition { + if ([sourceFileDefinition type] == XCDataModel) { + [self makeGroupMemberWithName:[sourceFileDefinition sourceFileName] + contents:[sourceFileDefinition data] + type:[sourceFileDefinition type] + fileOperationStyle:[sourceFileDefinition fileOperationType]]; + + [_project objects][_key] = [self asDictionary]; + } else { + [NSException + raise:NSInvalidArgumentException + format:@"Project file of type %@ can't be a child of a %@", + _versionGroupType, + NSStringFromXCSourceFileType([sourceFileDefinition type])]; + } +} + +//------------------------------------------------------------------------------------------- +#pragma mark Members + +- (NSArray *)members { + if (_members == nil) { + _members = [[NSMutableArray alloc] init]; + for (NSString *childKey in _children) { + XcodeMemberType type = [self typeForKey:childKey]; + + @autoreleasepool { + if (type == PBXFileReferenceType) { + [_members addObject:[_project fileWithKey:childKey]]; + } + } + } + } + return _members; +} + +- (NSArray *)buildFileKeys { + NSMutableArray *arrayOfBuildFileKeys = [NSMutableArray array]; + for (id groupMember in [self members]) { + + if ([groupMember groupMemberType] == PBXGroupType || + [groupMember groupMemberType] == PBXVariantGroupType) { + XCGroup *group = (XCGroup *)groupMember; + [arrayOfBuildFileKeys addObjectsFromArray:[group buildFileKeys]]; + } else if ([groupMember groupMemberType] == PBXFileReferenceType) { + [arrayOfBuildFileKeys addObject:[groupMember key]]; + } + } + return arrayOfBuildFileKeys; +} + +- (XCSourceFile *)memberWithKey:(NSString *)key { + XCSourceFile *groupMember = nil; + + if ([_children containsObject:key]) { + XcodeMemberType type = [self typeForKey:key]; + if (type == PBXFileReferenceType) { + groupMember = [_project fileWithKey:key]; + } + } + return groupMember; +} + +- (id)memberWithDisplayName:(NSString *)name { + for (id member in [self members]) { + if ([[member displayName] isEqualToString:name]) { + return member; + } + } + return nil; +} + +#pragma Current version + +- (void)setCurrentVersion:(NSString *)currentVersion { + [self willChangeValueForKey:@"currentVersion"]; + _currentVersion = currentVersion; + [self didChangeValueForKey:@"currentVersion"]; + + [_project objects][_key] = [self asDictionary]; +} +//------------------------------------------------------------------------------------------- +#pragma mark - Protocol Methods + +- (XcodeMemberType)groupMemberType { + return [self typeForKey:self.key]; +} + +- (NSString *)displayName { + if (_alias) { + return _alias; + } + return [_pathRelativeToParent lastPathComponent]; +} + +- (NSString *)pathRelativeToProjectRoot { + if (_pathRelativeToProjectRoot == nil) { + NSMutableArray *pathComponents = [[NSMutableArray alloc] init]; + XCGroup *group = nil; + NSString *key = [_key copy]; + + while ((group = [_project groupForGroupMemberWithKey:key]) != nil && + [group pathRelativeToParent] != nil) { + [pathComponents addObject:[group pathRelativeToParent]]; + key = [[group key] copy]; + } + + NSMutableString *fullPath = [[NSMutableString alloc] init]; + for (NSInteger i = (NSInteger)[pathComponents count] - 1; i >= 0; i--) { + [fullPath appendFormat:@"%@/", pathComponents[i]]; + } + _pathRelativeToProjectRoot = + [[fullPath stringByAppendingPathComponent:_pathRelativeToParent] copy]; + } + return _pathRelativeToProjectRoot; +} + +//------------------------------------------------------------------------------------------- +#pragma mark - Build file Methods +- (XcodeMemberType)buildPhase { + return PBXSourcesBuildPhaseType; +} + +- (NSString *)buildFileKey { + if (_buildFileKey == nil) { + [[_project objects] enumerateKeysAndObjectsUsingBlock:^( + NSString *key, NSDictionary *obj, BOOL *stop) { + if ([[obj valueForKey:@"isa"] xce_hasBuildFileType]) { + if ([[obj valueForKey:@"fileRef"] isEqualToString:_key]) { + _buildFileKey = [key copy]; + } + } + }]; + } + return [_buildFileKey copy]; +} + +- (void)becomeBuildFile { + if (![self isBuildFile]) { + NSMutableDictionary *sourceBuildFile = [NSMutableDictionary dictionary]; + sourceBuildFile[@"isa"] = + [NSString xce_stringFromMemberType:PBXBuildFileType]; + sourceBuildFile[@"fileRef"] = _key; + NSString *buildFileKey = [[XCKeyBuilder + forItemNamed:[self.displayName stringByAppendingString:@".buildFile"]] + build]; + [_project objects][buildFileKey] = sourceBuildFile; + } +} + +- (BOOL)isBuildFile { + if (_isBuildFile == nil) { + _isBuildFile = @NO; + [[_project objects] enumerateKeysAndObjectsUsingBlock:^( + NSString *key, NSDictionary *obj, BOOL *stop) { + if ([[obj valueForKey:@"isa"] xce_hasBuildFileType]) { + if ([[obj valueForKey:@"fileRef"] isEqualToString:_key]) { + _isBuildFile = nil; + + _isBuildFile = @YES; + } + } + }]; + } + return [_isBuildFile boolValue]; +} + +//------------------------------------------------------------------------------------------- +#pragma mark - Utility Methods + +- (NSString *)description { + return [NSString stringWithFormat:@"Group: displayName = %@, key=%@", + [self displayName], _key]; +} + +//------------------------------------------------------------------------------------------- +#pragma mark - Private Methods +//------------------------------------------------------------------------------------------- + +- (void)addMemberWithKey:(NSString *)key { + for (NSString *childKey in _children) { + if ([childKey isEqualToString:key]) { + [self flagMembersAsDirty]; + return; + } + } + [_children addObject:key]; + [self flagMembersAsDirty]; +} + +- (void)flagMembersAsDirty { + _members = nil; +} + +//------------------------------------------------------------------------------------------- + +- (NSString *)makeGroupMemberWithName:(NSString *)name + contents:(id)contents + type:(XcodeSourceFileType)type + fileOperationStyle:(XCFileOperationType)fileOperationStyle { + NSString *fileKey; + NSString *filePath; + XCSourceFile *currentSourceFile = + (XCSourceFile *)[self memberWithDisplayName:name]; + if ((currentSourceFile) == nil) { + NSString *refName = nil; + if (type == AssetCatalog) { + refName = [name lastPathComponent]; + } + NSDictionary *reference = [self makeFileReferenceWithPath:name + name:refName + type:type]; + fileKey = [[XCKeyBuilder forItemNamed:name] build]; + [_project objects][fileKey] = reference; + [self addMemberWithKey:fileKey]; + + filePath = [self pathRelativeToProjectRoot]; + } else { + filePath = [[currentSourceFile pathRelativeToProjectRoot] + stringByDeletingLastPathComponent]; + fileKey = currentSourceFile.key; + } + + BOOL writeFile = NO; + if (fileOperationStyle == XCFileOperationTypeOverwrite) { + writeFile = YES; + [_fileOperationQueue fileWithName:name existsInProjectDirectory:filePath]; + } else if (fileOperationStyle == XCFileOperationTypeAcceptExisting && + ![_fileOperationQueue fileWithName:name + existsInProjectDirectory:filePath]) { + writeFile = YES; + } + if (writeFile) { + [_fileOperationQueue queueDirectory:name inDirectory:filePath]; + [_fileOperationQueue commitFileOperations]; + filePath = [filePath stringByAppendingPathComponent:name]; + name = @"contents"; + if ([contents isKindOfClass:[NSString class]]) { + [_fileOperationQueue queueTextFile:name + inDirectory:filePath + withContents:contents]; + } else { + [_fileOperationQueue queueDataFile:name + inDirectory:filePath + withContents:contents]; + } + } + + return fileKey; +} + +//------------------------------------------------------------------------------------------- + +#pragma mark Xcodeproj methods + +// creates PBXFileReference and adds to group if not already there; returns key +// for file reference. Locates +// member via path rather than name, because that is how subprojects are stored +// by Xcode +- (void)makeGroupMemberWithName:(NSString *)name + path:(NSString *)path + type:(XcodeSourceFileType)type + fileOperationStyle:(XCFileOperationType)fileOperationStyle { + XCSourceFile *currentSourceFile = + (XCSourceFile *)[self memberWithDisplayName:name]; + if ((currentSourceFile) == nil) { + NSDictionary *reference = [self makeFileReferenceWithPath:path + name:name + type:type]; + NSString *fileKey = [[XCKeyBuilder forItemNamed:name] build]; + [_project objects][fileKey] = reference; + [self addMemberWithKey:fileKey]; + } +} + +// removes PBXFileReference from group and project +- (void)removeGroupMemberWithKey:(NSString *)key { + NSMutableArray *children = [self valueForKey:@"children"]; + [children removeObject:key]; + _project.objects[_key] = [self asDictionary]; + // remove PBXFileReference + [_project.objects removeObjectForKey:key]; +} + +// removes the given key from the files arrays of the given section, if found +// (intended to be used with +// PBXFrameworksBuildPhase and PBXResourcesBuildPhase) +// they are not required because we are currently not adding these entries; +// Xcode is doing it for us. The existing +// code for adding to a target doesn't do it, and I didn't add it since Xcode +// will take care of it for me and I was +// avoiding modifying existing code as much as possible) +- (void)removeBuildPhaseFileKey:(NSString *)key + forType:(XcodeMemberType)memberType { + NSArray *buildPhases = [_project keysForProjectObjectsOfType:memberType + withIdentifier:nil + singleton:NO + required:NO]; + for (NSString *buildPhaseKey in buildPhases) { + NSDictionary *buildPhaseDict = + [[_project objects] valueForKey:buildPhaseKey]; + NSMutableArray *fileKeys = [buildPhaseDict valueForKey:@"files"]; + for (NSString *fileKey in fileKeys) { + if ([fileKey isEqualToString:key]) { + [fileKeys removeObject:fileKey]; + } + } + } +} + +// removes entries from PBXBuildFiles, PBXFrameworksBuildPhase and +// PBXResourcesBuildPhase +- (void)removeProductsGroupFromProject:(NSString *)key { + // remove product group's build products from PDXBuildFiles + NSDictionary *productsGroup = _project.objects[key]; + for (NSString *childKey in [productsGroup valueForKey:@"children"]) { + NSArray *buildFileKeys = + [_project keysForProjectObjectsOfType:PBXBuildFileType + withIdentifier:childKey + singleton:NO + required:NO]; + // could be zero - we didn't add the test bundle as a build product + if ([buildFileKeys count] == 1) { + NSString *buildFileKey = buildFileKeys[0]; + [[_project objects] removeObjectForKey:buildFileKey]; + [self removeBuildPhaseFileKey:buildFileKey + forType:PBXFrameworksBuildPhaseType]; + [self removeBuildPhaseFileKey:buildFileKey + forType:PBXResourcesBuildPhaseType]; + } + } +} + +//------------------------------------------------------------------------------------------- + +#pragma mark Dictionary Representations + +- (NSDictionary *)makeFileReferenceWithPath:(NSString *)path + name:(NSString *)name + type:(XcodeSourceFileType)type { + NSMutableDictionary *reference = [NSMutableDictionary dictionary]; + reference[@"isa"] = [NSString xce_stringFromMemberType:PBXFileReferenceType]; + reference[@"fileEncoding"] = @"4"; + reference[@"lastKnownFileType"] = NSStringFromXCSourceFileType(type); + if (name != nil) { + reference[@"name"] = [name lastPathComponent]; + } + if (path != nil) { + reference[@"path"] = path; + } + reference[@"sourceTree"] = @""; + return reference; +} + +- (NSDictionary *)asDictionary { + NSMutableDictionary *groupData = [NSMutableDictionary dictionary]; + groupData[@"isa"] = [NSString xce_stringFromMemberType:XCVersionGroupType]; + groupData[@"sourceTree"] = @""; + groupData[@"versionGroupType"] = @"wrapper.xcdatamodel"; + + if (_alias != nil) { + groupData[@"name"] = _alias; + } + + if (_pathRelativeToParent) { + groupData[@"path"] = _pathRelativeToParent; + } + + if (_children) { + groupData[@"children"] = _children; + } + + if (_currentVersion) { + groupData[@"currentVersion"] = _currentVersion; + } + + return groupData; +} + +- (XcodeMemberType)typeForKey:(NSString *)key { + NSDictionary *obj = [[_project objects] valueForKey:key]; + return [[obj valueForKey:@"isa"] xce_asMemberType]; +} + +- (void)addSourceFile:(XCSourceFile *)sourceFile toTargets:(NSArray *)targets { + for (XCTarget *target in targets) { + [target addMember:sourceFile]; + } +} + +@end diff --git a/XcodeZombieCodeAnalyzer/XcodeZombieCodeAnalyzer/Vendor/XcodeEditor/XCXibDefinition.h b/XcodeZombieCodeAnalyzer/XcodeZombieCodeAnalyzer/Vendor/XcodeEditor/XCXibDefinition.h new file mode 100644 index 0000000..4d5f126 --- /dev/null +++ b/XcodeZombieCodeAnalyzer/XcodeZombieCodeAnalyzer/Vendor/XcodeEditor/XCXibDefinition.h @@ -0,0 +1,33 @@ +//////////////////////////////////////////////////////////////////////////////// +// +// JASPER BLUES +// Copyright 2012 Jasper Blues +// All Rights Reserved. +// +// NOTICE: Jasper Blues permits you to use, modify, and distribute this file +// in accordance with the terms of the license agreement accompanying it. +// +//////////////////////////////////////////////////////////////////////////////// + +#import "XCAbstractDefinition.h" +#import + +@interface XCXibDefinition : XCAbstractDefinition { + NSString *_name; + NSString *_content; +} +@property(nonatomic, strong, readonly) NSString *name; +@property(nonatomic, strong) NSString *content; + ++ (XCXibDefinition *)xibDefinitionWithName:(NSString *)name; + ++ (XCXibDefinition *)xibDefinitionWithName:(NSString *)name + content:(NSString *)content; + +- (id)initWithName:(NSString *)name; + +- (id)initWithName:(NSString *)name content:(NSString *)content; + +- (NSString *)xibFileName; + +@end diff --git a/XcodeZombieCodeAnalyzer/XcodeZombieCodeAnalyzer/Vendor/XcodeEditor/XCXibDefinition.m b/XcodeZombieCodeAnalyzer/XcodeZombieCodeAnalyzer/Vendor/XcodeEditor/XCXibDefinition.m new file mode 100644 index 0000000..ce00a0c --- /dev/null +++ b/XcodeZombieCodeAnalyzer/XcodeZombieCodeAnalyzer/Vendor/XcodeEditor/XCXibDefinition.m @@ -0,0 +1,57 @@ +//////////////////////////////////////////////////////////////////////////////// +// +// JASPER BLUES +// Copyright 2012 Jasper Blues +// All Rights Reserved. +// +// NOTICE: Jasper Blues permits you to use, modify, and distribute this file +// in accordance with the terms of the license agreement accompanying it. +// +//////////////////////////////////////////////////////////////////////////////// + +#import "XCXibDefinition.h" + +@implementation XCXibDefinition + +@synthesize name = _name; +@synthesize content = _content; + +/* ====================================================================================================================================== + */ +#pragma mark - Class Methods + ++ (XCXibDefinition *)xibDefinitionWithName:(NSString *)name { + return [[XCXibDefinition alloc] initWithName:name]; +} + ++ (XCXibDefinition *)xibDefinitionWithName:(NSString *)name + content:(NSString *)content { + return [[XCXibDefinition alloc] initWithName:name content:content]; +} + +/* ====================================================================================================================================== + */ +#pragma mark - Initialization & Destruction + +- (id)initWithName:(NSString *)name { + return [self initWithName:name content:nil]; +} + +- (id)initWithName:(NSString *)name content:(NSString *)content { + self = [super init]; + if (self) { + _name = [name copy]; + _content = [content copy]; + } + return self; +} + +/* ====================================================================================================================================== + */ +#pragma mark - Interface Methods + +- (NSString *)xibFileName { + return [_name stringByAppendingString:@".xib"]; +} + +@end \ No newline at end of file diff --git a/XcodeZombieCodeAnalyzer/XcodeZombieCodeAnalyzer/Vendor/XcodeEditor/XcodeEditor.h b/XcodeZombieCodeAnalyzer/XcodeZombieCodeAnalyzer/Vendor/XcodeEditor/XcodeEditor.h new file mode 100644 index 0000000..194f315 --- /dev/null +++ b/XcodeZombieCodeAnalyzer/XcodeZombieCodeAnalyzer/Vendor/XcodeEditor/XcodeEditor.h @@ -0,0 +1,39 @@ +//////////////////////////////////////////////////////////////////////////////// +// +// JASPER BLUES +// Copyright 2012 Jasper Blues +// All Rights Reserved. +// +// NOTICE: Jasper Blues permits you to use, modify, and distribute this file +// in accordance with the terms of the license agreement accompanying it. +// +//////////////////////////////////////////////////////////////////////////////// + +#import + +//! Project version number for XcodeEditor. +FOUNDATION_EXPORT double XcodeEditorVersionNumber; + +//! Project version string for XcodeEditor. +FOUNDATION_EXPORT const unsigned char XcodeEditorVersionString[]; + +// In this header, you should import all the public headers of your framework +// using statements like #import + +#import "XCAbstractDefinition.h" +#import "XCBuildShellScript.h" +#import "XCBuildShellScriptDefinition.h" +#import "XCClassDefinition.h" +#import "XCFileOperationQueue.h" +#import "XCFrameworkDefinition.h" +#import "XCGroup.h" +#import "XCKeyBuilder.h" +#import "XCProject+SubProject.h" +#import "XCProject.h" +#import "XCProjectBuildConfig.h" +#import "XCSourceFile.h" +#import "XCSourceFileDefinition.h" +#import "XCSubProjectDefinition.h" +#import "XCTarget.h" +#import "XCVersionGroup.h" +#import "XCXibDefinition.h" \ No newline at end of file diff --git a/XcodeZombieCodeAnalyzer/XcodeZombieCodeAnalyzer/Vendor/XcodeEditor/XcodeGroupMember.h b/XcodeZombieCodeAnalyzer/XcodeZombieCodeAnalyzer/Vendor/XcodeEditor/XcodeGroupMember.h new file mode 100644 index 0000000..0efd02b --- /dev/null +++ b/XcodeZombieCodeAnalyzer/XcodeZombieCodeAnalyzer/Vendor/XcodeEditor/XcodeGroupMember.h @@ -0,0 +1,27 @@ +//////////////////////////////////////////////////////////////////////////////// +// +// JASPER BLUES +// Copyright 2012 Jasper Blues +// All Rights Reserved. +// +// NOTICE: Jasper Blues permits you to use, modify, and distribute this file +// in accordance with the terms of the license agreement accompanying it. +// +//////////////////////////////////////////////////////////////////////////////// + +#import "XcodeMemberType.h" + +@protocol XcodeGroupMember + +- (NSString *)key; + +- (NSString *)displayName; + +- (NSString *)pathRelativeToProjectRoot; + +/** + * Group members can either be other groups (PBXGroup) or source files + * (PBXFileReference). + */ +- (XcodeMemberType)groupMemberType; +@end \ No newline at end of file diff --git a/XcodeZombieCodeAnalyzer/XcodeZombieCodeAnalyzer/Vendor/XcodeEditor/XcodeMemberType.h b/XcodeZombieCodeAnalyzer/XcodeZombieCodeAnalyzer/Vendor/XcodeEditor/XcodeMemberType.h new file mode 100644 index 0000000..19df152 --- /dev/null +++ b/XcodeZombieCodeAnalyzer/XcodeZombieCodeAnalyzer/Vendor/XcodeEditor/XcodeMemberType.h @@ -0,0 +1,55 @@ +//////////////////////////////////////////////////////////////////////////////// +// +// JASPER BLUES +// Copyright 2012 Jasper Blues +// All Rights Reserved. +// +// NOTICE: Jasper Blues permits you to use, modify, and distribute this file +// in accordance with the terms of the license agreement accompanying it. +// +//////////////////////////////////////////////////////////////////////////////// + +#import + +typedef enum { + PBXNilType, + PBXBuildFileType, + PBXContainerItemProxyType, + PBXCopyFilesBuildPhaseType, + PBXFileReferenceType, + PBXFrameworksBuildPhaseType, + PBXGroupType, + PBXNativeTargetType, + PBXProjectType, + PBXReferenceProxyType, + PBXResourcesBuildPhaseType, + PBXSourcesBuildPhaseType, + PBXTargetDependencyType, + PBXVariantGroupType, + XCBuildConfigurationType, + XCConfigurationListType, + PBXShellScriptBuildPhase, + XCVersionGroupType +} XcodeMemberType; + +@interface NSString (XcodeMemberTypeExtensions) + ++ (NSString *)xce_stringFromMemberType:(XcodeMemberType)nodeType; + +- (XcodeMemberType)xce_asMemberType; + +- (BOOL)xce_hasFileReferenceType; +- (BOOL)xce_hasFileReferenceOrReferenceProxyType; +- (BOOL)xce_hasReferenceProxyType; +- (BOOL)xce_hasGroupType; +- (BOOL)xce_hasProjectType; +- (BOOL)xce_hasNativeTargetType; +- (BOOL)xce_hasBuildFileType; +- (BOOL)xce_hasBuildConfigurationType; +- (BOOL)xce_hasContainerItemProxyType; +- (BOOL)xce_hasResourcesBuildPhaseType; +- (BOOL)xce_hasShellScriptBuildPhase; +- (BOOL)xce_hasSourcesOrFrameworksBuildPhaseType; +- (BOOL)xce_hasVersionedGroupType; + +@end diff --git a/XcodeZombieCodeAnalyzer/XcodeZombieCodeAnalyzer/Vendor/XcodeEditor/XcodeMemberType.m b/XcodeZombieCodeAnalyzer/XcodeZombieCodeAnalyzer/Vendor/XcodeEditor/XcodeMemberType.m new file mode 100644 index 0000000..218e894 --- /dev/null +++ b/XcodeZombieCodeAnalyzer/XcodeZombieCodeAnalyzer/Vendor/XcodeEditor/XcodeMemberType.m @@ -0,0 +1,134 @@ +//////////////////////////////////////////////////////////////////////////////// +// +// JASPER BLUES +// Copyright 2012 Jasper Blues +// All Rights Reserved. +// +// NOTICE: Jasper Blues permits you to use, modify, and distribute this file +// in accordance with the terms of the license agreement accompanying it. +// +//////////////////////////////////////////////////////////////////////////////// + +#import "XcodeMemberType.h" + +static NSString *const kPBXNilType = @"PBXNilType"; +static NSString *const kPBXBuildFile = @"PBXBuildFile"; +static NSString *const kPBXContainerItemProxy = @"PBXContainerItemProxy"; +static NSString *const kPBXCopyFilesBuildPhase = @"PBXCopyFilesBuildPhase"; +static NSString *const kPBXFileReference = @"PBXFileReference"; +static NSString *const kPBXFrameworksBuildPhase = @"PBXFrameworksBuildPhase"; +static NSString *const kPBXGroup = @"PBXGroup"; +static NSString *const kPBXNativeTarget = @"PBXNativeTarget"; +static NSString *const kPBXProject = @"PBXProject"; +static NSString *const kPBXReferenceProxy = @"PBXReferenceProxy"; +static NSString *const kPBXResourcesBuildPhase = @"PBXResourcesBuildPhase"; +static NSString *const kPBXShellScriptBuildPhase = @"PBXShellScriptBuildPhase"; +static NSString *const kPBXSourcesBuildPhase = @"PBXSourcesBuildPhase"; +static NSString *const kPBXTargetDependency = @"PBXTargetDependency"; +static NSString *const kPBXVariantGroup = @"PBXVariantGroup"; +static NSString *const kXCBuildConfiguration = @"XCBuildConfiguration"; +static NSString *const kXCConfigurationList = @"XCConfigurationList"; +static NSString *const kXCVersionGroup = @"XCVersionGroup"; + +static NSDictionary *DictionaryWithProjectNodeTypesAsStrings() { + // This is the most vital operation on adding 500+ files + // So, we caching this dictionary + static NSDictionary *_projectNodeTypesAsStrings; + if (_projectNodeTypesAsStrings) { + return _projectNodeTypesAsStrings; + } + + static dispatch_once_t onceToken; + dispatch_once(&onceToken, ^{ + _projectNodeTypesAsStrings = @{ + kPBXNilType : @(PBXNilType), + kPBXBuildFile : @(PBXBuildFileType), + kPBXContainerItemProxy : @(PBXContainerItemProxyType), + kPBXCopyFilesBuildPhase : @(PBXCopyFilesBuildPhaseType), + kPBXFileReference : @(PBXFileReferenceType), + kPBXFrameworksBuildPhase : @(PBXFrameworksBuildPhaseType), + kPBXGroup : @(PBXGroupType), + kPBXNativeTarget : @(PBXNativeTargetType), + kPBXProject : @(PBXProjectType), + kPBXReferenceProxy : @(PBXReferenceProxyType), + kPBXResourcesBuildPhase : @(PBXResourcesBuildPhaseType), + kPBXSourcesBuildPhase : @(PBXSourcesBuildPhaseType), + kPBXTargetDependency : @(PBXTargetDependencyType), + kPBXVariantGroup : @(PBXVariantGroupType), + kXCBuildConfiguration : @(XCBuildConfigurationType), + kXCConfigurationList : @(XCConfigurationListType), + kPBXShellScriptBuildPhase : @(PBXShellScriptBuildPhase), + kXCVersionGroup : @(XCVersionGroupType) + }; + }); + return _projectNodeTypesAsStrings; +} + +@implementation NSString (XcodeMemberTypeExtensions) + ++ (NSString *)xce_stringFromMemberType:(XcodeMemberType)nodeType { + NSDictionary *nodeTypesToString = DictionaryWithProjectNodeTypesAsStrings(); + return [[nodeTypesToString allKeysForObject:@(nodeType)] firstObject]; +} + +- (XcodeMemberType)xce_asMemberType { + NSDictionary *nodeTypesToString = DictionaryWithProjectNodeTypesAsStrings(); + return (XcodeMemberType)[[nodeTypesToString objectForKey:self] intValue]; +} + +- (BOOL)xce_hasFileReferenceType { + return [self isEqualToString:kPBXFileReference]; +} + +- (BOOL)xce_hasFileReferenceOrReferenceProxyType { + return [self isEqualToString:kPBXFileReference] || + [self isEqualToString:kPBXReferenceProxy]; +} + +- (BOOL)xce_hasReferenceProxyType { + return [self isEqualToString:kPBXReferenceProxy]; +} + +- (BOOL)xce_hasGroupType { + return [self isEqualToString:kPBXGroup] || + [self isEqualToString:kPBXVariantGroup]; +} + +- (BOOL)xce_hasProjectType { + return [self isEqualToString:kPBXProject]; +} + +- (BOOL)xce_hasNativeTargetType { + return [self isEqualToString:kPBXNativeTarget]; +} + +- (BOOL)xce_hasBuildFileType { + return [self isEqualToString:kPBXBuildFile]; +} + +- (BOOL)xce_hasBuildConfigurationType { + return [self isEqualToString:kXCBuildConfiguration]; +} + +- (BOOL)xce_hasShellScriptBuildPhase { + return [self isEqualToString:kPBXShellScriptBuildPhase]; +} + +- (BOOL)xce_hasContainerItemProxyType { + return [self isEqualToString:kPBXContainerItemProxy]; +} + +- (BOOL)xce_hasResourcesBuildPhaseType { + return [self isEqualToString:kPBXResourcesBuildPhase]; +} + +- (BOOL)xce_hasSourcesOrFrameworksBuildPhaseType { + return [self isEqualToString:kPBXSourcesBuildPhase] || + [self isEqualToString:kPBXFrameworksBuildPhase]; +} + +- (BOOL)xce_hasVersionedGroupType { + return [self isEqualToString:kXCVersionGroup]; +} + +@end \ No newline at end of file diff --git a/XcodeZombieCodeAnalyzer/XcodeZombieCodeAnalyzer/Vendor/XcodeEditor/XcodeSourceFileType.h b/XcodeZombieCodeAnalyzer/XcodeZombieCodeAnalyzer/Vendor/XcodeEditor/XcodeSourceFileType.h new file mode 100644 index 0000000..113500a --- /dev/null +++ b/XcodeZombieCodeAnalyzer/XcodeZombieCodeAnalyzer/Vendor/XcodeEditor/XcodeSourceFileType.h @@ -0,0 +1,47 @@ +//////////////////////////////////////////////////////////////////////////////// +// +// JASPER BLUES +// Copyright 2012 Jasper Blues +// All Rights Reserved. +// +// NOTICE: Jasper Blues permits you to use, modify, and distribute this file +// in accordance with the terms of the license agreement accompanying it. +// +//////////////////////////////////////////////////////////////////////////////// + +#import + +typedef NS_OPTIONS(NSInteger, XcodeSourceFileType) { + FileTypeNil = 0, // Unknown filetype + Framework = 1, // .framework + PropertyList = 2, // .plist + SourceCodeHeader = 3, // .h + SourceCodeObjC = 4, // .m + SourceCodeObjCPlusPlus = 5, // .mm + SourceCodeCPlusPlus = 6, // .cpp + XibFile = 7, // .xib + ImageResourcePNG = 8, // .png + Bundle = 9, // .bundle .octet + Archive = 10, // .a files + HTML = 11, // HTML file + TEXT = 12, // Some text file + XcodeProject = 13, // .xcodeproj + Folder = 14, // a Folder reference + AssetCatalog = 15, // Assets + SourceCodeSwift = 16, // .swift + Application = 17, // .app (wrapper.application) + Playground = 18, // .playground (file.playground) + ShellScript = 19, // no suffix Xcode seems to detect (text.script.sh) + Markdown = 20, // .md (net.daringfileball.markdown) + XMLPropertyList = 21, // .plist (text.plist.xml) + Storyboard = 22, // .storyboard (file.storyboard) + XCConfig = 23, // .xcconfig + XCDataModel = 24, // .xcdatamodel + LocalizableStrings = 25 // .strings +}; + +NSString *NSStringFromXCSourceFileType(XcodeSourceFileType type); + +XcodeSourceFileType XCSourceFileTypeFromStringRepresentation(NSString *string); + +XcodeSourceFileType XCSourceFileTypeFromFileName(NSString *fileName); diff --git a/XcodeZombieCodeAnalyzer/XcodeZombieCodeAnalyzer/Vendor/XcodeEditor/XcodeSourceFileType.m b/XcodeZombieCodeAnalyzer/XcodeZombieCodeAnalyzer/Vendor/XcodeEditor/XcodeSourceFileType.m new file mode 100644 index 0000000..86cf507 --- /dev/null +++ b/XcodeZombieCodeAnalyzer/XcodeZombieCodeAnalyzer/Vendor/XcodeEditor/XcodeSourceFileType.m @@ -0,0 +1,93 @@ +//////////////////////////////////////////////////////////////////////////////// +// +// JASPER BLUES +// Copyright 2012 Jasper Blues +// All Rights Reserved. +// +// NOTICE: Jasper Blues permits you to use, modify, and distribute this file +// in accordance with the terms of the license agreement accompanying it. +// +//////////////////////////////////////////////////////////////////////////////// + +#import "XcodeSourceFileType.h" + +static NSDictionary *NSDictionaryWithXCFileReferenceTypes() { + static NSDictionary *dictionary; + static dispatch_once_t onceToken; + dispatch_once(&onceToken, ^{ + dictionary = @{ + @"sourcecode.c.h" : @(SourceCodeHeader), + @"sourcecode.c.objc" : @(SourceCodeObjC), + @"wrapper.framework" : @(Framework), + @"text.plist.strings" : @(PropertyList), + @"sourcecode.cpp.objcpp" : @(SourceCodeObjCPlusPlus), + @"sourcecode.cpp.cpp" : @(SourceCodeCPlusPlus), + @"file.xib" : @(XibFile), + @"image.png" : @(ImageResourcePNG), + @"wrapper.cfbundle" : @(Bundle), + @"archive.ar" : @(Archive), + @"text.html" : @(HTML), + @"text" : @(TEXT), + @"wrapper.pb-project" : @(XcodeProject), + @"folder" : @(Folder), + @"folder.assetcatalog" : @(AssetCatalog), + @"sourcecode.swift" : @(SourceCodeSwift), + @"wrapper.application" : @(Application), + @"file.playground" : @(Playground), + @"text.script.sh" : @(ShellScript), + @"net.daringfireball.markdown" : @(Markdown), + @"text.plist.xml" : @(XMLPropertyList), + @"file.storyboard" : @(Storyboard), + @"text.xcconfig" : @(XCConfig), + @"wrapper.xcconfig" : @(XCConfig), + @"wrapper.xcdatamodel" : @(XCDataModel), + @"file.strings" : @(LocalizableStrings) + }; + }); + + return dictionary; +} + +NSString *NSStringFromXCSourceFileType(XcodeSourceFileType type) { + return [[NSDictionaryWithXCFileReferenceTypes() allKeysForObject:@(type)] + objectAtIndex:0]; +} + +XcodeSourceFileType XCSourceFileTypeFromStringRepresentation(NSString *string) { + NSDictionary *typeStrings = NSDictionaryWithXCFileReferenceTypes(); + + if (typeStrings[string]) { + return (XcodeSourceFileType)[typeStrings[string] intValue]; + } else { + return FileTypeNil; + } +} + +XcodeSourceFileType XCSourceFileTypeFromFileName(NSString *fileName) { + if ([fileName hasSuffix:@".h"] || [fileName hasSuffix:@".hh"] || + [fileName hasSuffix:@".hpp"] || [fileName hasSuffix:@".hxx"]) { + return SourceCodeHeader; + } + if ([fileName hasSuffix:@".c"] || [fileName hasSuffix:@".m"]) { + return SourceCodeObjC; + } + if ([fileName hasSuffix:@".mm"]) { + return SourceCodeObjCPlusPlus; + } + if ([fileName hasSuffix:@".cpp"]) { + return SourceCodeCPlusPlus; + } + if ([fileName hasSuffix:@".swift"]) { + return SourceCodeSwift; + } + if ([fileName hasSuffix:@".xcdatamodel"]) { + return XCDataModel; + } + if ([fileName hasSuffix:@".strings"]) { + return LocalizableStrings; + } + if ([fileName hasSuffix:@".plist"]) { + return PropertyList; + } + return FileTypeNil; +} diff --git a/XcodeZombieCodeAnalyzer/XcodeZombieCodeAnalyzer/Vendor/XcodeEditor/XcodeSourceTreeType.h b/XcodeZombieCodeAnalyzer/XcodeZombieCodeAnalyzer/Vendor/XcodeEditor/XcodeSourceTreeType.h new file mode 100644 index 0000000..4cc951f --- /dev/null +++ b/XcodeZombieCodeAnalyzer/XcodeZombieCodeAnalyzer/Vendor/XcodeEditor/XcodeSourceTreeType.h @@ -0,0 +1,22 @@ +//////////////////////////////////////////////////////////////////////////////// +// +// JASPER BLUES +// Copyright 2012 Jasper Blues +// All Rights Reserved. +// +// NOTICE: Jasper Blues permits you to use, modify, and distribute this file +// in accordance with the terms of the license agreement accompanying it. +// +//////////////////////////////////////////////////////////////////////////////// + +#import + +typedef enum { SourceTreeSDKRoot, SourceTreeGroup } XcodeSourceTreeType; + +@interface NSString (XcodeSourceTreeTypeExtensions) + ++ (NSString *)xce_stringFromSourceTreeType:(XcodeSourceTreeType)nodeType; + +- (XcodeSourceTreeType)xce_asSourceTreeType; + +@end diff --git a/XcodeZombieCodeAnalyzer/XcodeZombieCodeAnalyzer/Vendor/XcodeEditor/XcodeSourceTreeType.m b/XcodeZombieCodeAnalyzer/XcodeZombieCodeAnalyzer/Vendor/XcodeEditor/XcodeSourceTreeType.m new file mode 100644 index 0000000..08e18e7 --- /dev/null +++ b/XcodeZombieCodeAnalyzer/XcodeZombieCodeAnalyzer/Vendor/XcodeEditor/XcodeSourceTreeType.m @@ -0,0 +1,49 @@ +//////////////////////////////////////////////////////////////////////////////// +// +// JASPER BLUES +// Copyright 2012 Jasper Blues +// All Rights Reserved. +// +// NOTICE: Jasper Blues permits you to use, modify, and distribute this file +// in accordance with the terms of the license agreement accompanying it. +// +//////////////////////////////////////////////////////////////////////////////// + +#import "XcodeSourceTreeType.h" + +static NSString *const kPBXSourceTreeSKRoot = @"SDKROOT"; +static NSString *const kPBXSourceTreeGroup = @""; + +static NSDictionary *DictionaryWithProjectSourceTreeTypesAsStrings() { + // This is the most vital operation on adding 500+ files + // So, we caching this dictionary + static NSDictionary *_projectNodeTypesAsStrings; + if (_projectNodeTypesAsStrings) { + return _projectNodeTypesAsStrings; + } + + static dispatch_once_t onceToken; + dispatch_once(&onceToken, ^{ + _projectNodeTypesAsStrings = @{ + kPBXSourceTreeSKRoot : @(SourceTreeSDKRoot), + kPBXSourceTreeGroup : @(SourceTreeGroup), + }; + }); + return _projectNodeTypesAsStrings; +} + +@implementation NSString (XcodeSourceTreeTypeExtensions) + ++ (NSString *)xce_stringFromSourceTreeType:(XcodeSourceTreeType)nodeType { + NSDictionary *nodeTypesToString = + DictionaryWithProjectSourceTreeTypesAsStrings(); + return [[nodeTypesToString allKeysForObject:@(nodeType)] firstObject]; +} + +- (XcodeSourceTreeType)xce_asSourceTreeType { + NSDictionary *nodeTypesToString = + DictionaryWithProjectSourceTreeTypesAsStrings(); + return (XcodeSourceTreeType)[[nodeTypesToString objectForKey:self] intValue]; +} + +@end \ No newline at end of file diff --git a/XcodeZombieCodeAnalyzer/XcodeZombieCodeAnalyzer/XcodeEditorCPPWrapper.h b/XcodeZombieCodeAnalyzer/XcodeZombieCodeAnalyzer/XcodeEditorCPPWrapper.h new file mode 100644 index 0000000..8c3416e --- /dev/null +++ b/XcodeZombieCodeAnalyzer/XcodeZombieCodeAnalyzer/XcodeEditorCPPWrapper.h @@ -0,0 +1,7 @@ +// +// XcodeEditorCPPWrapper.h +// CodeSimilarityChecker +// +// Created by KyleWong on 13/02/2017. +// Copyright © 2017 KyleWong. All rights reserved. +// diff --git a/XcodeZombieCodeAnalyzer/XcodeZombieCodeAnalyzer/XcodeEditorCPPWrapper.mm b/XcodeZombieCodeAnalyzer/XcodeZombieCodeAnalyzer/XcodeEditorCPPWrapper.mm new file mode 100644 index 0000000..6a4f142 --- /dev/null +++ b/XcodeZombieCodeAnalyzer/XcodeZombieCodeAnalyzer/XcodeEditorCPPWrapper.mm @@ -0,0 +1,28 @@ +// +// XcodeEditorCPPWrapper.m +// CodeSimilarityChecker +// +// Created by KyleWong on 13/02/2017. +// Copyright © 2017 KyleWong. All rights reserved. +// + +#import "XcodeEditorCPPWrapper.h" +#import "XcodeEditor.h" + +#include +#include +using namespace std; + +vector getSourcesByProject(string prjPath){ + NSString *prjPathNS = [NSString stringWithCString:prjPath.c_str() encoding:NSUTF8StringEncoding]; + NSString *prjFileFolder = [prjPathNS stringByDeletingLastPathComponent]; + XCProject *project = [XCProject projectWithFilePath:prjPathNS]; + NSArray *filesAry = project.files; + vector filesVec; + for(XCSourceFile *file in filesAry){ + if(file.type == SourceCodeObjC || file.type == SourceCodeObjCPlusPlus || + file.type == SourceCodeCPlusPlus || file.type == SourceCodeSwift) + filesVec.push_back(string([[prjFileFolder stringByAppendingPathComponent:file.pathRelativeToProjectRoot] UTF8String])); + } + return filesVec; +} diff --git a/XcodeZombieCodeAnalyzer/XcodeZombieCodeAnalyzer/ZombieCodeUtil.hpp b/XcodeZombieCodeAnalyzer/XcodeZombieCodeAnalyzer/ZombieCodeUtil.hpp deleted file mode 100644 index d8e5907..0000000 --- a/XcodeZombieCodeAnalyzer/XcodeZombieCodeAnalyzer/ZombieCodeUtil.hpp +++ /dev/null @@ -1,41 +0,0 @@ -// -// CodeCheckUtil.hpp -// ClangCodeCheckPlugin -// -// Created by KyleWong on 14/10/2016. -// -// - -#ifndef CodeCheckUtil_hpp -#define CodeCheckUtil_hpp -#include -#include -#include - - -using namespace std; - -static inline bool has_suffix(const std::string &str, const std::string &suffix) -{ - return str.size() >= suffix.size() && - str.compare(str.size() - suffix.size(), suffix.size(), suffix) == 0; -} - -static inline string <rim(std::string &s) { - s.erase(s.begin(), find_if(s.begin(), s.end(), - not1(ptr_fun(isspace)))); - return s; -} - -// trim from end -static inline string &rtrim(string &s) { - s.erase(find_if(s.rbegin(), s.rend(), - not1(ptr_fun(isspace))).base(), s.end()); - return s; -} - -// trim from both ends -static inline string &trim(string &s) { - return ltrim(rtrim(s)); -} -#endif /* CodeCheckUtil_hpp */ diff --git a/XcodeZombieCodeAnalyzer/XcodeZombieCodeAnalyzer/json.hpp b/XcodeZombieCodeAnalyzer/XcodeZombieCodeAnalyzer/json.hpp deleted file mode 100644 index 36a56af..0000000 --- a/XcodeZombieCodeAnalyzer/XcodeZombieCodeAnalyzer/json.hpp +++ /dev/null @@ -1,10591 +0,0 @@ -/* - __ _____ _____ _____ - __| | __| | | | JSON for Modern C++ -| | |__ | | | | | | version 2.0.5 -|_____|_____|_____|_|___| https://github.com/nlohmann/json - -Licensed under the MIT License . -Copyright (c) 2013-2016 Niels Lohmann . - -Permission is hereby granted, free of charge, to any person obtaining a copy -of this software and associated documentation files (the "Software"), to deal -in the Software without restriction, including without limitation the rights -to use, copy, modify, merge, publish, distribute, sublicense, and/or sell -copies of the Software, and to permit persons to whom the Software is -furnished to do so, subject to the following conditions: - -The above copyright notice and this permission notice shall be included in all -copies or substantial portions of the Software. - -THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR -IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, -FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE -AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER -LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, -OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE -SOFTWARE. -*/ - -#ifndef NLOHMANN_JSON_HPP -#define NLOHMANN_JSON_HPP - -#include -#include -#include -#include -#include -#include -#include -#include -#include -#include -#include -#include -#include -#include -#include -#include -#include -#include -#include -#include -#include -#include -#include -#include -#include - -// exclude unsupported compilers -#if defined(__clang__) - #define CLANG_VERSION (__clang_major__ * 10000 + __clang_minor__ * 100 + __clang_patchlevel__) - #if CLANG_VERSION < 30400 - #error "unsupported Clang version - see https://github.com/nlohmann/json#supported-compilers" - #endif -#elif defined(__GNUC__) - #define GCC_VERSION (__GNUC__ * 10000 + __GNUC_MINOR__ * 100 + __GNUC_PATCHLEVEL__) - #if GCC_VERSION < 40900 - #error "unsupported GCC version - see https://github.com/nlohmann/json#supported-compilers" - #endif -#endif - -// disable float-equal warnings on GCC/clang -#if defined(__clang__) || defined(__GNUC__) || defined(__GNUG__) - #pragma GCC diagnostic push - #pragma GCC diagnostic ignored "-Wfloat-equal" -#endif - -// allow for portable deprecation warnings -#if defined(__clang__) || defined(__GNUC__) || defined(__GNUG__) - #define JSON_DEPRECATED __attribute__((deprecated)) -#elif defined(_MSC_VER) - #define JSON_DEPRECATED __declspec(deprecated) -#else - #define JSON_DEPRECATED -#endif - -/*! -@brief namespace for Niels Lohmann -@see https://github.com/nlohmann -@since version 1.0.0 -*/ -namespace nlohmann -{ - - -/*! -@brief unnamed namespace with internal helper functions -@since version 1.0.0 -*/ -namespace -{ -/*! -@brief Helper to determine whether there's a key_type for T. - -Thus helper is used to tell associative containers apart from other containers -such as sequence containers. For instance, `std::map` passes the test as it -contains a `mapped_type`, whereas `std::vector` fails the test. - -@sa http://stackoverflow.com/a/7728728/266378 -@since version 1.0.0 -*/ -template -struct has_mapped_type -{ - private: - template static char test(typename C::mapped_type*); - template static char (&test(...))[2]; - public: - static constexpr bool value = sizeof(test(0)) == 1; -}; - -/*! -@brief helper class to create locales with decimal point - -This struct is used a default locale during the JSON serialization. JSON -requires the decimal point to be `.`, so this function overloads the -`do_decimal_point()` function to return `.`. This function is called by -float-to-string conversions to retrieve the decimal separator between integer -and fractional parts. - -@sa https://github.com/nlohmann/json/issues/51#issuecomment-86869315 -@since version 2.0.0 -*/ -struct DecimalSeparator : std::numpunct -{ - char do_decimal_point() const - { - return '.'; - } -}; - -} - -/*! -@brief a class to store JSON values - -@tparam ObjectType type for JSON objects (`std::map` by default; will be used -in @ref object_t) -@tparam ArrayType type for JSON arrays (`std::vector` by default; will be used -in @ref array_t) -@tparam StringType type for JSON strings and object keys (`std::string` by -default; will be used in @ref string_t) -@tparam BooleanType type for JSON booleans (`bool` by default; will be used -in @ref boolean_t) -@tparam NumberIntegerType type for JSON integer numbers (`int64_t` by -default; will be used in @ref number_integer_t) -@tparam NumberUnsignedType type for JSON unsigned integer numbers (@c -`uint64_t` by default; will be used in @ref number_unsigned_t) -@tparam NumberFloatType type for JSON floating-point numbers (`double` by -default; will be used in @ref number_float_t) -@tparam AllocatorType type of the allocator to use (`std::allocator` by -default) - -@requirement The class satisfies the following concept requirements: -- Basic - - [DefaultConstructible](http://en.cppreference.com/w/cpp/concept/DefaultConstructible): - JSON values can be default constructed. The result will be a JSON null value. - - [MoveConstructible](http://en.cppreference.com/w/cpp/concept/MoveConstructible): - A JSON value can be constructed from an rvalue argument. - - [CopyConstructible](http://en.cppreference.com/w/cpp/concept/CopyConstructible): - A JSON value can be copy-constructed from an lvalue expression. - - [MoveAssignable](http://en.cppreference.com/w/cpp/concept/MoveAssignable): - A JSON value van be assigned from an rvalue argument. - - [CopyAssignable](http://en.cppreference.com/w/cpp/concept/CopyAssignable): - A JSON value can be copy-assigned from an lvalue expression. - - [Destructible](http://en.cppreference.com/w/cpp/concept/Destructible): - JSON values can be destructed. -- Layout - - [StandardLayoutType](http://en.cppreference.com/w/cpp/concept/StandardLayoutType): - JSON values have - [standard layout](http://en.cppreference.com/w/cpp/language/data_members#Standard_layout): - All non-static data members are private and standard layout types, the class - has no virtual functions or (virtual) base classes. -- Library-wide - - [EqualityComparable](http://en.cppreference.com/w/cpp/concept/EqualityComparable): - JSON values can be compared with `==`, see @ref - operator==(const_reference,const_reference). - - [LessThanComparable](http://en.cppreference.com/w/cpp/concept/LessThanComparable): - JSON values can be compared with `<`, see @ref - operator<(const_reference,const_reference). - - [Swappable](http://en.cppreference.com/w/cpp/concept/Swappable): - Any JSON lvalue or rvalue of can be swapped with any lvalue or rvalue of - other compatible types, using unqualified function call @ref swap(). - - [NullablePointer](http://en.cppreference.com/w/cpp/concept/NullablePointer): - JSON values can be compared against `std::nullptr_t` objects which are used - to model the `null` value. -- Container - - [Container](http://en.cppreference.com/w/cpp/concept/Container): - JSON values can be used like STL containers and provide iterator access. - - [ReversibleContainer](http://en.cppreference.com/w/cpp/concept/ReversibleContainer); - JSON values can be used like STL containers and provide reverse iterator - access. - -@invariant The member variables @a m_value and @a m_type have the following -relationship: -- If `m_type == value_t::object`, then `m_value.object != nullptr`. -- If `m_type == value_t::array`, then `m_value.array != nullptr`. -- If `m_type == value_t::string`, then `m_value.string != nullptr`. -The invariants are checked by member function assert_invariant(). - -@internal -@note ObjectType trick from http://stackoverflow.com/a/9860911 -@endinternal - -@see [RFC 7159: The JavaScript Object Notation (JSON) Data Interchange -Format](http://rfc7159.net/rfc7159) - -@since version 1.0.0 - -@nosubgrouping -*/ -template < - template class ObjectType = std::map, - template class ArrayType = std::vector, - class StringType = std::string, - class BooleanType = bool, - class NumberIntegerType = std::int64_t, - class NumberUnsignedType = std::uint64_t, - class NumberFloatType = double, - template class AllocatorType = std::allocator - > -class basic_json -{ - private: - /// workaround type for MSVC - using basic_json_t = basic_json; - - public: - // forward declarations - template class json_reverse_iterator; - class json_pointer; - - ///////////////////// - // container types // - ///////////////////// - - /// @name container types - /// The canonic container types to use @ref basic_json like any other STL - /// container. - /// @{ - - /// the type of elements in a basic_json container - using value_type = basic_json; - - /// the type of an element reference - using reference = value_type&; - /// the type of an element const reference - using const_reference = const value_type&; - - /// a type to represent differences between iterators - using difference_type = std::ptrdiff_t; - /// a type to represent container sizes - using size_type = std::size_t; - - /// the allocator type - using allocator_type = AllocatorType; - - /// the type of an element pointer - using pointer = typename std::allocator_traits::pointer; - /// the type of an element const pointer - using const_pointer = typename std::allocator_traits::const_pointer; - - /// an iterator for a basic_json container - class iterator; - /// a const iterator for a basic_json container - class const_iterator; - /// a reverse iterator for a basic_json container - using reverse_iterator = json_reverse_iterator; - /// a const reverse iterator for a basic_json container - using const_reverse_iterator = json_reverse_iterator; - - /// @} - - - /*! - @brief returns the allocator associated with the container - */ - static allocator_type get_allocator() - { - return allocator_type(); - } - - - /////////////////////////// - // JSON value data types // - /////////////////////////// - - /// @name JSON value data types - /// The data types to store a JSON value. These types are derived from - /// the template arguments passed to class @ref basic_json. - /// @{ - - /*! - @brief a type for an object - - [RFC 7159](http://rfc7159.net/rfc7159) describes JSON objects as follows: - > An object is an unordered collection of zero or more name/value pairs, - > where a name is a string and a value is a string, number, boolean, null, - > object, or array. - - To store objects in C++, a type is defined by the template parameters - described below. - - @tparam ObjectType the container to store objects (e.g., `std::map` or - `std::unordered_map`) - @tparam StringType the type of the keys or names (e.g., `std::string`). - The comparison function `std::less` is used to order elements - inside the container. - @tparam AllocatorType the allocator to use for objects (e.g., - `std::allocator`) - - #### Default type - - With the default values for @a ObjectType (`std::map`), @a StringType - (`std::string`), and @a AllocatorType (`std::allocator`), the default - value for @a object_t is: - - @code {.cpp} - std::map< - std::string, // key_type - basic_json, // value_type - std::less, // key_compare - std::allocator> // allocator_type - > - @endcode - - #### Behavior - - The choice of @a object_t influences the behavior of the JSON class. With - the default type, objects have the following behavior: - - - When all names are unique, objects will be interoperable in the sense - that all software implementations receiving that object will agree on - the name-value mappings. - - When the names within an object are not unique, later stored name/value - pairs overwrite previously stored name/value pairs, leaving the used - names unique. For instance, `{"key": 1}` and `{"key": 2, "key": 1}` will - be treated as equal and both stored as `{"key": 1}`. - - Internally, name/value pairs are stored in lexicographical order of the - names. Objects will also be serialized (see @ref dump) in this order. - For instance, `{"b": 1, "a": 2}` and `{"a": 2, "b": 1}` will be stored - and serialized as `{"a": 2, "b": 1}`. - - When comparing objects, the order of the name/value pairs is irrelevant. - This makes objects interoperable in the sense that they will not be - affected by these differences. For instance, `{"b": 1, "a": 2}` and - `{"a": 2, "b": 1}` will be treated as equal. - - #### Limits - - [RFC 7159](http://rfc7159.net/rfc7159) specifies: - > An implementation may set limits on the maximum depth of nesting. - - In this class, the object's limit of nesting is not constraint explicitly. - However, a maximum depth of nesting may be introduced by the compiler or - runtime environment. A theoretical limit can be queried by calling the - @ref max_size function of a JSON object. - - #### Storage - - Objects are stored as pointers in a @ref basic_json type. That is, for any - access to object values, a pointer of type `object_t*` must be - dereferenced. - - @sa @ref array_t -- type for an array value - - @since version 1.0.0 - - @note The order name/value pairs are added to the object is *not* - preserved by the library. Therefore, iterating an object may return - name/value pairs in a different order than they were originally stored. In - fact, keys will be traversed in alphabetical order as `std::map` with - `std::less` is used by default. Please note this behavior conforms to [RFC - 7159](http://rfc7159.net/rfc7159), because any order implements the - specified "unordered" nature of JSON objects. - */ - using object_t = ObjectType, - AllocatorType>>; - - /*! - @brief a type for an array - - [RFC 7159](http://rfc7159.net/rfc7159) describes JSON arrays as follows: - > An array is an ordered sequence of zero or more values. - - To store objects in C++, a type is defined by the template parameters - explained below. - - @tparam ArrayType container type to store arrays (e.g., `std::vector` or - `std::list`) - @tparam AllocatorType allocator to use for arrays (e.g., `std::allocator`) - - #### Default type - - With the default values for @a ArrayType (`std::vector`) and @a - AllocatorType (`std::allocator`), the default value for @a array_t is: - - @code {.cpp} - std::vector< - basic_json, // value_type - std::allocator // allocator_type - > - @endcode - - #### Limits - - [RFC 7159](http://rfc7159.net/rfc7159) specifies: - > An implementation may set limits on the maximum depth of nesting. - - In this class, the array's limit of nesting is not constraint explicitly. - However, a maximum depth of nesting may be introduced by the compiler or - runtime environment. A theoretical limit can be queried by calling the - @ref max_size function of a JSON array. - - #### Storage - - Arrays are stored as pointers in a @ref basic_json type. That is, for any - access to array values, a pointer of type `array_t*` must be dereferenced. - - @sa @ref object_t -- type for an object value - - @since version 1.0.0 - */ - using array_t = ArrayType>; - - /*! - @brief a type for a string - - [RFC 7159](http://rfc7159.net/rfc7159) describes JSON strings as follows: - > A string is a sequence of zero or more Unicode characters. - - To store objects in C++, a type is defined by the template parameter - described below. Unicode values are split by the JSON class into - byte-sized characters during deserialization. - - @tparam StringType the container to store strings (e.g., `std::string`). - Note this container is used for keys/names in objects, see @ref object_t. - - #### Default type - - With the default values for @a StringType (`std::string`), the default - value for @a string_t is: - - @code {.cpp} - std::string - @endcode - - #### String comparison - - [RFC 7159](http://rfc7159.net/rfc7159) states: - > Software implementations are typically required to test names of object - > members for equality. Implementations that transform the textual - > representation into sequences of Unicode code units and then perform the - > comparison numerically, code unit by code unit, are interoperable in the - > sense that implementations will agree in all cases on equality or - > inequality of two strings. For example, implementations that compare - > strings with escaped characters unconverted may incorrectly find that - > `"a\\b"` and `"a\u005Cb"` are not equal. - - This implementation is interoperable as it does compare strings code unit - by code unit. - - #### Storage - - String values are stored as pointers in a @ref basic_json type. That is, - for any access to string values, a pointer of type `string_t*` must be - dereferenced. - - @since version 1.0.0 - */ - using string_t = StringType; - - /*! - @brief a type for a boolean - - [RFC 7159](http://rfc7159.net/rfc7159) implicitly describes a boolean as a - type which differentiates the two literals `true` and `false`. - - To store objects in C++, a type is defined by the template parameter @a - BooleanType which chooses the type to use. - - #### Default type - - With the default values for @a BooleanType (`bool`), the default value for - @a boolean_t is: - - @code {.cpp} - bool - @endcode - - #### Storage - - Boolean values are stored directly inside a @ref basic_json type. - - @since version 1.0.0 - */ - using boolean_t = BooleanType; - - /*! - @brief a type for a number (integer) - - [RFC 7159](http://rfc7159.net/rfc7159) describes numbers as follows: - > The representation of numbers is similar to that used in most - > programming languages. A number is represented in base 10 using decimal - > digits. It contains an integer component that may be prefixed with an - > optional minus sign, which may be followed by a fraction part and/or an - > exponent part. Leading zeros are not allowed. (...) Numeric values that - > cannot be represented in the grammar below (such as Infinity and NaN) - > are not permitted. - - This description includes both integer and floating-point numbers. - However, C++ allows more precise storage if it is known whether the number - is a signed integer, an unsigned integer or a floating-point number. - Therefore, three different types, @ref number_integer_t, @ref - number_unsigned_t and @ref number_float_t are used. - - To store integer numbers in C++, a type is defined by the template - parameter @a NumberIntegerType which chooses the type to use. - - #### Default type - - With the default values for @a NumberIntegerType (`int64_t`), the default - value for @a number_integer_t is: - - @code {.cpp} - int64_t - @endcode - - #### Default behavior - - - The restrictions about leading zeros is not enforced in C++. Instead, - leading zeros in integer literals lead to an interpretation as octal - number. Internally, the value will be stored as decimal number. For - instance, the C++ integer literal `010` will be serialized to `8`. - During deserialization, leading zeros yield an error. - - Not-a-number (NaN) values will be serialized to `null`. - - #### Limits - - [RFC 7159](http://rfc7159.net/rfc7159) specifies: - > An implementation may set limits on the range and precision of numbers. - - When the default type is used, the maximal integer number that can be - stored is `9223372036854775807` (INT64_MAX) and the minimal integer number - that can be stored is `-9223372036854775808` (INT64_MIN). Integer numbers - that are out of range will yield over/underflow when used in a - constructor. During deserialization, too large or small integer numbers - will be automatically be stored as @ref number_unsigned_t or @ref - number_float_t. - - [RFC 7159](http://rfc7159.net/rfc7159) further states: - > Note that when such software is used, numbers that are integers and are - > in the range \f$[-2^{53}+1, 2^{53}-1]\f$ are interoperable in the sense - > that implementations will agree exactly on their numeric values. - - As this range is a subrange of the exactly supported range [INT64_MIN, - INT64_MAX], this class's integer type is interoperable. - - #### Storage - - Integer number values are stored directly inside a @ref basic_json type. - - @sa @ref number_float_t -- type for number values (floating-point) - - @sa @ref number_unsigned_t -- type for number values (unsigned integer) - - @since version 1.0.0 - */ - using number_integer_t = NumberIntegerType; - - /*! - @brief a type for a number (unsigned) - - [RFC 7159](http://rfc7159.net/rfc7159) describes numbers as follows: - > The representation of numbers is similar to that used in most - > programming languages. A number is represented in base 10 using decimal - > digits. It contains an integer component that may be prefixed with an - > optional minus sign, which may be followed by a fraction part and/or an - > exponent part. Leading zeros are not allowed. (...) Numeric values that - > cannot be represented in the grammar below (such as Infinity and NaN) - > are not permitted. - - This description includes both integer and floating-point numbers. - However, C++ allows more precise storage if it is known whether the number - is a signed integer, an unsigned integer or a floating-point number. - Therefore, three different types, @ref number_integer_t, @ref - number_unsigned_t and @ref number_float_t are used. - - To store unsigned integer numbers in C++, a type is defined by the - template parameter @a NumberUnsignedType which chooses the type to use. - - #### Default type - - With the default values for @a NumberUnsignedType (`uint64_t`), the - default value for @a number_unsigned_t is: - - @code {.cpp} - uint64_t - @endcode - - #### Default behavior - - - The restrictions about leading zeros is not enforced in C++. Instead, - leading zeros in integer literals lead to an interpretation as octal - number. Internally, the value will be stored as decimal number. For - instance, the C++ integer literal `010` will be serialized to `8`. - During deserialization, leading zeros yield an error. - - Not-a-number (NaN) values will be serialized to `null`. - - #### Limits - - [RFC 7159](http://rfc7159.net/rfc7159) specifies: - > An implementation may set limits on the range and precision of numbers. - - When the default type is used, the maximal integer number that can be - stored is `18446744073709551615` (UINT64_MAX) and the minimal integer - number that can be stored is `0`. Integer numbers that are out of range - will yield over/underflow when used in a constructor. During - deserialization, too large or small integer numbers will be automatically - be stored as @ref number_integer_t or @ref number_float_t. - - [RFC 7159](http://rfc7159.net/rfc7159) further states: - > Note that when such software is used, numbers that are integers and are - > in the range \f$[-2^{53}+1, 2^{53}-1]\f$ are interoperable in the sense - > that implementations will agree exactly on their numeric values. - - As this range is a subrange (when considered in conjunction with the - number_integer_t type) of the exactly supported range [0, UINT64_MAX], - this class's integer type is interoperable. - - #### Storage - - Integer number values are stored directly inside a @ref basic_json type. - - @sa @ref number_float_t -- type for number values (floating-point) - @sa @ref number_integer_t -- type for number values (integer) - - @since version 2.0.0 - */ - using number_unsigned_t = NumberUnsignedType; - - /*! - @brief a type for a number (floating-point) - - [RFC 7159](http://rfc7159.net/rfc7159) describes numbers as follows: - > The representation of numbers is similar to that used in most - > programming languages. A number is represented in base 10 using decimal - > digits. It contains an integer component that may be prefixed with an - > optional minus sign, which may be followed by a fraction part and/or an - > exponent part. Leading zeros are not allowed. (...) Numeric values that - > cannot be represented in the grammar below (such as Infinity and NaN) - > are not permitted. - - This description includes both integer and floating-point numbers. - However, C++ allows more precise storage if it is known whether the number - is a signed integer, an unsigned integer or a floating-point number. - Therefore, three different types, @ref number_integer_t, @ref - number_unsigned_t and @ref number_float_t are used. - - To store floating-point numbers in C++, a type is defined by the template - parameter @a NumberFloatType which chooses the type to use. - - #### Default type - - With the default values for @a NumberFloatType (`double`), the default - value for @a number_float_t is: - - @code {.cpp} - double - @endcode - - #### Default behavior - - - The restrictions about leading zeros is not enforced in C++. Instead, - leading zeros in floating-point literals will be ignored. Internally, - the value will be stored as decimal number. For instance, the C++ - floating-point literal `01.2` will be serialized to `1.2`. During - deserialization, leading zeros yield an error. - - Not-a-number (NaN) values will be serialized to `null`. - - #### Limits - - [RFC 7159](http://rfc7159.net/rfc7159) states: - > This specification allows implementations to set limits on the range and - > precision of numbers accepted. Since software that implements IEEE - > 754-2008 binary64 (double precision) numbers is generally available and - > widely used, good interoperability can be achieved by implementations - > that expect no more precision or range than these provide, in the sense - > that implementations will approximate JSON numbers within the expected - > precision. - - This implementation does exactly follow this approach, as it uses double - precision floating-point numbers. Note values smaller than - `-1.79769313486232e+308` and values greater than `1.79769313486232e+308` - will be stored as NaN internally and be serialized to `null`. - - #### Storage - - Floating-point number values are stored directly inside a @ref basic_json - type. - - @sa @ref number_integer_t -- type for number values (integer) - - @sa @ref number_unsigned_t -- type for number values (unsigned integer) - - @since version 1.0.0 - */ - using number_float_t = NumberFloatType; - - /// @} - - - /////////////////////////// - // JSON type enumeration // - /////////////////////////// - - /*! - @brief the JSON type enumeration - - This enumeration collects the different JSON types. It is internally used - to distinguish the stored values, and the functions @ref is_null(), @ref - is_object(), @ref is_array(), @ref is_string(), @ref is_boolean(), @ref - is_number() (with @ref is_number_integer(), @ref is_number_unsigned(), and - @ref is_number_float()), @ref is_discarded(), @ref is_primitive(), and - @ref is_structured() rely on it. - - @note There are three enumeration entries (number_integer, - number_unsigned, and number_float), because the library distinguishes - these three types for numbers: @ref number_unsigned_t is used for unsigned - integers, @ref number_integer_t is used for signed integers, and @ref - number_float_t is used for floating-point numbers or to approximate - integers which do not fit in the limits of their respective type. - - @sa @ref basic_json(const value_t value_type) -- create a JSON value with - the default value for a given type - - @since version 1.0.0 - */ - enum class value_t : uint8_t - { - null, ///< null value - object, ///< object (unordered set of name/value pairs) - array, ///< array (ordered collection of values) - string, ///< string value - boolean, ///< boolean value - number_integer, ///< number value (signed integer) - number_unsigned, ///< number value (unsigned integer) - number_float, ///< number value (floating-point) - discarded ///< discarded by the the parser callback function - }; - - - private: - - /// helper for exception-safe object creation - template - static T* create(Args&& ... args) - { - AllocatorType alloc; - auto deleter = [&](T * object) - { - alloc.deallocate(object, 1); - }; - std::unique_ptr object(alloc.allocate(1), deleter); - alloc.construct(object.get(), std::forward(args)...); - assert(object.get() != nullptr); - return object.release(); - } - - //////////////////////// - // JSON value storage // - //////////////////////// - - /*! - @brief a JSON value - - The actual storage for a JSON value of the @ref basic_json class. This - union combines the different storage types for the JSON value types - defined in @ref value_t. - - JSON type | value_t type | used type - --------- | --------------- | ------------------------ - object | object | pointer to @ref object_t - array | array | pointer to @ref array_t - string | string | pointer to @ref string_t - boolean | boolean | @ref boolean_t - number | number_integer | @ref number_integer_t - number | number_unsigned | @ref number_unsigned_t - number | number_float | @ref number_float_t - null | null | *no value is stored* - - @note Variable-length types (objects, arrays, and strings) are stored as - pointers. The size of the union should not exceed 64 bits if the default - value types are used. - - @since version 1.0.0 - */ - union json_value - { - /// object (stored with pointer to save storage) - object_t* object; - /// array (stored with pointer to save storage) - array_t* array; - /// string (stored with pointer to save storage) - string_t* string; - /// boolean - boolean_t boolean; - /// number (integer) - number_integer_t number_integer; - /// number (unsigned integer) - number_unsigned_t number_unsigned; - /// number (floating-point) - number_float_t number_float; - - /// default constructor (for null values) - json_value() = default; - /// constructor for booleans - json_value(boolean_t v) noexcept : boolean(v) {} - /// constructor for numbers (integer) - json_value(number_integer_t v) noexcept : number_integer(v) {} - /// constructor for numbers (unsigned) - json_value(number_unsigned_t v) noexcept : number_unsigned(v) {} - /// constructor for numbers (floating-point) - json_value(number_float_t v) noexcept : number_float(v) {} - /// constructor for empty values of a given type - json_value(value_t t) - { - switch (t) - { - case value_t::object: - { - object = create(); - break; - } - - case value_t::array: - { - array = create(); - break; - } - - case value_t::string: - { - string = create(""); - break; - } - - case value_t::boolean: - { - boolean = boolean_t(false); - break; - } - - case value_t::number_integer: - { - number_integer = number_integer_t(0); - break; - } - - case value_t::number_unsigned: - { - number_unsigned = number_unsigned_t(0); - break; - } - - case value_t::number_float: - { - number_float = number_float_t(0.0); - break; - } - - default: - { - break; - } - } - } - - /// constructor for strings - json_value(const string_t& value) - { - string = create(value); - } - - /// constructor for objects - json_value(const object_t& value) - { - object = create(value); - } - - /// constructor for arrays - json_value(const array_t& value) - { - array = create(value); - } - }; - - /*! - @brief checks the class invariants - - This function asserts the class invariants. It needs to be called at the - end of every constructor to make sure that created objects respect the - invariant. Furthermore, it has to be called each time the type of a JSON - value is changed, because the invariant expresses a relationship between - @a m_type and @a m_value. - */ - void assert_invariant() const - { - assert(m_type != value_t::object or m_value.object != nullptr); - assert(m_type != value_t::array or m_value.array != nullptr); - assert(m_type != value_t::string or m_value.string != nullptr); - } - - public: - ////////////////////////// - // JSON parser callback // - ////////////////////////// - - /*! - @brief JSON callback events - - This enumeration lists the parser events that can trigger calling a - callback function of type @ref parser_callback_t during parsing. - - @image html callback_events.png "Example when certain parse events are triggered" - - @since version 1.0.0 - */ - enum class parse_event_t : uint8_t - { - /// the parser read `{` and started to process a JSON object - object_start, - /// the parser read `}` and finished processing a JSON object - object_end, - /// the parser read `[` and started to process a JSON array - array_start, - /// the parser read `]` and finished processing a JSON array - array_end, - /// the parser read a key of a value in an object - key, - /// the parser finished reading a JSON value - value - }; - - /*! - @brief per-element parser callback type - - With a parser callback function, the result of parsing a JSON text can be - influenced. When passed to @ref parse(std::istream&, const - parser_callback_t) or @ref parse(const char*, const parser_callback_t), - it is called on certain events (passed as @ref parse_event_t via parameter - @a event) with a set recursion depth @a depth and context JSON value - @a parsed. The return value of the callback function is a boolean - indicating whether the element that emitted the callback shall be kept or - not. - - We distinguish six scenarios (determined by the event type) in which the - callback function can be called. The following table describes the values - of the parameters @a depth, @a event, and @a parsed. - - parameter @a event | description | parameter @a depth | parameter @a parsed - ------------------ | ----------- | ------------------ | ------------------- - parse_event_t::object_start | the parser read `{` and started to process a JSON object | depth of the parent of the JSON object | a JSON value with type discarded - parse_event_t::key | the parser read a key of a value in an object | depth of the currently parsed JSON object | a JSON string containing the key - parse_event_t::object_end | the parser read `}` and finished processing a JSON object | depth of the parent of the JSON object | the parsed JSON object - parse_event_t::array_start | the parser read `[` and started to process a JSON array | depth of the parent of the JSON array | a JSON value with type discarded - parse_event_t::array_end | the parser read `]` and finished processing a JSON array | depth of the parent of the JSON array | the parsed JSON array - parse_event_t::value | the parser finished reading a JSON value | depth of the value | the parsed JSON value - - @image html callback_events.png "Example when certain parse events are triggered" - - Discarding a value (i.e., returning `false`) has different effects - depending on the context in which function was called: - - - Discarded values in structured types are skipped. That is, the parser - will behave as if the discarded value was never read. - - In case a value outside a structured type is skipped, it is replaced - with `null`. This case happens if the top-level element is skipped. - - @param[in] depth the depth of the recursion during parsing - - @param[in] event an event of type parse_event_t indicating the context in - the callback function has been called - - @param[in,out] parsed the current intermediate parse result; note that - writing to this value has no effect for parse_event_t::key events - - @return Whether the JSON value which called the function during parsing - should be kept (`true`) or not (`false`). In the latter case, it is either - skipped completely or replaced by an empty discarded object. - - @sa @ref parse(std::istream&, parser_callback_t) or - @ref parse(const char*, parser_callback_t) for examples - - @since version 1.0.0 - */ - using parser_callback_t = std::function; - - - ////////////////// - // constructors // - ////////////////// - - /// @name constructors and destructors - /// Constructors of class @ref basic_json, copy/move constructor, copy - /// assignment, static functions creating objects, and the destructor. - /// @{ - - /*! - @brief create an empty value with a given type - - Create an empty JSON value with a given type. The value will be default - initialized with an empty value which depends on the type: - - Value type | initial value - ----------- | ------------- - null | `null` - boolean | `false` - string | `""` - number | `0` - object | `{}` - array | `[]` - - @param[in] value_type the type of the value to create - - @complexity Constant. - - @throw std::bad_alloc if allocation for object, array, or string value - fails - - @liveexample{The following code shows the constructor for different @ref - value_t values,basic_json__value_t} - - @sa @ref basic_json(std::nullptr_t) -- create a `null` value - @sa @ref basic_json(boolean_t value) -- create a boolean value - @sa @ref basic_json(const string_t&) -- create a string value - @sa @ref basic_json(const object_t&) -- create a object value - @sa @ref basic_json(const array_t&) -- create a array value - @sa @ref basic_json(const number_float_t) -- create a number - (floating-point) value - @sa @ref basic_json(const number_integer_t) -- create a number (integer) - value - @sa @ref basic_json(const number_unsigned_t) -- create a number (unsigned) - value - - @since version 1.0.0 - */ - basic_json(const value_t value_type) - : m_type(value_type), m_value(value_type) - { - assert_invariant(); - } - - /*! - @brief create a null object - - Create a `null` JSON value. It either takes a null pointer as parameter - (explicitly creating `null`) or no parameter (implicitly creating `null`). - The passed null pointer itself is not read -- it is only used to choose - the right constructor. - - @complexity Constant. - - @exceptionsafety No-throw guarantee: this constructor never throws - exceptions. - - @liveexample{The following code shows the constructor with and without a - null pointer parameter.,basic_json__nullptr_t} - - @since version 1.0.0 - */ - basic_json(std::nullptr_t = nullptr) noexcept - : basic_json(value_t::null) - { - assert_invariant(); - } - - /*! - @brief create an object (explicit) - - Create an object JSON value with a given content. - - @param[in] val a value for the object - - @complexity Linear in the size of the passed @a val. - - @throw std::bad_alloc if allocation for object value fails - - @liveexample{The following code shows the constructor with an @ref - object_t parameter.,basic_json__object_t} - - @sa @ref basic_json(const CompatibleObjectType&) -- create an object value - from a compatible STL container - - @since version 1.0.0 - */ - basic_json(const object_t& val) - : m_type(value_t::object), m_value(val) - { - assert_invariant(); - } - - /*! - @brief create an object (implicit) - - Create an object JSON value with a given content. This constructor allows - any type @a CompatibleObjectType that can be used to construct values of - type @ref object_t. - - @tparam CompatibleObjectType An object type whose `key_type` and - `value_type` is compatible to @ref object_t. Examples include `std::map`, - `std::unordered_map`, `std::multimap`, and `std::unordered_multimap` with - a `key_type` of `std::string`, and a `value_type` from which a @ref - basic_json value can be constructed. - - @param[in] val a value for the object - - @complexity Linear in the size of the passed @a val. - - @throw std::bad_alloc if allocation for object value fails - - @liveexample{The following code shows the constructor with several - compatible object type parameters.,basic_json__CompatibleObjectType} - - @sa @ref basic_json(const object_t&) -- create an object value - - @since version 1.0.0 - */ - template::value and - std::is_constructible::value, int>::type = 0> - basic_json(const CompatibleObjectType& val) - : m_type(value_t::object) - { - using std::begin; - using std::end; - m_value.object = create(begin(val), end(val)); - assert_invariant(); - } - - /*! - @brief create an array (explicit) - - Create an array JSON value with a given content. - - @param[in] val a value for the array - - @complexity Linear in the size of the passed @a val. - - @throw std::bad_alloc if allocation for array value fails - - @liveexample{The following code shows the constructor with an @ref array_t - parameter.,basic_json__array_t} - - @sa @ref basic_json(const CompatibleArrayType&) -- create an array value - from a compatible STL containers - - @since version 1.0.0 - */ - basic_json(const array_t& val) - : m_type(value_t::array), m_value(val) - { - assert_invariant(); - } - - /*! - @brief create an array (implicit) - - Create an array JSON value with a given content. This constructor allows - any type @a CompatibleArrayType that can be used to construct values of - type @ref array_t. - - @tparam CompatibleArrayType An object type whose `value_type` is - compatible to @ref array_t. Examples include `std::vector`, `std::deque`, - `std::list`, `std::forward_list`, `std::array`, `std::set`, - `std::unordered_set`, `std::multiset`, and `unordered_multiset` with a - `value_type` from which a @ref basic_json value can be constructed. - - @param[in] val a value for the array - - @complexity Linear in the size of the passed @a val. - - @throw std::bad_alloc if allocation for array value fails - - @liveexample{The following code shows the constructor with several - compatible array type parameters.,basic_json__CompatibleArrayType} - - @sa @ref basic_json(const array_t&) -- create an array value - - @since version 1.0.0 - */ - template::value and - not std::is_same::value and - not std::is_same::value and - not std::is_same::value and - not std::is_same::value and - not std::is_same::value and - std::is_constructible::value, int>::type = 0> - basic_json(const CompatibleArrayType& val) - : m_type(value_t::array) - { - using std::begin; - using std::end; - m_value.array = create(begin(val), end(val)); - assert_invariant(); - } - - /*! - @brief create a string (explicit) - - Create an string JSON value with a given content. - - @param[in] val a value for the string - - @complexity Linear in the size of the passed @a val. - - @throw std::bad_alloc if allocation for string value fails - - @liveexample{The following code shows the constructor with an @ref - string_t parameter.,basic_json__string_t} - - @sa @ref basic_json(const typename string_t::value_type*) -- create a - string value from a character pointer - @sa @ref basic_json(const CompatibleStringType&) -- create a string value - from a compatible string container - - @since version 1.0.0 - */ - basic_json(const string_t& val) - : m_type(value_t::string), m_value(val) - { - assert_invariant(); - } - - /*! - @brief create a string (explicit) - - Create a string JSON value with a given content. - - @param[in] val a literal value for the string - - @complexity Linear in the size of the passed @a val. - - @throw std::bad_alloc if allocation for string value fails - - @liveexample{The following code shows the constructor with string literal - parameter.,basic_json__string_t_value_type} - - @sa @ref basic_json(const string_t&) -- create a string value - @sa @ref basic_json(const CompatibleStringType&) -- create a string value - from a compatible string container - - @since version 1.0.0 - */ - basic_json(const typename string_t::value_type* val) - : basic_json(string_t(val)) - { - assert_invariant(); - } - - /*! - @brief create a string (implicit) - - Create a string JSON value with a given content. - - @param[in] val a value for the string - - @tparam CompatibleStringType an string type which is compatible to @ref - string_t, for instance `std::string`. - - @complexity Linear in the size of the passed @a val. - - @throw std::bad_alloc if allocation for string value fails - - @liveexample{The following code shows the construction of a string value - from a compatible type.,basic_json__CompatibleStringType} - - @sa @ref basic_json(const string_t&) -- create a string value - @sa @ref basic_json(const typename string_t::value_type*) -- create a - string value from a character pointer - - @since version 1.0.0 - */ - template::value, int>::type = 0> - basic_json(const CompatibleStringType& val) - : basic_json(string_t(val)) - { - assert_invariant(); - } - - /*! - @brief create a boolean (explicit) - - Creates a JSON boolean type from a given value. - - @param[in] val a boolean value to store - - @complexity Constant. - - @liveexample{The example below demonstrates boolean - values.,basic_json__boolean_t} - - @since version 1.0.0 - */ - basic_json(boolean_t val) noexcept - : m_type(value_t::boolean), m_value(val) - { - assert_invariant(); - } - - /*! - @brief create an integer number (explicit) - - Create an integer number JSON value with a given content. - - @tparam T A helper type to remove this function via SFINAE in case @ref - number_integer_t is the same as `int`. In this case, this constructor - would have the same signature as @ref basic_json(const int value). Note - the helper type @a T is not visible in this constructor's interface. - - @param[in] val an integer to create a JSON number from - - @complexity Constant. - - @liveexample{The example below shows the construction of an integer - number value.,basic_json__number_integer_t} - - @sa @ref basic_json(const int) -- create a number value (integer) - @sa @ref basic_json(const CompatibleNumberIntegerType) -- create a number - value (integer) from a compatible number type - - @since version 1.0.0 - */ - template::value) and - std::is_same::value, int>::type = 0> - basic_json(const number_integer_t val) noexcept - : m_type(value_t::number_integer), m_value(val) - { - assert_invariant(); - } - - /*! - @brief create an integer number from an enum type (explicit) - - Create an integer number JSON value with a given content. - - @param[in] val an integer to create a JSON number from - - @note This constructor allows to pass enums directly to a constructor. As - C++ has no way of specifying the type of an anonymous enum explicitly, we - can only rely on the fact that such values implicitly convert to int. As - int may already be the same type of number_integer_t, we may need to - switch off the constructor @ref basic_json(const number_integer_t). - - @complexity Constant. - - @liveexample{The example below shows the construction of an integer - number value from an anonymous enum.,basic_json__const_int} - - @sa @ref basic_json(const number_integer_t) -- create a number value - (integer) - @sa @ref basic_json(const CompatibleNumberIntegerType) -- create a number - value (integer) from a compatible number type - - @since version 1.0.0 - */ - basic_json(const int val) noexcept - : m_type(value_t::number_integer), - m_value(static_cast(val)) - { - assert_invariant(); - } - - /*! - @brief create an integer number (implicit) - - Create an integer number JSON value with a given content. This constructor - allows any type @a CompatibleNumberIntegerType that can be used to - construct values of type @ref number_integer_t. - - @tparam CompatibleNumberIntegerType An integer type which is compatible to - @ref number_integer_t. Examples include the types `int`, `int32_t`, - `long`, and `short`. - - @param[in] val an integer to create a JSON number from - - @complexity Constant. - - @liveexample{The example below shows the construction of several integer - number values from compatible - types.,basic_json__CompatibleIntegerNumberType} - - @sa @ref basic_json(const number_integer_t) -- create a number value - (integer) - @sa @ref basic_json(const int) -- create a number value (integer) - - @since version 1.0.0 - */ - template::value and - std::numeric_limits::is_integer and - std::numeric_limits::is_signed, - CompatibleNumberIntegerType>::type = 0> - basic_json(const CompatibleNumberIntegerType val) noexcept - : m_type(value_t::number_integer), - m_value(static_cast(val)) - { - assert_invariant(); - } - - /*! - @brief create an unsigned integer number (explicit) - - Create an unsigned integer number JSON value with a given content. - - @tparam T helper type to compare number_unsigned_t and unsigned int (not - visible in) the interface. - - @param[in] val an integer to create a JSON number from - - @complexity Constant. - - @sa @ref basic_json(const CompatibleNumberUnsignedType) -- create a number - value (unsigned integer) from a compatible number type - - @since version 2.0.0 - */ - template::value) and - std::is_same::value, int>::type = 0> - basic_json(const number_unsigned_t val) noexcept - : m_type(value_t::number_unsigned), m_value(val) - { - assert_invariant(); - } - - /*! - @brief create an unsigned number (implicit) - - Create an unsigned number JSON value with a given content. This - constructor allows any type @a CompatibleNumberUnsignedType that can be - used to construct values of type @ref number_unsigned_t. - - @tparam CompatibleNumberUnsignedType An integer type which is compatible - to @ref number_unsigned_t. Examples may include the types `unsigned int`, - `uint32_t`, or `unsigned short`. - - @param[in] val an unsigned integer to create a JSON number from - - @complexity Constant. - - @sa @ref basic_json(const number_unsigned_t) -- create a number value - (unsigned) - - @since version 2.0.0 - */ - template::value and - std::numeric_limits::is_integer and - not std::numeric_limits::is_signed, - CompatibleNumberUnsignedType>::type = 0> - basic_json(const CompatibleNumberUnsignedType val) noexcept - : m_type(value_t::number_unsigned), - m_value(static_cast(val)) - { - assert_invariant(); - } - - /*! - @brief create a floating-point number (explicit) - - Create a floating-point number JSON value with a given content. - - @param[in] val a floating-point value to create a JSON number from - - @note [RFC 7159](http://www.rfc-editor.org/rfc/rfc7159.txt), section 6 - disallows NaN values: - > Numeric values that cannot be represented in the grammar below (such as - > Infinity and NaN) are not permitted. - In case the parameter @a val is not a number, a JSON null value is created - instead. - - @complexity Constant. - - @liveexample{The following example creates several floating-point - values.,basic_json__number_float_t} - - @sa @ref basic_json(const CompatibleNumberFloatType) -- create a number - value (floating-point) from a compatible number type - - @since version 1.0.0 - */ - basic_json(const number_float_t val) noexcept - : m_type(value_t::number_float), m_value(val) - { - // replace infinity and NAN by null - if (not std::isfinite(val)) - { - m_type = value_t::null; - m_value = json_value(); - } - - assert_invariant(); - } - - /*! - @brief create an floating-point number (implicit) - - Create an floating-point number JSON value with a given content. This - constructor allows any type @a CompatibleNumberFloatType that can be used - to construct values of type @ref number_float_t. - - @tparam CompatibleNumberFloatType A floating-point type which is - compatible to @ref number_float_t. Examples may include the types `float` - or `double`. - - @param[in] val a floating-point to create a JSON number from - - @note [RFC 7159](http://www.rfc-editor.org/rfc/rfc7159.txt), section 6 - disallows NaN values: - > Numeric values that cannot be represented in the grammar below (such as - > Infinity and NaN) are not permitted. - In case the parameter @a val is not a number, a JSON null value is - created instead. - - @complexity Constant. - - @liveexample{The example below shows the construction of several - floating-point number values from compatible - types.,basic_json__CompatibleNumberFloatType} - - @sa @ref basic_json(const number_float_t) -- create a number value - (floating-point) - - @since version 1.0.0 - */ - template::value and - std::is_floating_point::value>::type> - basic_json(const CompatibleNumberFloatType val) noexcept - : basic_json(number_float_t(val)) - { - assert_invariant(); - } - - /*! - @brief create a container (array or object) from an initializer list - - Creates a JSON value of type array or object from the passed initializer - list @a init. In case @a type_deduction is `true` (default), the type of - the JSON value to be created is deducted from the initializer list @a init - according to the following rules: - - 1. If the list is empty, an empty JSON object value `{}` is created. - 2. If the list consists of pairs whose first element is a string, a JSON - object value is created where the first elements of the pairs are - treated as keys and the second elements are as values. - 3. In all other cases, an array is created. - - The rules aim to create the best fit between a C++ initializer list and - JSON values. The rationale is as follows: - - 1. The empty initializer list is written as `{}` which is exactly an empty - JSON object. - 2. C++ has now way of describing mapped types other than to list a list of - pairs. As JSON requires that keys must be of type string, rule 2 is the - weakest constraint one can pose on initializer lists to interpret them - as an object. - 3. In all other cases, the initializer list could not be interpreted as - JSON object type, so interpreting it as JSON array type is safe. - - With the rules described above, the following JSON values cannot be - expressed by an initializer list: - - - the empty array (`[]`): use @ref array(std::initializer_list) - with an empty initializer list in this case - - arrays whose elements satisfy rule 2: use @ref - array(std::initializer_list) with the same initializer list - in this case - - @note When used without parentheses around an empty initializer list, @ref - basic_json() is called instead of this function, yielding the JSON null - value. - - @param[in] init initializer list with JSON values - - @param[in] type_deduction internal parameter; when set to `true`, the type - of the JSON value is deducted from the initializer list @a init; when set - to `false`, the type provided via @a manual_type is forced. This mode is - used by the functions @ref array(std::initializer_list) and - @ref object(std::initializer_list). - - @param[in] manual_type internal parameter; when @a type_deduction is set - to `false`, the created JSON value will use the provided type (only @ref - value_t::array and @ref value_t::object are valid); when @a type_deduction - is set to `true`, this parameter has no effect - - @throw std::domain_error if @a type_deduction is `false`, @a manual_type - is `value_t::object`, but @a init contains an element which is not a pair - whose first element is a string; example: `"cannot create object from - initializer list"` - - @complexity Linear in the size of the initializer list @a init. - - @liveexample{The example below shows how JSON values are created from - initializer lists.,basic_json__list_init_t} - - @sa @ref array(std::initializer_list) -- create a JSON array - value from an initializer list - @sa @ref object(std::initializer_list) -- create a JSON object - value from an initializer list - - @since version 1.0.0 - */ - basic_json(std::initializer_list init, - bool type_deduction = true, - value_t manual_type = value_t::array) - { - // check if each element is an array with two elements whose first - // element is a string - bool is_an_object = std::all_of(init.begin(), init.end(), - [](const basic_json & element) - { - return element.is_array() and element.size() == 2 and element[0].is_string(); - }); - - // adjust type if type deduction is not wanted - if (not type_deduction) - { - // if array is wanted, do not create an object though possible - if (manual_type == value_t::array) - { - is_an_object = false; - } - - // if object is wanted but impossible, throw an exception - if (manual_type == value_t::object and not is_an_object) - { - throw std::domain_error("cannot create object from initializer list"); - } - } - - if (is_an_object) - { - // the initializer list is a list of pairs -> create object - m_type = value_t::object; - m_value = value_t::object; - - std::for_each(init.begin(), init.end(), [this](const basic_json & element) - { - m_value.object->emplace(*(element[0].m_value.string), element[1]); - }); - } - else - { - // the initializer list describes an array -> create array - m_type = value_t::array; - m_value.array = create(init); - } - - assert_invariant(); - } - - /*! - @brief explicitly create an array from an initializer list - - Creates a JSON array value from a given initializer list. That is, given a - list of values `a, b, c`, creates the JSON value `[a, b, c]`. If the - initializer list is empty, the empty array `[]` is created. - - @note This function is only needed to express two edge cases that cannot - be realized with the initializer list constructor (@ref - basic_json(std::initializer_list, bool, value_t)). These cases - are: - 1. creating an array whose elements are all pairs whose first element is a - string -- in this case, the initializer list constructor would create an - object, taking the first elements as keys - 2. creating an empty array -- passing the empty initializer list to the - initializer list constructor yields an empty object - - @param[in] init initializer list with JSON values to create an array from - (optional) - - @return JSON array value - - @complexity Linear in the size of @a init. - - @liveexample{The following code shows an example for the `array` - function.,array} - - @sa @ref basic_json(std::initializer_list, bool, value_t) -- - create a JSON value from an initializer list - @sa @ref object(std::initializer_list) -- create a JSON object - value from an initializer list - - @since version 1.0.0 - */ - static basic_json array(std::initializer_list init = - std::initializer_list()) - { - return basic_json(init, false, value_t::array); - } - - /*! - @brief explicitly create an object from an initializer list - - Creates a JSON object value from a given initializer list. The initializer - lists elements must be pairs, and their first elements must be strings. If - the initializer list is empty, the empty object `{}` is created. - - @note This function is only added for symmetry reasons. In contrast to the - related function @ref array(std::initializer_list), there are - no cases which can only be expressed by this function. That is, any - initializer list @a init can also be passed to the initializer list - constructor @ref basic_json(std::initializer_list, bool, - value_t). - - @param[in] init initializer list to create an object from (optional) - - @return JSON object value - - @throw std::domain_error if @a init is not a pair whose first elements are - strings; thrown by - @ref basic_json(std::initializer_list, bool, value_t) - - @complexity Linear in the size of @a init. - - @liveexample{The following code shows an example for the `object` - function.,object} - - @sa @ref basic_json(std::initializer_list, bool, value_t) -- - create a JSON value from an initializer list - @sa @ref array(std::initializer_list) -- create a JSON array - value from an initializer list - - @since version 1.0.0 - */ - static basic_json object(std::initializer_list init = - std::initializer_list()) - { - return basic_json(init, false, value_t::object); - } - - /*! - @brief construct an array with count copies of given value - - Constructs a JSON array value by creating @a cnt copies of a passed value. - In case @a cnt is `0`, an empty array is created. As postcondition, - `std::distance(begin(),end()) == cnt` holds. - - @param[in] cnt the number of JSON copies of @a val to create - @param[in] val the JSON value to copy - - @complexity Linear in @a cnt. - - @liveexample{The following code shows examples for the @ref - basic_json(size_type\, const basic_json&) - constructor.,basic_json__size_type_basic_json} - - @since version 1.0.0 - */ - basic_json(size_type cnt, const basic_json& val) - : m_type(value_t::array) - { - m_value.array = create(cnt, val); - assert_invariant(); - } - - /*! - @brief construct a JSON container given an iterator range - - Constructs the JSON value with the contents of the range `[first, last)`. - The semantics depends on the different types a JSON value can have: - - In case of primitive types (number, boolean, or string), @a first must - be `begin()` and @a last must be `end()`. In this case, the value is - copied. Otherwise, std::out_of_range is thrown. - - In case of structured types (array, object), the constructor behaves as - similar versions for `std::vector`. - - In case of a null type, std::domain_error is thrown. - - @tparam InputIT an input iterator type (@ref iterator or @ref - const_iterator) - - @param[in] first begin of the range to copy from (included) - @param[in] last end of the range to copy from (excluded) - - @pre Iterators @a first and @a last must be initialized. **This - precondition is enforced with an assertion.** - - @throw std::domain_error if iterators are not compatible; that is, do not - belong to the same JSON value; example: `"iterators are not compatible"` - @throw std::out_of_range if iterators are for a primitive type (number, - boolean, or string) where an out of range error can be detected easily; - example: `"iterators out of range"` - @throw std::bad_alloc if allocation for object, array, or string fails - @throw std::domain_error if called with a null value; example: `"cannot - use construct with iterators from null"` - - @complexity Linear in distance between @a first and @a last. - - @liveexample{The example below shows several ways to create JSON values by - specifying a subrange with iterators.,basic_json__InputIt_InputIt} - - @since version 1.0.0 - */ - template::value or - std::is_same::value, int>::type = 0> - basic_json(InputIT first, InputIT last) - { - assert(first.m_object != nullptr); - assert(last.m_object != nullptr); - - // make sure iterator fits the current value - if (first.m_object != last.m_object) - { - throw std::domain_error("iterators are not compatible"); - } - - // copy type from first iterator - m_type = first.m_object->m_type; - - // check if iterator range is complete for primitive values - switch (m_type) - { - case value_t::boolean: - case value_t::number_float: - case value_t::number_integer: - case value_t::number_unsigned: - case value_t::string: - { - if (not first.m_it.primitive_iterator.is_begin() or not last.m_it.primitive_iterator.is_end()) - { - throw std::out_of_range("iterators out of range"); - } - break; - } - - default: - { - break; - } - } - - switch (m_type) - { - case value_t::number_integer: - { - m_value.number_integer = first.m_object->m_value.number_integer; - break; - } - - case value_t::number_unsigned: - { - m_value.number_unsigned = first.m_object->m_value.number_unsigned; - break; - } - - case value_t::number_float: - { - m_value.number_float = first.m_object->m_value.number_float; - break; - } - - case value_t::boolean: - { - m_value.boolean = first.m_object->m_value.boolean; - break; - } - - case value_t::string: - { - m_value = *first.m_object->m_value.string; - break; - } - - case value_t::object: - { - m_value.object = create(first.m_it.object_iterator, last.m_it.object_iterator); - break; - } - - case value_t::array: - { - m_value.array = create(first.m_it.array_iterator, last.m_it.array_iterator); - break; - } - - default: - { - throw std::domain_error("cannot use construct with iterators from " + first.m_object->type_name()); - } - } - - assert_invariant(); - } - - /*! - @brief construct a JSON value given an input stream - - @param[in,out] i stream to read a serialized JSON value from - @param[in] cb a parser callback function of type @ref parser_callback_t - which is used to control the deserialization by filtering unwanted values - (optional) - - @complexity Linear in the length of the input. The parser is a predictive - LL(1) parser. The complexity can be higher if the parser callback function - @a cb has a super-linear complexity. - - @note A UTF-8 byte order mark is silently ignored. - - @deprecated This constructor is deprecated and will be removed in version - 3.0.0 to unify the interface of the library. Deserialization will be - done by stream operators or by calling one of the `parse` functions, - e.g. @ref parse(std::istream&, const parser_callback_t). That is, calls - like `json j(i);` for an input stream @a i need to be replaced by - `json j = json::parse(i);`. See the example below. - - @liveexample{The example below demonstrates constructing a JSON value from - a `std::stringstream` with and without callback - function.,basic_json__istream} - - @since version 2.0.0, deprecated in version 2.0.3, to be removed in - version 3.0.0 - */ - JSON_DEPRECATED - explicit basic_json(std::istream& i, const parser_callback_t cb = nullptr) - { - *this = parser(i, cb).parse(); - assert_invariant(); - } - - /////////////////////////////////////// - // other constructors and destructor // - /////////////////////////////////////// - - /*! - @brief copy constructor - - Creates a copy of a given JSON value. - - @param[in] other the JSON value to copy - - @complexity Linear in the size of @a other. - - @requirement This function helps `basic_json` satisfying the - [Container](http://en.cppreference.com/w/cpp/concept/Container) - requirements: - - The complexity is linear. - - As postcondition, it holds: `other == basic_json(other)`. - - @throw std::bad_alloc if allocation for object, array, or string fails. - - @liveexample{The following code shows an example for the copy - constructor.,basic_json__basic_json} - - @since version 1.0.0 - */ - basic_json(const basic_json& other) - : m_type(other.m_type) - { - // check of passed value is valid - other.assert_invariant(); - - switch (m_type) - { - case value_t::object: - { - m_value = *other.m_value.object; - break; - } - - case value_t::array: - { - m_value = *other.m_value.array; - break; - } - - case value_t::string: - { - m_value = *other.m_value.string; - break; - } - - case value_t::boolean: - { - m_value = other.m_value.boolean; - break; - } - - case value_t::number_integer: - { - m_value = other.m_value.number_integer; - break; - } - - case value_t::number_unsigned: - { - m_value = other.m_value.number_unsigned; - break; - } - - case value_t::number_float: - { - m_value = other.m_value.number_float; - break; - } - - default: - { - break; - } - } - - assert_invariant(); - } - - /*! - @brief move constructor - - Move constructor. Constructs a JSON value with the contents of the given - value @a other using move semantics. It "steals" the resources from @a - other and leaves it as JSON null value. - - @param[in,out] other value to move to this object - - @post @a other is a JSON null value - - @complexity Constant. - - @liveexample{The code below shows the move constructor explicitly called - via std::move.,basic_json__moveconstructor} - - @since version 1.0.0 - */ - basic_json(basic_json&& other) noexcept - : m_type(std::move(other.m_type)), - m_value(std::move(other.m_value)) - { - // check that passed value is valid - other.assert_invariant(); - - // invalidate payload - other.m_type = value_t::null; - other.m_value = {}; - - assert_invariant(); - } - - /*! - @brief copy assignment - - Copy assignment operator. Copies a JSON value via the "copy and swap" - strategy: It is expressed in terms of the copy constructor, destructor, - and the swap() member function. - - @param[in] other value to copy from - - @complexity Linear. - - @requirement This function helps `basic_json` satisfying the - [Container](http://en.cppreference.com/w/cpp/concept/Container) - requirements: - - The complexity is linear. - - @liveexample{The code below shows and example for the copy assignment. It - creates a copy of value `a` which is then swapped with `b`. Finally\, the - copy of `a` (which is the null value after the swap) is - destroyed.,basic_json__copyassignment} - - @since version 1.0.0 - */ - reference& operator=(basic_json other) noexcept ( - std::is_nothrow_move_constructible::value and - std::is_nothrow_move_assignable::value and - std::is_nothrow_move_constructible::value and - std::is_nothrow_move_assignable::value - ) - { - // check that passed value is valid - other.assert_invariant(); - - using std::swap; - swap(m_type, other.m_type); - swap(m_value, other.m_value); - - assert_invariant(); - return *this; - } - - /*! - @brief destructor - - Destroys the JSON value and frees all allocated memory. - - @complexity Linear. - - @requirement This function helps `basic_json` satisfying the - [Container](http://en.cppreference.com/w/cpp/concept/Container) - requirements: - - The complexity is linear. - - All stored elements are destroyed and all memory is freed. - - @since version 1.0.0 - */ - ~basic_json() - { - assert_invariant(); - - switch (m_type) - { - case value_t::object: - { - AllocatorType alloc; - alloc.destroy(m_value.object); - alloc.deallocate(m_value.object, 1); - break; - } - - case value_t::array: - { - AllocatorType alloc; - alloc.destroy(m_value.array); - alloc.deallocate(m_value.array, 1); - break; - } - - case value_t::string: - { - AllocatorType alloc; - alloc.destroy(m_value.string); - alloc.deallocate(m_value.string, 1); - break; - } - - default: - { - // all other types need no specific destructor - break; - } - } - } - - /// @} - - public: - /////////////////////// - // object inspection // - /////////////////////// - - /// @name object inspection - /// Functions to inspect the type of a JSON value. - /// @{ - - /*! - @brief serialization - - Serialization function for JSON values. The function tries to mimic - Python's `json.dumps()` function, and currently supports its @a indent - parameter. - - @param[in] indent If indent is nonnegative, then array elements and object - members will be pretty-printed with that indent level. An indent level of - `0` will only insert newlines. `-1` (the default) selects the most compact - representation. - - @return string containing the serialization of the JSON value - - @complexity Linear. - - @liveexample{The following example shows the effect of different @a indent - parameters to the result of the serialization.,dump} - - @see https://docs.python.org/2/library/json.html#json.dump - - @since version 1.0.0 - */ - string_t dump(const int indent = -1) const - { - std::stringstream ss; - // fix locale problems - const static std::locale loc(std::locale(), new DecimalSeparator); - ss.imbue(loc); - - // 6, 15 or 16 digits of precision allows round-trip IEEE 754 - // string->float->string, string->double->string or string->long - // double->string; to be safe, we read this value from - // std::numeric_limits::digits10 - ss.precision(std::numeric_limits::digits10); - - if (indent >= 0) - { - dump(ss, true, static_cast(indent)); - } - else - { - dump(ss, false, 0); - } - - return ss.str(); - } - - /*! - @brief return the type of the JSON value (explicit) - - Return the type of the JSON value as a value from the @ref value_t - enumeration. - - @return the type of the JSON value - - @complexity Constant. - - @exceptionsafety No-throw guarantee: this member function never throws - exceptions. - - @liveexample{The following code exemplifies `type()` for all JSON - types.,type} - - @since version 1.0.0 - */ - constexpr value_t type() const noexcept - { - return m_type; - } - - /*! - @brief return whether type is primitive - - This function returns true iff the JSON type is primitive (string, number, - boolean, or null). - - @return `true` if type is primitive (string, number, boolean, or null), - `false` otherwise. - - @complexity Constant. - - @exceptionsafety No-throw guarantee: this member function never throws - exceptions. - - @liveexample{The following code exemplifies `is_primitive()` for all JSON - types.,is_primitive} - - @sa @ref is_structured() -- returns whether JSON value is structured - @sa @ref is_null() -- returns whether JSON value is `null` - @sa @ref is_string() -- returns whether JSON value is a string - @sa @ref is_boolean() -- returns whether JSON value is a boolean - @sa @ref is_number() -- returns whether JSON value is a number - - @since version 1.0.0 - */ - constexpr bool is_primitive() const noexcept - { - return is_null() or is_string() or is_boolean() or is_number(); - } - - /*! - @brief return whether type is structured - - This function returns true iff the JSON type is structured (array or - object). - - @return `true` if type is structured (array or object), `false` otherwise. - - @complexity Constant. - - @exceptionsafety No-throw guarantee: this member function never throws - exceptions. - - @liveexample{The following code exemplifies `is_structured()` for all JSON - types.,is_structured} - - @sa @ref is_primitive() -- returns whether value is primitive - @sa @ref is_array() -- returns whether value is an array - @sa @ref is_object() -- returns whether value is an object - - @since version 1.0.0 - */ - constexpr bool is_structured() const noexcept - { - return is_array() or is_object(); - } - - /*! - @brief return whether value is null - - This function returns true iff the JSON value is null. - - @return `true` if type is null, `false` otherwise. - - @complexity Constant. - - @exceptionsafety No-throw guarantee: this member function never throws - exceptions. - - @liveexample{The following code exemplifies `is_null()` for all JSON - types.,is_null} - - @since version 1.0.0 - */ - constexpr bool is_null() const noexcept - { - return m_type == value_t::null; - } - - /*! - @brief return whether value is a boolean - - This function returns true iff the JSON value is a boolean. - - @return `true` if type is boolean, `false` otherwise. - - @complexity Constant. - - @exceptionsafety No-throw guarantee: this member function never throws - exceptions. - - @liveexample{The following code exemplifies `is_boolean()` for all JSON - types.,is_boolean} - - @since version 1.0.0 - */ - constexpr bool is_boolean() const noexcept - { - return m_type == value_t::boolean; - } - - /*! - @brief return whether value is a number - - This function returns true iff the JSON value is a number. This includes - both integer and floating-point values. - - @return `true` if type is number (regardless whether integer, unsigned - integer or floating-type), `false` otherwise. - - @complexity Constant. - - @exceptionsafety No-throw guarantee: this member function never throws - exceptions. - - @liveexample{The following code exemplifies `is_number()` for all JSON - types.,is_number} - - @sa @ref is_number_integer() -- check if value is an integer or unsigned - integer number - @sa @ref is_number_unsigned() -- check if value is an unsigned integer - number - @sa @ref is_number_float() -- check if value is a floating-point number - - @since version 1.0.0 - */ - constexpr bool is_number() const noexcept - { - return is_number_integer() or is_number_float(); - } - - /*! - @brief return whether value is an integer number - - This function returns true iff the JSON value is an integer or unsigned - integer number. This excludes floating-point values. - - @return `true` if type is an integer or unsigned integer number, `false` - otherwise. - - @complexity Constant. - - @exceptionsafety No-throw guarantee: this member function never throws - exceptions. - - @liveexample{The following code exemplifies `is_number_integer()` for all - JSON types.,is_number_integer} - - @sa @ref is_number() -- check if value is a number - @sa @ref is_number_unsigned() -- check if value is an unsigned integer - number - @sa @ref is_number_float() -- check if value is a floating-point number - - @since version 1.0.0 - */ - constexpr bool is_number_integer() const noexcept - { - return m_type == value_t::number_integer or m_type == value_t::number_unsigned; - } - - /*! - @brief return whether value is an unsigned integer number - - This function returns true iff the JSON value is an unsigned integer - number. This excludes floating-point and (signed) integer values. - - @return `true` if type is an unsigned integer number, `false` otherwise. - - @complexity Constant. - - @exceptionsafety No-throw guarantee: this member function never throws - exceptions. - - @liveexample{The following code exemplifies `is_number_unsigned()` for all - JSON types.,is_number_unsigned} - - @sa @ref is_number() -- check if value is a number - @sa @ref is_number_integer() -- check if value is an integer or unsigned - integer number - @sa @ref is_number_float() -- check if value is a floating-point number - - @since version 2.0.0 - */ - constexpr bool is_number_unsigned() const noexcept - { - return m_type == value_t::number_unsigned; - } - - /*! - @brief return whether value is a floating-point number - - This function returns true iff the JSON value is a floating-point number. - This excludes integer and unsigned integer values. - - @return `true` if type is a floating-point number, `false` otherwise. - - @complexity Constant. - - @exceptionsafety No-throw guarantee: this member function never throws - exceptions. - - @liveexample{The following code exemplifies `is_number_float()` for all - JSON types.,is_number_float} - - @sa @ref is_number() -- check if value is number - @sa @ref is_number_integer() -- check if value is an integer number - @sa @ref is_number_unsigned() -- check if value is an unsigned integer - number - - @since version 1.0.0 - */ - constexpr bool is_number_float() const noexcept - { - return m_type == value_t::number_float; - } - - /*! - @brief return whether value is an object - - This function returns true iff the JSON value is an object. - - @return `true` if type is object, `false` otherwise. - - @complexity Constant. - - @exceptionsafety No-throw guarantee: this member function never throws - exceptions. - - @liveexample{The following code exemplifies `is_object()` for all JSON - types.,is_object} - - @since version 1.0.0 - */ - constexpr bool is_object() const noexcept - { - return m_type == value_t::object; - } - - /*! - @brief return whether value is an array - - This function returns true iff the JSON value is an array. - - @return `true` if type is array, `false` otherwise. - - @complexity Constant. - - @exceptionsafety No-throw guarantee: this member function never throws - exceptions. - - @liveexample{The following code exemplifies `is_array()` for all JSON - types.,is_array} - - @since version 1.0.0 - */ - constexpr bool is_array() const noexcept - { - return m_type == value_t::array; - } - - /*! - @brief return whether value is a string - - This function returns true iff the JSON value is a string. - - @return `true` if type is string, `false` otherwise. - - @complexity Constant. - - @exceptionsafety No-throw guarantee: this member function never throws - exceptions. - - @liveexample{The following code exemplifies `is_string()` for all JSON - types.,is_string} - - @since version 1.0.0 - */ - constexpr bool is_string() const noexcept - { - return m_type == value_t::string; - } - - /*! - @brief return whether value is discarded - - This function returns true iff the JSON value was discarded during parsing - with a callback function (see @ref parser_callback_t). - - @note This function will always be `false` for JSON values after parsing. - That is, discarded values can only occur during parsing, but will be - removed when inside a structured value or replaced by null in other cases. - - @return `true` if type is discarded, `false` otherwise. - - @complexity Constant. - - @exceptionsafety No-throw guarantee: this member function never throws - exceptions. - - @liveexample{The following code exemplifies `is_discarded()` for all JSON - types.,is_discarded} - - @since version 1.0.0 - */ - constexpr bool is_discarded() const noexcept - { - return m_type == value_t::discarded; - } - - /*! - @brief return the type of the JSON value (implicit) - - Implicitly return the type of the JSON value as a value from the @ref - value_t enumeration. - - @return the type of the JSON value - - @complexity Constant. - - @exceptionsafety No-throw guarantee: this member function never throws - exceptions. - - @liveexample{The following code exemplifies the @ref value_t operator for - all JSON types.,operator__value_t} - - @since version 1.0.0 - */ - constexpr operator value_t() const noexcept - { - return m_type; - } - - /// @} - - private: - ////////////////// - // value access // - ////////////////// - - /// get an object (explicit) - template::value and - std::is_convertible::value, int>::type = 0> - T get_impl(T*) const - { - if (is_object()) - { - return T(m_value.object->begin(), m_value.object->end()); - } - else - { - throw std::domain_error("type must be object, but is " + type_name()); - } - } - - /// get an object (explicit) - object_t get_impl(object_t*) const - { - if (is_object()) - { - return *(m_value.object); - } - else - { - throw std::domain_error("type must be object, but is " + type_name()); - } - } - - /// get an array (explicit) - template::value and - not std::is_same::value and - not std::is_arithmetic::value and - not std::is_convertible::value and - not has_mapped_type::value, int>::type = 0> - T get_impl(T*) const - { - if (is_array()) - { - T to_vector; - std::transform(m_value.array->begin(), m_value.array->end(), - std::inserter(to_vector, to_vector.end()), [](basic_json i) - { - return i.get(); - }); - return to_vector; - } - else - { - throw std::domain_error("type must be array, but is " + type_name()); - } - } - - /// get an array (explicit) - template::value and - not std::is_same::value, int>::type = 0> - std::vector get_impl(std::vector*) const - { - if (is_array()) - { - std::vector to_vector; - to_vector.reserve(m_value.array->size()); - std::transform(m_value.array->begin(), m_value.array->end(), - std::inserter(to_vector, to_vector.end()), [](basic_json i) - { - return i.get(); - }); - return to_vector; - } - else - { - throw std::domain_error("type must be array, but is " + type_name()); - } - } - - /// get an array (explicit) - template::value and - not has_mapped_type::value, int>::type = 0> - T get_impl(T*) const - { - if (is_array()) - { - return T(m_value.array->begin(), m_value.array->end()); - } - else - { - throw std::domain_error("type must be array, but is " + type_name()); - } - } - - /// get an array (explicit) - array_t get_impl(array_t*) const - { - if (is_array()) - { - return *(m_value.array); - } - else - { - throw std::domain_error("type must be array, but is " + type_name()); - } - } - - /// get a string (explicit) - template::value, int>::type = 0> - T get_impl(T*) const - { - if (is_string()) - { - return *m_value.string; - } - else - { - throw std::domain_error("type must be string, but is " + type_name()); - } - } - - /// get a number (explicit) - template::value, int>::type = 0> - T get_impl(T*) const - { - switch (m_type) - { - case value_t::number_integer: - { - return static_cast(m_value.number_integer); - } - - case value_t::number_unsigned: - { - return static_cast(m_value.number_unsigned); - } - - case value_t::number_float: - { - return static_cast(m_value.number_float); - } - - default: - { - throw std::domain_error("type must be number, but is " + type_name()); - } - } - } - - /// get a boolean (explicit) - constexpr boolean_t get_impl(boolean_t*) const - { - return is_boolean() - ? m_value.boolean - : throw std::domain_error("type must be boolean, but is " + type_name()); - } - - /// get a pointer to the value (object) - object_t* get_impl_ptr(object_t*) noexcept - { - return is_object() ? m_value.object : nullptr; - } - - /// get a pointer to the value (object) - constexpr const object_t* get_impl_ptr(const object_t*) const noexcept - { - return is_object() ? m_value.object : nullptr; - } - - /// get a pointer to the value (array) - array_t* get_impl_ptr(array_t*) noexcept - { - return is_array() ? m_value.array : nullptr; - } - - /// get a pointer to the value (array) - constexpr const array_t* get_impl_ptr(const array_t*) const noexcept - { - return is_array() ? m_value.array : nullptr; - } - - /// get a pointer to the value (string) - string_t* get_impl_ptr(string_t*) noexcept - { - return is_string() ? m_value.string : nullptr; - } - - /// get a pointer to the value (string) - constexpr const string_t* get_impl_ptr(const string_t*) const noexcept - { - return is_string() ? m_value.string : nullptr; - } - - /// get a pointer to the value (boolean) - boolean_t* get_impl_ptr(boolean_t*) noexcept - { - return is_boolean() ? &m_value.boolean : nullptr; - } - - /// get a pointer to the value (boolean) - constexpr const boolean_t* get_impl_ptr(const boolean_t*) const noexcept - { - return is_boolean() ? &m_value.boolean : nullptr; - } - - /// get a pointer to the value (integer number) - number_integer_t* get_impl_ptr(number_integer_t*) noexcept - { - return is_number_integer() ? &m_value.number_integer : nullptr; - } - - /// get a pointer to the value (integer number) - constexpr const number_integer_t* get_impl_ptr(const number_integer_t*) const noexcept - { - return is_number_integer() ? &m_value.number_integer : nullptr; - } - - /// get a pointer to the value (unsigned number) - number_unsigned_t* get_impl_ptr(number_unsigned_t*) noexcept - { - return is_number_unsigned() ? &m_value.number_unsigned : nullptr; - } - - /// get a pointer to the value (unsigned number) - constexpr const number_unsigned_t* get_impl_ptr(const number_unsigned_t*) const noexcept - { - return is_number_unsigned() ? &m_value.number_unsigned : nullptr; - } - - /// get a pointer to the value (floating-point number) - number_float_t* get_impl_ptr(number_float_t*) noexcept - { - return is_number_float() ? &m_value.number_float : nullptr; - } - - /// get a pointer to the value (floating-point number) - constexpr const number_float_t* get_impl_ptr(const number_float_t*) const noexcept - { - return is_number_float() ? &m_value.number_float : nullptr; - } - - /*! - @brief helper function to implement get_ref() - - This funcion helps to implement get_ref() without code duplication for - const and non-const overloads - - @tparam ThisType will be deduced as `basic_json` or `const basic_json` - - @throw std::domain_error if ReferenceType does not match underlying value - type of the current JSON - */ - template - static ReferenceType get_ref_impl(ThisType& obj) - { - // helper type - using PointerType = typename std::add_pointer::type; - - // delegate the call to get_ptr<>() - auto ptr = obj.template get_ptr(); - - if (ptr != nullptr) - { - return *ptr; - } - else - { - throw std::domain_error("incompatible ReferenceType for get_ref, actual type is " + - obj.type_name()); - } - } - - public: - - /// @name value access - /// Direct access to the stored value of a JSON value. - /// @{ - - /*! - @brief get a value (explicit) - - Explicit type conversion between the JSON value and a compatible value. - - @tparam ValueType non-pointer type compatible to the JSON value, for - instance `int` for JSON integer numbers, `bool` for JSON booleans, or - `std::vector` types for JSON arrays - - @return copy of the JSON value, converted to type @a ValueType - - @throw std::domain_error in case passed type @a ValueType is incompatible - to JSON; example: `"type must be object, but is null"` - - @complexity Linear in the size of the JSON value. - - @liveexample{The example below shows several conversions from JSON values - to other types. There a few things to note: (1) Floating-point numbers can - be converted to integers\, (2) A JSON array can be converted to a standard - `std::vector`\, (3) A JSON object can be converted to C++ - associative containers such as `std::unordered_map`.,get__ValueType_const} - - @internal - The idea of using a casted null pointer to choose the correct - implementation is from . - @endinternal - - @sa @ref operator ValueType() const for implicit conversion - @sa @ref get() for pointer-member access - - @since version 1.0.0 - */ - template::value, int>::type = 0> - ValueType get() const - { - return get_impl(static_cast(nullptr)); - } - - /*! - @brief get a pointer value (explicit) - - Explicit pointer access to the internally stored JSON value. No copies are - made. - - @warning The pointer becomes invalid if the underlying JSON object - changes. - - @tparam PointerType pointer type; must be a pointer to @ref array_t, @ref - object_t, @ref string_t, @ref boolean_t, @ref number_integer_t, - @ref number_unsigned_t, or @ref number_float_t. - - @return pointer to the internally stored JSON value if the requested - pointer type @a PointerType fits to the JSON value; `nullptr` otherwise - - @complexity Constant. - - @liveexample{The example below shows how pointers to internal values of a - JSON value can be requested. Note that no type conversions are made and a - `nullptr` is returned if the value and the requested pointer type does not - match.,get__PointerType} - - @sa @ref get_ptr() for explicit pointer-member access - - @since version 1.0.0 - */ - template::value, int>::type = 0> - PointerType get() noexcept - { - // delegate the call to get_ptr - return get_ptr(); - } - - /*! - @brief get a pointer value (explicit) - @copydoc get() - */ - template::value, int>::type = 0> - constexpr const PointerType get() const noexcept - { - // delegate the call to get_ptr - return get_ptr(); - } - - /*! - @brief get a pointer value (implicit) - - Implicit pointer access to the internally stored JSON value. No copies are - made. - - @warning Writing data to the pointee of the result yields an undefined - state. - - @tparam PointerType pointer type; must be a pointer to @ref array_t, @ref - object_t, @ref string_t, @ref boolean_t, @ref number_integer_t, - @ref number_unsigned_t, or @ref number_float_t. Enforced by a static - assertion. - - @return pointer to the internally stored JSON value if the requested - pointer type @a PointerType fits to the JSON value; `nullptr` otherwise - - @complexity Constant. - - @liveexample{The example below shows how pointers to internal values of a - JSON value can be requested. Note that no type conversions are made and a - `nullptr` is returned if the value and the requested pointer type does not - match.,get_ptr} - - @since version 1.0.0 - */ - template::value, int>::type = 0> - PointerType get_ptr() noexcept - { - // get the type of the PointerType (remove pointer and const) - using pointee_t = typename std::remove_const::type>::type>::type; - // make sure the type matches the allowed types - static_assert( - std::is_same::value - or std::is_same::value - or std::is_same::value - or std::is_same::value - or std::is_same::value - or std::is_same::value - or std::is_same::value - , "incompatible pointer type"); - - // delegate the call to get_impl_ptr<>() - return get_impl_ptr(static_cast(nullptr)); - } - - /*! - @brief get a pointer value (implicit) - @copydoc get_ptr() - */ - template::value and - std::is_const::type>::value, int>::type = 0> - constexpr const PointerType get_ptr() const noexcept - { - // get the type of the PointerType (remove pointer and const) - using pointee_t = typename std::remove_const::type>::type>::type; - // make sure the type matches the allowed types - static_assert( - std::is_same::value - or std::is_same::value - or std::is_same::value - or std::is_same::value - or std::is_same::value - or std::is_same::value - or std::is_same::value - , "incompatible pointer type"); - - // delegate the call to get_impl_ptr<>() const - return get_impl_ptr(static_cast(nullptr)); - } - - /*! - @brief get a reference value (implicit) - - Implict reference access to the internally stored JSON value. No copies - are made. - - @warning Writing data to the referee of the result yields an undefined - state. - - @tparam ReferenceType reference type; must be a reference to @ref array_t, - @ref object_t, @ref string_t, @ref boolean_t, @ref number_integer_t, or - @ref number_float_t. Enforced by static assertion. - - @return reference to the internally stored JSON value if the requested - reference type @a ReferenceType fits to the JSON value; throws - std::domain_error otherwise - - @throw std::domain_error in case passed type @a ReferenceType is - incompatible with the stored JSON value - - @complexity Constant. - - @liveexample{The example shows several calls to `get_ref()`.,get_ref} - - @since version 1.1.0 - */ - template::value, int>::type = 0> - ReferenceType get_ref() - { - // delegate call to get_ref_impl - return get_ref_impl(*this); - } - - /*! - @brief get a reference value (implicit) - @copydoc get_ref() - */ - template::value and - std::is_const::type>::value, int>::type = 0> - ReferenceType get_ref() const - { - // delegate call to get_ref_impl - return get_ref_impl(*this); - } - - /*! - @brief get a value (implicit) - - Implicit type conversion between the JSON value and a compatible value. - The call is realized by calling @ref get() const. - - @tparam ValueType non-pointer type compatible to the JSON value, for - instance `int` for JSON integer numbers, `bool` for JSON booleans, or - `std::vector` types for JSON arrays. The character type of @ref string_t - as well as an initializer list of this type is excluded to avoid - ambiguities as these types implicitly convert to `std::string`. - - @return copy of the JSON value, converted to type @a ValueType - - @throw std::domain_error in case passed type @a ValueType is incompatible - to JSON, thrown by @ref get() const - - @complexity Linear in the size of the JSON value. - - @liveexample{The example below shows several conversions from JSON values - to other types. There a few things to note: (1) Floating-point numbers can - be converted to integers\, (2) A JSON array can be converted to a standard - `std::vector`\, (3) A JSON object can be converted to C++ - associative containers such as `std::unordered_map`.,operator__ValueType} - - @since version 1.0.0 - */ - template < typename ValueType, typename std::enable_if < - not std::is_pointer::value and - not std::is_same::value -#ifndef _MSC_VER // Fix for issue #167 operator<< abiguity under VS2015 - and not std::is_same>::value -#endif - , int >::type = 0 > - operator ValueType() const - { - // delegate the call to get<>() const - return get(); - } - - /// @} - - - //////////////////// - // element access // - //////////////////// - - /// @name element access - /// Access to the JSON value. - /// @{ - - /*! - @brief access specified array element with bounds checking - - Returns a reference to the element at specified location @a idx, with - bounds checking. - - @param[in] idx index of the element to access - - @return reference to the element at index @a idx - - @throw std::domain_error if the JSON value is not an array; example: - `"cannot use at() with string"` - @throw std::out_of_range if the index @a idx is out of range of the array; - that is, `idx >= size()`; example: `"array index 7 is out of range"` - - @complexity Constant. - - @liveexample{The example below shows how array elements can be read and - written using `at()`.,at__size_type} - - @since version 1.0.0 - */ - reference at(size_type idx) - { - // at only works for arrays - if (is_array()) - { - try - { - return m_value.array->at(idx); - } - catch (std::out_of_range&) - { - // create better exception explanation - throw std::out_of_range("array index " + std::to_string(idx) + " is out of range"); - } - } - else - { - throw std::domain_error("cannot use at() with " + type_name()); - } - } - - /*! - @brief access specified array element with bounds checking - - Returns a const reference to the element at specified location @a idx, - with bounds checking. - - @param[in] idx index of the element to access - - @return const reference to the element at index @a idx - - @throw std::domain_error if the JSON value is not an array; example: - `"cannot use at() with string"` - @throw std::out_of_range if the index @a idx is out of range of the array; - that is, `idx >= size()`; example: `"array index 7 is out of range"` - - @complexity Constant. - - @liveexample{The example below shows how array elements can be read using - `at()`.,at__size_type_const} - - @since version 1.0.0 - */ - const_reference at(size_type idx) const - { - // at only works for arrays - if (is_array()) - { - try - { - return m_value.array->at(idx); - } - catch (std::out_of_range&) - { - // create better exception explanation - throw std::out_of_range("array index " + std::to_string(idx) + " is out of range"); - } - } - else - { - throw std::domain_error("cannot use at() with " + type_name()); - } - } - - /*! - @brief access specified object element with bounds checking - - Returns a reference to the element at with specified key @a key, with - bounds checking. - - @param[in] key key of the element to access - - @return reference to the element at key @a key - - @throw std::domain_error if the JSON value is not an object; example: - `"cannot use at() with boolean"` - @throw std::out_of_range if the key @a key is is not stored in the object; - that is, `find(key) == end()`; example: `"key "the fast" not found"` - - @complexity Logarithmic in the size of the container. - - @liveexample{The example below shows how object elements can be read and - written using `at()`.,at__object_t_key_type} - - @sa @ref operator[](const typename object_t::key_type&) for unchecked - access by reference - @sa @ref value() for access by value with a default value - - @since version 1.0.0 - */ - reference at(const typename object_t::key_type& key) - { - // at only works for objects - if (is_object()) - { - try - { - return m_value.object->at(key); - } - catch (std::out_of_range&) - { - // create better exception explanation - throw std::out_of_range("key '" + key + "' not found"); - } - } - else - { - throw std::domain_error("cannot use at() with " + type_name()); - } - } - - /*! - @brief access specified object element with bounds checking - - Returns a const reference to the element at with specified key @a key, - with bounds checking. - - @param[in] key key of the element to access - - @return const reference to the element at key @a key - - @throw std::domain_error if the JSON value is not an object; example: - `"cannot use at() with boolean"` - @throw std::out_of_range if the key @a key is is not stored in the object; - that is, `find(key) == end()`; example: `"key "the fast" not found"` - - @complexity Logarithmic in the size of the container. - - @liveexample{The example below shows how object elements can be read using - `at()`.,at__object_t_key_type_const} - - @sa @ref operator[](const typename object_t::key_type&) for unchecked - access by reference - @sa @ref value() for access by value with a default value - - @since version 1.0.0 - */ - const_reference at(const typename object_t::key_type& key) const - { - // at only works for objects - if (is_object()) - { - try - { - return m_value.object->at(key); - } - catch (std::out_of_range&) - { - // create better exception explanation - throw std::out_of_range("key '" + key + "' not found"); - } - } - else - { - throw std::domain_error("cannot use at() with " + type_name()); - } - } - - /*! - @brief access specified array element - - Returns a reference to the element at specified location @a idx. - - @note If @a idx is beyond the range of the array (i.e., `idx >= size()`), - then the array is silently filled up with `null` values to make `idx` a - valid reference to the last stored element. - - @param[in] idx index of the element to access - - @return reference to the element at index @a idx - - @throw std::domain_error if JSON is not an array or null; example: - `"cannot use operator[] with string"` - - @complexity Constant if @a idx is in the range of the array. Otherwise - linear in `idx - size()`. - - @liveexample{The example below shows how array elements can be read and - written using `[]` operator. Note the addition of `null` - values.,operatorarray__size_type} - - @since version 1.0.0 - */ - reference operator[](size_type idx) - { - // implicitly convert null value to an empty array - if (is_null()) - { - m_type = value_t::array; - m_value.array = create(); - assert_invariant(); - } - - // operator[] only works for arrays - if (is_array()) - { - // fill up array with null values if given idx is outside range - if (idx >= m_value.array->size()) - { - m_value.array->insert(m_value.array->end(), - idx - m_value.array->size() + 1, - basic_json()); - } - - return m_value.array->operator[](idx); - } - else - { - throw std::domain_error("cannot use operator[] with " + type_name()); - } - } - - /*! - @brief access specified array element - - Returns a const reference to the element at specified location @a idx. - - @param[in] idx index of the element to access - - @return const reference to the element at index @a idx - - @throw std::domain_error if JSON is not an array; example: `"cannot use - operator[] with null"` - - @complexity Constant. - - @liveexample{The example below shows how array elements can be read using - the `[]` operator.,operatorarray__size_type_const} - - @since version 1.0.0 - */ - const_reference operator[](size_type idx) const - { - // const operator[] only works for arrays - if (is_array()) - { - return m_value.array->operator[](idx); - } - else - { - throw std::domain_error("cannot use operator[] with " + type_name()); - } - } - - /*! - @brief access specified object element - - Returns a reference to the element at with specified key @a key. - - @note If @a key is not found in the object, then it is silently added to - the object and filled with a `null` value to make `key` a valid reference. - In case the value was `null` before, it is converted to an object. - - @param[in] key key of the element to access - - @return reference to the element at key @a key - - @throw std::domain_error if JSON is not an object or null; example: - `"cannot use operator[] with string"` - - @complexity Logarithmic in the size of the container. - - @liveexample{The example below shows how object elements can be read and - written using the `[]` operator.,operatorarray__key_type} - - @sa @ref at(const typename object_t::key_type&) for access by reference - with range checking - @sa @ref value() for access by value with a default value - - @since version 1.0.0 - */ - reference operator[](const typename object_t::key_type& key) - { - // implicitly convert null value to an empty object - if (is_null()) - { - m_type = value_t::object; - m_value.object = create(); - assert_invariant(); - } - - // operator[] only works for objects - if (is_object()) - { - return m_value.object->operator[](key); - } - else - { - throw std::domain_error("cannot use operator[] with " + type_name()); - } - } - - /*! - @brief read-only access specified object element - - Returns a const reference to the element at with specified key @a key. No - bounds checking is performed. - - @warning If the element with key @a key does not exist, the behavior is - undefined. - - @param[in] key key of the element to access - - @return const reference to the element at key @a key - - @pre The element with key @a key must exist. **This precondition is - enforced with an assertion.** - - @throw std::domain_error if JSON is not an object; example: `"cannot use - operator[] with null"` - - @complexity Logarithmic in the size of the container. - - @liveexample{The example below shows how object elements can be read using - the `[]` operator.,operatorarray__key_type_const} - - @sa @ref at(const typename object_t::key_type&) for access by reference - with range checking - @sa @ref value() for access by value with a default value - - @since version 1.0.0 - */ - const_reference operator[](const typename object_t::key_type& key) const - { - // const operator[] only works for objects - if (is_object()) - { - assert(m_value.object->find(key) != m_value.object->end()); - return m_value.object->find(key)->second; - } - else - { - throw std::domain_error("cannot use operator[] with " + type_name()); - } - } - - /*! - @brief access specified object element - - Returns a reference to the element at with specified key @a key. - - @note If @a key is not found in the object, then it is silently added to - the object and filled with a `null` value to make `key` a valid reference. - In case the value was `null` before, it is converted to an object. - - @param[in] key key of the element to access - - @return reference to the element at key @a key - - @throw std::domain_error if JSON is not an object or null; example: - `"cannot use operator[] with string"` - - @complexity Logarithmic in the size of the container. - - @liveexample{The example below shows how object elements can be read and - written using the `[]` operator.,operatorarray__key_type} - - @sa @ref at(const typename object_t::key_type&) for access by reference - with range checking - @sa @ref value() for access by value with a default value - - @since version 1.0.0 - */ - template - reference operator[](T * (&key)[n]) - { - return operator[](static_cast(key)); - } - - /*! - @brief read-only access specified object element - - Returns a const reference to the element at with specified key @a key. No - bounds checking is performed. - - @warning If the element with key @a key does not exist, the behavior is - undefined. - - @note This function is required for compatibility reasons with Clang. - - @param[in] key key of the element to access - - @return const reference to the element at key @a key - - @throw std::domain_error if JSON is not an object; example: `"cannot use - operator[] with null"` - - @complexity Logarithmic in the size of the container. - - @liveexample{The example below shows how object elements can be read using - the `[]` operator.,operatorarray__key_type_const} - - @sa @ref at(const typename object_t::key_type&) for access by reference - with range checking - @sa @ref value() for access by value with a default value - - @since version 1.0.0 - */ - template - const_reference operator[](T * (&key)[n]) const - { - return operator[](static_cast(key)); - } - - /*! - @brief access specified object element - - Returns a reference to the element at with specified key @a key. - - @note If @a key is not found in the object, then it is silently added to - the object and filled with a `null` value to make `key` a valid reference. - In case the value was `null` before, it is converted to an object. - - @param[in] key key of the element to access - - @return reference to the element at key @a key - - @throw std::domain_error if JSON is not an object or null; example: - `"cannot use operator[] with string"` - - @complexity Logarithmic in the size of the container. - - @liveexample{The example below shows how object elements can be read and - written using the `[]` operator.,operatorarray__key_type} - - @sa @ref at(const typename object_t::key_type&) for access by reference - with range checking - @sa @ref value() for access by value with a default value - - @since version 1.1.0 - */ - template - reference operator[](T* key) - { - // implicitly convert null to object - if (is_null()) - { - m_type = value_t::object; - m_value = value_t::object; - assert_invariant(); - } - - // at only works for objects - if (is_object()) - { - return m_value.object->operator[](key); - } - else - { - throw std::domain_error("cannot use operator[] with " + type_name()); - } - } - - /*! - @brief read-only access specified object element - - Returns a const reference to the element at with specified key @a key. No - bounds checking is performed. - - @warning If the element with key @a key does not exist, the behavior is - undefined. - - @param[in] key key of the element to access - - @return const reference to the element at key @a key - - @pre The element with key @a key must exist. **This precondition is - enforced with an assertion.** - - @throw std::domain_error if JSON is not an object; example: `"cannot use - operator[] with null"` - - @complexity Logarithmic in the size of the container. - - @liveexample{The example below shows how object elements can be read using - the `[]` operator.,operatorarray__key_type_const} - - @sa @ref at(const typename object_t::key_type&) for access by reference - with range checking - @sa @ref value() for access by value with a default value - - @since version 1.1.0 - */ - template - const_reference operator[](T* key) const - { - // at only works for objects - if (is_object()) - { - assert(m_value.object->find(key) != m_value.object->end()); - return m_value.object->find(key)->second; - } - else - { - throw std::domain_error("cannot use operator[] with " + type_name()); - } - } - - /*! - @brief access specified object element with default value - - Returns either a copy of an object's element at the specified key @a key - or a given default value if no element with key @a key exists. - - The function is basically equivalent to executing - @code {.cpp} - try { - return at(key); - } catch(std::out_of_range) { - return default_value; - } - @endcode - - @note Unlike @ref at(const typename object_t::key_type&), this function - does not throw if the given key @a key was not found. - - @note Unlike @ref operator[](const typename object_t::key_type& key), this - function does not implicitly add an element to the position defined by @a - key. This function is furthermore also applicable to const objects. - - @param[in] key key of the element to access - @param[in] default_value the value to return if @a key is not found - - @tparam ValueType type compatible to JSON values, for instance `int` for - JSON integer numbers, `bool` for JSON booleans, or `std::vector` types for - JSON arrays. Note the type of the expected value at @a key and the default - value @a default_value must be compatible. - - @return copy of the element at key @a key or @a default_value if @a key - is not found - - @throw std::domain_error if JSON is not an object; example: `"cannot use - value() with null"` - - @complexity Logarithmic in the size of the container. - - @liveexample{The example below shows how object elements can be queried - with a default value.,basic_json__value} - - @sa @ref at(const typename object_t::key_type&) for access by reference - with range checking - @sa @ref operator[](const typename object_t::key_type&) for unchecked - access by reference - - @since version 1.0.0 - */ - template::value, int>::type = 0> - ValueType value(const typename object_t::key_type& key, ValueType default_value) const - { - // at only works for objects - if (is_object()) - { - // if key is found, return value and given default value otherwise - const auto it = find(key); - if (it != end()) - { - return *it; - } - else - { - return default_value; - } - } - else - { - throw std::domain_error("cannot use value() with " + type_name()); - } - } - - /*! - @brief overload for a default value of type const char* - @copydoc basic_json::value(const typename object_t::key_type&, ValueType) const - */ - string_t value(const typename object_t::key_type& key, const char* default_value) const - { - return value(key, string_t(default_value)); - } - - /*! - @brief access specified object element via JSON Pointer with default value - - Returns either a copy of an object's element at the specified key @a key - or a given default value if no element with key @a key exists. - - The function is basically equivalent to executing - @code {.cpp} - try { - return at(ptr); - } catch(std::out_of_range) { - return default_value; - } - @endcode - - @note Unlike @ref at(const json_pointer&), this function does not throw - if the given key @a key was not found. - - @param[in] ptr a JSON pointer to the element to access - @param[in] default_value the value to return if @a ptr found no value - - @tparam ValueType type compatible to JSON values, for instance `int` for - JSON integer numbers, `bool` for JSON booleans, or `std::vector` types for - JSON arrays. Note the type of the expected value at @a key and the default - value @a default_value must be compatible. - - @return copy of the element at key @a key or @a default_value if @a key - is not found - - @throw std::domain_error if JSON is not an object; example: `"cannot use - value() with null"` - - @complexity Logarithmic in the size of the container. - - @liveexample{The example below shows how object elements can be queried - with a default value.,basic_json__value_ptr} - - @sa @ref operator[](const json_pointer&) for unchecked access by reference - - @since version 2.0.2 - */ - template::value, int>::type = 0> - ValueType value(const json_pointer& ptr, ValueType default_value) const - { - // at only works for objects - if (is_object()) - { - // if pointer resolves a value, return it or use default value - try - { - return ptr.get_checked(this); - } - catch (std::out_of_range&) - { - return default_value; - } - } - else - { - throw std::domain_error("cannot use value() with " + type_name()); - } - } - - /*! - @brief overload for a default value of type const char* - @copydoc basic_json::value(const json_pointer&, ValueType) const - */ - string_t value(const json_pointer& ptr, const char* default_value) const - { - return value(ptr, string_t(default_value)); - } - - /*! - @brief access the first element - - Returns a reference to the first element in the container. For a JSON - container `c`, the expression `c.front()` is equivalent to `*c.begin()`. - - @return In case of a structured type (array or object), a reference to the - first element is returned. In cast of number, string, or boolean values, a - reference to the value is returned. - - @complexity Constant. - - @pre The JSON value must not be `null` (would throw `std::out_of_range`) - or an empty array or object (undefined behavior, **guarded by - assertions**). - @post The JSON value remains unchanged. - - @throw std::out_of_range when called on `null` value - - @liveexample{The following code shows an example for `front()`.,front} - - @sa @ref back() -- access the last element - - @since version 1.0.0 - */ - reference front() - { - return *begin(); - } - - /*! - @copydoc basic_json::front() - */ - const_reference front() const - { - return *cbegin(); - } - - /*! - @brief access the last element - - Returns a reference to the last element in the container. For a JSON - container `c`, the expression `c.back()` is equivalent to - @code {.cpp} - auto tmp = c.end(); - --tmp; - return *tmp; - @endcode - - @return In case of a structured type (array or object), a reference to the - last element is returned. In cast of number, string, or boolean values, a - reference to the value is returned. - - @complexity Constant. - - @pre The JSON value must not be `null` (would throw `std::out_of_range`) - or an empty array or object (undefined behavior, **guarded by - assertions**). - @post The JSON value remains unchanged. - - @throw std::out_of_range when called on `null` value. - - @liveexample{The following code shows an example for `back()`.,back} - - @sa @ref front() -- access the first element - - @since version 1.0.0 - */ - reference back() - { - auto tmp = end(); - --tmp; - return *tmp; - } - - /*! - @copydoc basic_json::back() - */ - const_reference back() const - { - auto tmp = cend(); - --tmp; - return *tmp; - } - - /*! - @brief remove element given an iterator - - Removes the element specified by iterator @a pos. The iterator @a pos must - be valid and dereferenceable. Thus the `end()` iterator (which is valid, - but is not dereferenceable) cannot be used as a value for @a pos. - - If called on a primitive type other than `null`, the resulting JSON value - will be `null`. - - @param[in] pos iterator to the element to remove - @return Iterator following the last removed element. If the iterator @a - pos refers to the last element, the `end()` iterator is returned. - - @tparam IteratorType an @ref iterator or @ref const_iterator - - @post Invalidates iterators and references at or after the point of the - erase, including the `end()` iterator. - - @throw std::domain_error if called on a `null` value; example: `"cannot - use erase() with null"` - @throw std::domain_error if called on an iterator which does not belong to - the current JSON value; example: `"iterator does not fit current value"` - @throw std::out_of_range if called on a primitive type with invalid - iterator (i.e., any iterator which is not `begin()`); example: `"iterator - out of range"` - - @complexity The complexity depends on the type: - - objects: amortized constant - - arrays: linear in distance between pos and the end of the container - - strings: linear in the length of the string - - other types: constant - - @liveexample{The example shows the result of `erase()` for different JSON - types.,erase__IteratorType} - - @sa @ref erase(IteratorType, IteratorType) -- removes the elements in - the given range - @sa @ref erase(const typename object_t::key_type&) -- removes the element - from an object at the given key - @sa @ref erase(const size_type) -- removes the element from an array at - the given index - - @since version 1.0.0 - */ - template::value or - std::is_same::value, int>::type - = 0> - IteratorType erase(IteratorType pos) - { - // make sure iterator fits the current value - if (this != pos.m_object) - { - throw std::domain_error("iterator does not fit current value"); - } - - IteratorType result = end(); - - switch (m_type) - { - case value_t::boolean: - case value_t::number_float: - case value_t::number_integer: - case value_t::number_unsigned: - case value_t::string: - { - if (not pos.m_it.primitive_iterator.is_begin()) - { - throw std::out_of_range("iterator out of range"); - } - - if (is_string()) - { - AllocatorType alloc; - alloc.destroy(m_value.string); - alloc.deallocate(m_value.string, 1); - m_value.string = nullptr; - } - - m_type = value_t::null; - assert_invariant(); - break; - } - - case value_t::object: - { - result.m_it.object_iterator = m_value.object->erase(pos.m_it.object_iterator); - break; - } - - case value_t::array: - { - result.m_it.array_iterator = m_value.array->erase(pos.m_it.array_iterator); - break; - } - - default: - { - throw std::domain_error("cannot use erase() with " + type_name()); - } - } - - return result; - } - - /*! - @brief remove elements given an iterator range - - Removes the element specified by the range `[first; last)`. The iterator - @a first does not need to be dereferenceable if `first == last`: erasing - an empty range is a no-op. - - If called on a primitive type other than `null`, the resulting JSON value - will be `null`. - - @param[in] first iterator to the beginning of the range to remove - @param[in] last iterator past the end of the range to remove - @return Iterator following the last removed element. If the iterator @a - second refers to the last element, the `end()` iterator is returned. - - @tparam IteratorType an @ref iterator or @ref const_iterator - - @post Invalidates iterators and references at or after the point of the - erase, including the `end()` iterator. - - @throw std::domain_error if called on a `null` value; example: `"cannot - use erase() with null"` - @throw std::domain_error if called on iterators which does not belong to - the current JSON value; example: `"iterators do not fit current value"` - @throw std::out_of_range if called on a primitive type with invalid - iterators (i.e., if `first != begin()` and `last != end()`); example: - `"iterators out of range"` - - @complexity The complexity depends on the type: - - objects: `log(size()) + std::distance(first, last)` - - arrays: linear in the distance between @a first and @a last, plus linear - in the distance between @a last and end of the container - - strings: linear in the length of the string - - other types: constant - - @liveexample{The example shows the result of `erase()` for different JSON - types.,erase__IteratorType_IteratorType} - - @sa @ref erase(IteratorType) -- removes the element at a given position - @sa @ref erase(const typename object_t::key_type&) -- removes the element - from an object at the given key - @sa @ref erase(const size_type) -- removes the element from an array at - the given index - - @since version 1.0.0 - */ - template::value or - std::is_same::value, int>::type - = 0> - IteratorType erase(IteratorType first, IteratorType last) - { - // make sure iterator fits the current value - if (this != first.m_object or this != last.m_object) - { - throw std::domain_error("iterators do not fit current value"); - } - - IteratorType result = end(); - - switch (m_type) - { - case value_t::boolean: - case value_t::number_float: - case value_t::number_integer: - case value_t::number_unsigned: - case value_t::string: - { - if (not first.m_it.primitive_iterator.is_begin() or not last.m_it.primitive_iterator.is_end()) - { - throw std::out_of_range("iterators out of range"); - } - - if (is_string()) - { - AllocatorType alloc; - alloc.destroy(m_value.string); - alloc.deallocate(m_value.string, 1); - m_value.string = nullptr; - } - - m_type = value_t::null; - assert_invariant(); - break; - } - - case value_t::object: - { - result.m_it.object_iterator = m_value.object->erase(first.m_it.object_iterator, - last.m_it.object_iterator); - break; - } - - case value_t::array: - { - result.m_it.array_iterator = m_value.array->erase(first.m_it.array_iterator, - last.m_it.array_iterator); - break; - } - - default: - { - throw std::domain_error("cannot use erase() with " + type_name()); - } - } - - return result; - } - - /*! - @brief remove element from a JSON object given a key - - Removes elements from a JSON object with the key value @a key. - - @param[in] key value of the elements to remove - - @return Number of elements removed. If @a ObjectType is the default - `std::map` type, the return value will always be `0` (@a key was not - found) or `1` (@a key was found). - - @post References and iterators to the erased elements are invalidated. - Other references and iterators are not affected. - - @throw std::domain_error when called on a type other than JSON object; - example: `"cannot use erase() with null"` - - @complexity `log(size()) + count(key)` - - @liveexample{The example shows the effect of `erase()`.,erase__key_type} - - @sa @ref erase(IteratorType) -- removes the element at a given position - @sa @ref erase(IteratorType, IteratorType) -- removes the elements in - the given range - @sa @ref erase(const size_type) -- removes the element from an array at - the given index - - @since version 1.0.0 - */ - size_type erase(const typename object_t::key_type& key) - { - // this erase only works for objects - if (is_object()) - { - return m_value.object->erase(key); - } - else - { - throw std::domain_error("cannot use erase() with " + type_name()); - } - } - - /*! - @brief remove element from a JSON array given an index - - Removes element from a JSON array at the index @a idx. - - @param[in] idx index of the element to remove - - @throw std::domain_error when called on a type other than JSON array; - example: `"cannot use erase() with null"` - @throw std::out_of_range when `idx >= size()`; example: `"array index 17 - is out of range"` - - @complexity Linear in distance between @a idx and the end of the container. - - @liveexample{The example shows the effect of `erase()`.,erase__size_type} - - @sa @ref erase(IteratorType) -- removes the element at a given position - @sa @ref erase(IteratorType, IteratorType) -- removes the elements in - the given range - @sa @ref erase(const typename object_t::key_type&) -- removes the element - from an object at the given key - - @since version 1.0.0 - */ - void erase(const size_type idx) - { - // this erase only works for arrays - if (is_array()) - { - if (idx >= size()) - { - throw std::out_of_range("array index " + std::to_string(idx) + " is out of range"); - } - - m_value.array->erase(m_value.array->begin() + static_cast(idx)); - } - else - { - throw std::domain_error("cannot use erase() with " + type_name()); - } - } - - /// @} - - - //////////// - // lookup // - //////////// - - /// @name lookup - /// @{ - - /*! - @brief find an element in a JSON object - - Finds an element in a JSON object with key equivalent to @a key. If the - element is not found or the JSON value is not an object, end() is - returned. - - @param[in] key key value of the element to search for - - @return Iterator to an element with key equivalent to @a key. If no such - element is found, past-the-end (see end()) iterator is returned. - - @complexity Logarithmic in the size of the JSON object. - - @liveexample{The example shows how `find()` is used.,find__key_type} - - @since version 1.0.0 - */ - iterator find(typename object_t::key_type key) - { - auto result = end(); - - if (is_object()) - { - result.m_it.object_iterator = m_value.object->find(key); - } - - return result; - } - - /*! - @brief find an element in a JSON object - @copydoc find(typename object_t::key_type) - */ - const_iterator find(typename object_t::key_type key) const - { - auto result = cend(); - - if (is_object()) - { - result.m_it.object_iterator = m_value.object->find(key); - } - - return result; - } - - /*! - @brief returns the number of occurrences of a key in a JSON object - - Returns the number of elements with key @a key. If ObjectType is the - default `std::map` type, the return value will always be `0` (@a key was - not found) or `1` (@a key was found). - - @param[in] key key value of the element to count - - @return Number of elements with key @a key. If the JSON value is not an - object, the return value will be `0`. - - @complexity Logarithmic in the size of the JSON object. - - @liveexample{The example shows how `count()` is used.,count} - - @since version 1.0.0 - */ - size_type count(typename object_t::key_type key) const - { - // return 0 for all nonobject types - return is_object() ? m_value.object->count(key) : 0; - } - - /// @} - - - /////////////// - // iterators // - /////////////// - - /// @name iterators - /// @{ - - /*! - @brief returns an iterator to the first element - - Returns an iterator to the first element. - - @image html range-begin-end.svg "Illustration from cppreference.com" - - @return iterator to the first element - - @complexity Constant. - - @requirement This function helps `basic_json` satisfying the - [Container](http://en.cppreference.com/w/cpp/concept/Container) - requirements: - - The complexity is constant. - - @liveexample{The following code shows an example for `begin()`.,begin} - - @sa @ref cbegin() -- returns a const iterator to the beginning - @sa @ref end() -- returns an iterator to the end - @sa @ref cend() -- returns a const iterator to the end - - @since version 1.0.0 - */ - iterator begin() noexcept - { - iterator result(this); - result.set_begin(); - return result; - } - - /*! - @copydoc basic_json::cbegin() - */ - const_iterator begin() const noexcept - { - return cbegin(); - } - - /*! - @brief returns a const iterator to the first element - - Returns a const iterator to the first element. - - @image html range-begin-end.svg "Illustration from cppreference.com" - - @return const iterator to the first element - - @complexity Constant. - - @requirement This function helps `basic_json` satisfying the - [Container](http://en.cppreference.com/w/cpp/concept/Container) - requirements: - - The complexity is constant. - - Has the semantics of `const_cast(*this).begin()`. - - @liveexample{The following code shows an example for `cbegin()`.,cbegin} - - @sa @ref begin() -- returns an iterator to the beginning - @sa @ref end() -- returns an iterator to the end - @sa @ref cend() -- returns a const iterator to the end - - @since version 1.0.0 - */ - const_iterator cbegin() const noexcept - { - const_iterator result(this); - result.set_begin(); - return result; - } - - /*! - @brief returns an iterator to one past the last element - - Returns an iterator to one past the last element. - - @image html range-begin-end.svg "Illustration from cppreference.com" - - @return iterator one past the last element - - @complexity Constant. - - @requirement This function helps `basic_json` satisfying the - [Container](http://en.cppreference.com/w/cpp/concept/Container) - requirements: - - The complexity is constant. - - @liveexample{The following code shows an example for `end()`.,end} - - @sa @ref cend() -- returns a const iterator to the end - @sa @ref begin() -- returns an iterator to the beginning - @sa @ref cbegin() -- returns a const iterator to the beginning - - @since version 1.0.0 - */ - iterator end() noexcept - { - iterator result(this); - result.set_end(); - return result; - } - - /*! - @copydoc basic_json::cend() - */ - const_iterator end() const noexcept - { - return cend(); - } - - /*! - @brief returns a const iterator to one past the last element - - Returns a const iterator to one past the last element. - - @image html range-begin-end.svg "Illustration from cppreference.com" - - @return const iterator one past the last element - - @complexity Constant. - - @requirement This function helps `basic_json` satisfying the - [Container](http://en.cppreference.com/w/cpp/concept/Container) - requirements: - - The complexity is constant. - - Has the semantics of `const_cast(*this).end()`. - - @liveexample{The following code shows an example for `cend()`.,cend} - - @sa @ref end() -- returns an iterator to the end - @sa @ref begin() -- returns an iterator to the beginning - @sa @ref cbegin() -- returns a const iterator to the beginning - - @since version 1.0.0 - */ - const_iterator cend() const noexcept - { - const_iterator result(this); - result.set_end(); - return result; - } - - /*! - @brief returns an iterator to the reverse-beginning - - Returns an iterator to the reverse-beginning; that is, the last element. - - @image html range-rbegin-rend.svg "Illustration from cppreference.com" - - @complexity Constant. - - @requirement This function helps `basic_json` satisfying the - [ReversibleContainer](http://en.cppreference.com/w/cpp/concept/ReversibleContainer) - requirements: - - The complexity is constant. - - Has the semantics of `reverse_iterator(end())`. - - @liveexample{The following code shows an example for `rbegin()`.,rbegin} - - @sa @ref crbegin() -- returns a const reverse iterator to the beginning - @sa @ref rend() -- returns a reverse iterator to the end - @sa @ref crend() -- returns a const reverse iterator to the end - - @since version 1.0.0 - */ - reverse_iterator rbegin() noexcept - { - return reverse_iterator(end()); - } - - /*! - @copydoc basic_json::crbegin() - */ - const_reverse_iterator rbegin() const noexcept - { - return crbegin(); - } - - /*! - @brief returns an iterator to the reverse-end - - Returns an iterator to the reverse-end; that is, one before the first - element. - - @image html range-rbegin-rend.svg "Illustration from cppreference.com" - - @complexity Constant. - - @requirement This function helps `basic_json` satisfying the - [ReversibleContainer](http://en.cppreference.com/w/cpp/concept/ReversibleContainer) - requirements: - - The complexity is constant. - - Has the semantics of `reverse_iterator(begin())`. - - @liveexample{The following code shows an example for `rend()`.,rend} - - @sa @ref crend() -- returns a const reverse iterator to the end - @sa @ref rbegin() -- returns a reverse iterator to the beginning - @sa @ref crbegin() -- returns a const reverse iterator to the beginning - - @since version 1.0.0 - */ - reverse_iterator rend() noexcept - { - return reverse_iterator(begin()); - } - - /*! - @copydoc basic_json::crend() - */ - const_reverse_iterator rend() const noexcept - { - return crend(); - } - - /*! - @brief returns a const reverse iterator to the last element - - Returns a const iterator to the reverse-beginning; that is, the last - element. - - @image html range-rbegin-rend.svg "Illustration from cppreference.com" - - @complexity Constant. - - @requirement This function helps `basic_json` satisfying the - [ReversibleContainer](http://en.cppreference.com/w/cpp/concept/ReversibleContainer) - requirements: - - The complexity is constant. - - Has the semantics of `const_cast(*this).rbegin()`. - - @liveexample{The following code shows an example for `crbegin()`.,crbegin} - - @sa @ref rbegin() -- returns a reverse iterator to the beginning - @sa @ref rend() -- returns a reverse iterator to the end - @sa @ref crend() -- returns a const reverse iterator to the end - - @since version 1.0.0 - */ - const_reverse_iterator crbegin() const noexcept - { - return const_reverse_iterator(cend()); - } - - /*! - @brief returns a const reverse iterator to one before the first - - Returns a const reverse iterator to the reverse-end; that is, one before - the first element. - - @image html range-rbegin-rend.svg "Illustration from cppreference.com" - - @complexity Constant. - - @requirement This function helps `basic_json` satisfying the - [ReversibleContainer](http://en.cppreference.com/w/cpp/concept/ReversibleContainer) - requirements: - - The complexity is constant. - - Has the semantics of `const_cast(*this).rend()`. - - @liveexample{The following code shows an example for `crend()`.,crend} - - @sa @ref rend() -- returns a reverse iterator to the end - @sa @ref rbegin() -- returns a reverse iterator to the beginning - @sa @ref crbegin() -- returns a const reverse iterator to the beginning - - @since version 1.0.0 - */ - const_reverse_iterator crend() const noexcept - { - return const_reverse_iterator(cbegin()); - } - - private: - // forward declaration - template class iteration_proxy; - - public: - /*! - @brief wrapper to access iterator member functions in range-based for - - This function allows to access @ref iterator::key() and @ref - iterator::value() during range-based for loops. In these loops, a - reference to the JSON values is returned, so there is no access to the - underlying iterator. - - @note The name of this function is not yet final and may change in the - future. - */ - static iteration_proxy iterator_wrapper(reference cont) - { - return iteration_proxy(cont); - } - - /*! - @copydoc iterator_wrapper(reference) - */ - static iteration_proxy iterator_wrapper(const_reference cont) - { - return iteration_proxy(cont); - } - - /// @} - - - ////////////// - // capacity // - ////////////// - - /// @name capacity - /// @{ - - /*! - @brief checks whether the container is empty - - Checks if a JSON value has no elements. - - @return The return value depends on the different types and is - defined as follows: - Value type | return value - ----------- | ------------- - null | `true` - boolean | `false` - string | `false` - number | `false` - object | result of function `object_t::empty()` - array | result of function `array_t::empty()` - - @note This function does not return whether a string stored as JSON value - is empty - it returns whether the JSON container itself is empty which is - false in the case of a string. - - @complexity Constant, as long as @ref array_t and @ref object_t satisfy - the Container concept; that is, their `empty()` functions have constant - complexity. - - @requirement This function helps `basic_json` satisfying the - [Container](http://en.cppreference.com/w/cpp/concept/Container) - requirements: - - The complexity is constant. - - Has the semantics of `begin() == end()`. - - @liveexample{The following code uses `empty()` to check if a JSON - object contains any elements.,empty} - - @sa @ref size() -- returns the number of elements - - @since version 1.0.0 - */ - bool empty() const noexcept - { - switch (m_type) - { - case value_t::null: - { - // null values are empty - return true; - } - - case value_t::array: - { - // delegate call to array_t::empty() - return m_value.array->empty(); - } - - case value_t::object: - { - // delegate call to object_t::empty() - return m_value.object->empty(); - } - - default: - { - // all other types are nonempty - return false; - } - } - } - - /*! - @brief returns the number of elements - - Returns the number of elements in a JSON value. - - @return The return value depends on the different types and is - defined as follows: - Value type | return value - ----------- | ------------- - null | `0` - boolean | `1` - string | `1` - number | `1` - object | result of function object_t::size() - array | result of function array_t::size() - - @note This function does not return the length of a string stored as JSON - value - it returns the number of elements in the JSON value which is 1 in - the case of a string. - - @complexity Constant, as long as @ref array_t and @ref object_t satisfy - the Container concept; that is, their size() functions have constant - complexity. - - @requirement This function helps `basic_json` satisfying the - [Container](http://en.cppreference.com/w/cpp/concept/Container) - requirements: - - The complexity is constant. - - Has the semantics of `std::distance(begin(), end())`. - - @liveexample{The following code calls `size()` on the different value - types.,size} - - @sa @ref empty() -- checks whether the container is empty - @sa @ref max_size() -- returns the maximal number of elements - - @since version 1.0.0 - */ - size_type size() const noexcept - { - switch (m_type) - { - case value_t::null: - { - // null values are empty - return 0; - } - - case value_t::array: - { - // delegate call to array_t::size() - return m_value.array->size(); - } - - case value_t::object: - { - // delegate call to object_t::size() - return m_value.object->size(); - } - - default: - { - // all other types have size 1 - return 1; - } - } - } - - /*! - @brief returns the maximum possible number of elements - - Returns the maximum number of elements a JSON value is able to hold due to - system or library implementation limitations, i.e. `std::distance(begin(), - end())` for the JSON value. - - @return The return value depends on the different types and is - defined as follows: - Value type | return value - ----------- | ------------- - null | `0` (same as `size()`) - boolean | `1` (same as `size()`) - string | `1` (same as `size()`) - number | `1` (same as `size()`) - object | result of function `object_t::max_size()` - array | result of function `array_t::max_size()` - - @complexity Constant, as long as @ref array_t and @ref object_t satisfy - the Container concept; that is, their `max_size()` functions have constant - complexity. - - @requirement This function helps `basic_json` satisfying the - [Container](http://en.cppreference.com/w/cpp/concept/Container) - requirements: - - The complexity is constant. - - Has the semantics of returning `b.size()` where `b` is the largest - possible JSON value. - - @liveexample{The following code calls `max_size()` on the different value - types. Note the output is implementation specific.,max_size} - - @sa @ref size() -- returns the number of elements - - @since version 1.0.0 - */ - size_type max_size() const noexcept - { - switch (m_type) - { - case value_t::array: - { - // delegate call to array_t::max_size() - return m_value.array->max_size(); - } - - case value_t::object: - { - // delegate call to object_t::max_size() - return m_value.object->max_size(); - } - - default: - { - // all other types have max_size() == size() - return size(); - } - } - } - - /// @} - - - /////////////// - // modifiers // - /////////////// - - /// @name modifiers - /// @{ - - /*! - @brief clears the contents - - Clears the content of a JSON value and resets it to the default value as - if @ref basic_json(value_t) would have been called: - - Value type | initial value - ----------- | ------------- - null | `null` - boolean | `false` - string | `""` - number | `0` - object | `{}` - array | `[]` - - @note Floating-point numbers are set to `0.0` which will be serialized to - `0`. The vale type remains @ref number_float_t. - - @complexity Linear in the size of the JSON value. - - @liveexample{The example below shows the effect of `clear()` to different - JSON types.,clear} - - @since version 1.0.0 - */ - void clear() noexcept - { - switch (m_type) - { - case value_t::number_integer: - { - m_value.number_integer = 0; - break; - } - - case value_t::number_unsigned: - { - m_value.number_unsigned = 0; - break; - } - - case value_t::number_float: - { - m_value.number_float = 0.0; - break; - } - - case value_t::boolean: - { - m_value.boolean = false; - break; - } - - case value_t::string: - { - m_value.string->clear(); - break; - } - - case value_t::array: - { - m_value.array->clear(); - break; - } - - case value_t::object: - { - m_value.object->clear(); - break; - } - - default: - { - break; - } - } - } - - /*! - @brief add an object to an array - - Appends the given element @a val to the end of the JSON value. If the - function is called on a JSON null value, an empty array is created before - appending @a val. - - @param[in] val the value to add to the JSON array - - @throw std::domain_error when called on a type other than JSON array or - null; example: `"cannot use push_back() with number"` - - @complexity Amortized constant. - - @liveexample{The example shows how `push_back()` and `+=` can be used to - add elements to a JSON array. Note how the `null` value was silently - converted to a JSON array.,push_back} - - @since version 1.0.0 - */ - void push_back(basic_json&& val) - { - // push_back only works for null objects or arrays - if (not(is_null() or is_array())) - { - throw std::domain_error("cannot use push_back() with " + type_name()); - } - - // transform null object into an array - if (is_null()) - { - m_type = value_t::array; - m_value = value_t::array; - assert_invariant(); - } - - // add element to array (move semantics) - m_value.array->push_back(std::move(val)); - // invalidate object - val.m_type = value_t::null; - } - - /*! - @brief add an object to an array - @copydoc push_back(basic_json&&) - */ - reference operator+=(basic_json&& val) - { - push_back(std::move(val)); - return *this; - } - - /*! - @brief add an object to an array - @copydoc push_back(basic_json&&) - */ - void push_back(const basic_json& val) - { - // push_back only works for null objects or arrays - if (not(is_null() or is_array())) - { - throw std::domain_error("cannot use push_back() with " + type_name()); - } - - // transform null object into an array - if (is_null()) - { - m_type = value_t::array; - m_value = value_t::array; - assert_invariant(); - } - - // add element to array - m_value.array->push_back(val); - } - - /*! - @brief add an object to an array - @copydoc push_back(basic_json&&) - */ - reference operator+=(const basic_json& val) - { - push_back(val); - return *this; - } - - /*! - @brief add an object to an object - - Inserts the given element @a val to the JSON object. If the function is - called on a JSON null value, an empty object is created before inserting - @a val. - - @param[in] val the value to add to the JSON object - - @throw std::domain_error when called on a type other than JSON object or - null; example: `"cannot use push_back() with number"` - - @complexity Logarithmic in the size of the container, O(log(`size()`)). - - @liveexample{The example shows how `push_back()` and `+=` can be used to - add elements to a JSON object. Note how the `null` value was silently - converted to a JSON object.,push_back__object_t__value} - - @since version 1.0.0 - */ - void push_back(const typename object_t::value_type& val) - { - // push_back only works for null objects or objects - if (not(is_null() or is_object())) - { - throw std::domain_error("cannot use push_back() with " + type_name()); - } - - // transform null object into an object - if (is_null()) - { - m_type = value_t::object; - m_value = value_t::object; - assert_invariant(); - } - - // add element to array - m_value.object->insert(val); - } - - /*! - @brief add an object to an object - @copydoc push_back(const typename object_t::value_type&) - */ - reference operator+=(const typename object_t::value_type& val) - { - push_back(val); - return *this; - } - - /*! - @brief add an object to an object - - This function allows to use `push_back` with an initializer list. In case - - 1. the current value is an object, - 2. the initializer list @a init contains only two elements, and - 3. the first element of @a init is a string, - - @a init is converted into an object element and added using - @ref push_back(const typename object_t::value_type&). Otherwise, @a init - is converted to a JSON value and added using @ref push_back(basic_json&&). - - @param init an initializer list - - @complexity Linear in the size of the initializer list @a init. - - @note This function is required to resolve an ambiguous overload error, - because pairs like `{"key", "value"}` can be both interpreted as - `object_t::value_type` or `std::initializer_list`, see - https://github.com/nlohmann/json/issues/235 for more information. - - @liveexample{The example shows how initializer lists are treated as - objects when possible.,push_back__initializer_list} - */ - void push_back(std::initializer_list init) - { - if (is_object() and init.size() == 2 and init.begin()->is_string()) - { - const string_t key = *init.begin(); - push_back(typename object_t::value_type(key, *(init.begin() + 1))); - } - else - { - push_back(basic_json(init)); - } - } - - /*! - @brief add an object to an object - @copydoc push_back(std::initializer_list) - */ - reference operator+=(std::initializer_list init) - { - push_back(init); - return *this; - } - - /*! - @brief inserts element - - Inserts element @a val before iterator @a pos. - - @param[in] pos iterator before which the content will be inserted; may be - the end() iterator - @param[in] val element to insert - @return iterator pointing to the inserted @a val. - - @throw std::domain_error if called on JSON values other than arrays; - example: `"cannot use insert() with string"` - @throw std::domain_error if @a pos is not an iterator of *this; example: - `"iterator does not fit current value"` - - @complexity Constant plus linear in the distance between pos and end of the - container. - - @liveexample{The example shows how `insert()` is used.,insert} - - @since version 1.0.0 - */ - iterator insert(const_iterator pos, const basic_json& val) - { - // insert only works for arrays - if (is_array()) - { - // check if iterator pos fits to this JSON value - if (pos.m_object != this) - { - throw std::domain_error("iterator does not fit current value"); - } - - // insert to array and return iterator - iterator result(this); - result.m_it.array_iterator = m_value.array->insert(pos.m_it.array_iterator, val); - return result; - } - else - { - throw std::domain_error("cannot use insert() with " + type_name()); - } - } - - /*! - @brief inserts element - @copydoc insert(const_iterator, const basic_json&) - */ - iterator insert(const_iterator pos, basic_json&& val) - { - return insert(pos, val); - } - - /*! - @brief inserts elements - - Inserts @a cnt copies of @a val before iterator @a pos. - - @param[in] pos iterator before which the content will be inserted; may be - the end() iterator - @param[in] cnt number of copies of @a val to insert - @param[in] val element to insert - @return iterator pointing to the first element inserted, or @a pos if - `cnt==0` - - @throw std::domain_error if called on JSON values other than arrays; - example: `"cannot use insert() with string"` - @throw std::domain_error if @a pos is not an iterator of *this; example: - `"iterator does not fit current value"` - - @complexity Linear in @a cnt plus linear in the distance between @a pos - and end of the container. - - @liveexample{The example shows how `insert()` is used.,insert__count} - - @since version 1.0.0 - */ - iterator insert(const_iterator pos, size_type cnt, const basic_json& val) - { - // insert only works for arrays - if (is_array()) - { - // check if iterator pos fits to this JSON value - if (pos.m_object != this) - { - throw std::domain_error("iterator does not fit current value"); - } - - // insert to array and return iterator - iterator result(this); - result.m_it.array_iterator = m_value.array->insert(pos.m_it.array_iterator, cnt, val); - return result; - } - else - { - throw std::domain_error("cannot use insert() with " + type_name()); - } - } - - /*! - @brief inserts elements - - Inserts elements from range `[first, last)` before iterator @a pos. - - @param[in] pos iterator before which the content will be inserted; may be - the end() iterator - @param[in] first begin of the range of elements to insert - @param[in] last end of the range of elements to insert - - @throw std::domain_error if called on JSON values other than arrays; - example: `"cannot use insert() with string"` - @throw std::domain_error if @a pos is not an iterator of *this; example: - `"iterator does not fit current value"` - @throw std::domain_error if @a first and @a last do not belong to the same - JSON value; example: `"iterators do not fit"` - @throw std::domain_error if @a first or @a last are iterators into - container for which insert is called; example: `"passed iterators may not - belong to container"` - - @return iterator pointing to the first element inserted, or @a pos if - `first==last` - - @complexity Linear in `std::distance(first, last)` plus linear in the - distance between @a pos and end of the container. - - @liveexample{The example shows how `insert()` is used.,insert__range} - - @since version 1.0.0 - */ - iterator insert(const_iterator pos, const_iterator first, const_iterator last) - { - // insert only works for arrays - if (not is_array()) - { - throw std::domain_error("cannot use insert() with " + type_name()); - } - - // check if iterator pos fits to this JSON value - if (pos.m_object != this) - { - throw std::domain_error("iterator does not fit current value"); - } - - // check if range iterators belong to the same JSON object - if (first.m_object != last.m_object) - { - throw std::domain_error("iterators do not fit"); - } - - if (first.m_object == this or last.m_object == this) - { - throw std::domain_error("passed iterators may not belong to container"); - } - - // insert to array and return iterator - iterator result(this); - result.m_it.array_iterator = m_value.array->insert( - pos.m_it.array_iterator, - first.m_it.array_iterator, - last.m_it.array_iterator); - return result; - } - - /*! - @brief inserts elements - - Inserts elements from initializer list @a ilist before iterator @a pos. - - @param[in] pos iterator before which the content will be inserted; may be - the end() iterator - @param[in] ilist initializer list to insert the values from - - @throw std::domain_error if called on JSON values other than arrays; - example: `"cannot use insert() with string"` - @throw std::domain_error if @a pos is not an iterator of *this; example: - `"iterator does not fit current value"` - - @return iterator pointing to the first element inserted, or @a pos if - `ilist` is empty - - @complexity Linear in `ilist.size()` plus linear in the distance between - @a pos and end of the container. - - @liveexample{The example shows how `insert()` is used.,insert__ilist} - - @since version 1.0.0 - */ - iterator insert(const_iterator pos, std::initializer_list ilist) - { - // insert only works for arrays - if (not is_array()) - { - throw std::domain_error("cannot use insert() with " + type_name()); - } - - // check if iterator pos fits to this JSON value - if (pos.m_object != this) - { - throw std::domain_error("iterator does not fit current value"); - } - - // insert to array and return iterator - iterator result(this); - result.m_it.array_iterator = m_value.array->insert(pos.m_it.array_iterator, ilist); - return result; - } - - /*! - @brief exchanges the values - - Exchanges the contents of the JSON value with those of @a other. Does not - invoke any move, copy, or swap operations on individual elements. All - iterators and references remain valid. The past-the-end iterator is - invalidated. - - @param[in,out] other JSON value to exchange the contents with - - @complexity Constant. - - @liveexample{The example below shows how JSON values can be swapped with - `swap()`.,swap__reference} - - @since version 1.0.0 - */ - void swap(reference other) noexcept ( - std::is_nothrow_move_constructible::value and - std::is_nothrow_move_assignable::value and - std::is_nothrow_move_constructible::value and - std::is_nothrow_move_assignable::value - ) - { - std::swap(m_type, other.m_type); - std::swap(m_value, other.m_value); - assert_invariant(); - } - - /*! - @brief exchanges the values - - Exchanges the contents of a JSON array with those of @a other. Does not - invoke any move, copy, or swap operations on individual elements. All - iterators and references remain valid. The past-the-end iterator is - invalidated. - - @param[in,out] other array to exchange the contents with - - @throw std::domain_error when JSON value is not an array; example: `"cannot - use swap() with string"` - - @complexity Constant. - - @liveexample{The example below shows how arrays can be swapped with - `swap()`.,swap__array_t} - - @since version 1.0.0 - */ - void swap(array_t& other) - { - // swap only works for arrays - if (is_array()) - { - std::swap(*(m_value.array), other); - } - else - { - throw std::domain_error("cannot use swap() with " + type_name()); - } - } - - /*! - @brief exchanges the values - - Exchanges the contents of a JSON object with those of @a other. Does not - invoke any move, copy, or swap operations on individual elements. All - iterators and references remain valid. The past-the-end iterator is - invalidated. - - @param[in,out] other object to exchange the contents with - - @throw std::domain_error when JSON value is not an object; example: - `"cannot use swap() with string"` - - @complexity Constant. - - @liveexample{The example below shows how objects can be swapped with - `swap()`.,swap__object_t} - - @since version 1.0.0 - */ - void swap(object_t& other) - { - // swap only works for objects - if (is_object()) - { - std::swap(*(m_value.object), other); - } - else - { - throw std::domain_error("cannot use swap() with " + type_name()); - } - } - - /*! - @brief exchanges the values - - Exchanges the contents of a JSON string with those of @a other. Does not - invoke any move, copy, or swap operations on individual elements. All - iterators and references remain valid. The past-the-end iterator is - invalidated. - - @param[in,out] other string to exchange the contents with - - @throw std::domain_error when JSON value is not a string; example: `"cannot - use swap() with boolean"` - - @complexity Constant. - - @liveexample{The example below shows how strings can be swapped with - `swap()`.,swap__string_t} - - @since version 1.0.0 - */ - void swap(string_t& other) - { - // swap only works for strings - if (is_string()) - { - std::swap(*(m_value.string), other); - } - else - { - throw std::domain_error("cannot use swap() with " + type_name()); - } - } - - /// @} - - - ////////////////////////////////////////// - // lexicographical comparison operators // - ////////////////////////////////////////// - - /// @name lexicographical comparison operators - /// @{ - - private: - /*! - @brief comparison operator for JSON types - - Returns an ordering that is similar to Python: - - order: null < boolean < number < object < array < string - - furthermore, each type is not smaller than itself - - @since version 1.0.0 - */ - friend bool operator<(const value_t lhs, const value_t rhs) noexcept - { - static constexpr std::array order = {{ - 0, // null - 3, // object - 4, // array - 5, // string - 1, // boolean - 2, // integer - 2, // unsigned - 2, // float - } - }; - - // discarded values are not comparable - if (lhs == value_t::discarded or rhs == value_t::discarded) - { - return false; - } - - return order[static_cast(lhs)] < order[static_cast(rhs)]; - } - - public: - /*! - @brief comparison: equal - - Compares two JSON values for equality according to the following rules: - - Two JSON values are equal if (1) they are from the same type and (2) - their stored values are the same. - - Integer and floating-point numbers are automatically converted before - comparison. Floating-point numbers are compared indirectly: two - floating-point numbers `f1` and `f2` are considered equal if neither - `f1 > f2` nor `f2 > f1` holds. - - Two JSON null values are equal. - - @param[in] lhs first JSON value to consider - @param[in] rhs second JSON value to consider - @return whether the values @a lhs and @a rhs are equal - - @complexity Linear. - - @liveexample{The example demonstrates comparing several JSON - types.,operator__equal} - - @since version 1.0.0 - */ - friend bool operator==(const_reference lhs, const_reference rhs) noexcept - { - const auto lhs_type = lhs.type(); - const auto rhs_type = rhs.type(); - - if (lhs_type == rhs_type) - { - switch (lhs_type) - { - case value_t::array: - { - return *lhs.m_value.array == *rhs.m_value.array; - } - case value_t::object: - { - return *lhs.m_value.object == *rhs.m_value.object; - } - case value_t::null: - { - return true; - } - case value_t::string: - { - return *lhs.m_value.string == *rhs.m_value.string; - } - case value_t::boolean: - { - return lhs.m_value.boolean == rhs.m_value.boolean; - } - case value_t::number_integer: - { - return lhs.m_value.number_integer == rhs.m_value.number_integer; - } - case value_t::number_unsigned: - { - return lhs.m_value.number_unsigned == rhs.m_value.number_unsigned; - } - case value_t::number_float: - { - return lhs.m_value.number_float == rhs.m_value.number_float; - } - default: - { - return false; - } - } - } - else if (lhs_type == value_t::number_integer and rhs_type == value_t::number_float) - { - return static_cast(lhs.m_value.number_integer) == rhs.m_value.number_float; - } - else if (lhs_type == value_t::number_float and rhs_type == value_t::number_integer) - { - return lhs.m_value.number_float == static_cast(rhs.m_value.number_integer); - } - else if (lhs_type == value_t::number_unsigned and rhs_type == value_t::number_float) - { - return static_cast(lhs.m_value.number_unsigned) == rhs.m_value.number_float; - } - else if (lhs_type == value_t::number_float and rhs_type == value_t::number_unsigned) - { - return lhs.m_value.number_float == static_cast(rhs.m_value.number_unsigned); - } - else if (lhs_type == value_t::number_unsigned and rhs_type == value_t::number_integer) - { - return static_cast(lhs.m_value.number_unsigned) == rhs.m_value.number_integer; - } - else if (lhs_type == value_t::number_integer and rhs_type == value_t::number_unsigned) - { - return lhs.m_value.number_integer == static_cast(rhs.m_value.number_unsigned); - } - - return false; - } - - /*! - @brief comparison: equal - - The functions compares the given JSON value against a null pointer. As the - null pointer can be used to initialize a JSON value to null, a comparison - of JSON value @a v with a null pointer should be equivalent to call - `v.is_null()`. - - @param[in] v JSON value to consider - @return whether @a v is null - - @complexity Constant. - - @liveexample{The example compares several JSON types to the null pointer. - ,operator__equal__nullptr_t} - - @since version 1.0.0 - */ - friend bool operator==(const_reference v, std::nullptr_t) noexcept - { - return v.is_null(); - } - - /*! - @brief comparison: equal - @copydoc operator==(const_reference, std::nullptr_t) - */ - friend bool operator==(std::nullptr_t, const_reference v) noexcept - { - return v.is_null(); - } - - /*! - @brief comparison: not equal - - Compares two JSON values for inequality by calculating `not (lhs == rhs)`. - - @param[in] lhs first JSON value to consider - @param[in] rhs second JSON value to consider - @return whether the values @a lhs and @a rhs are not equal - - @complexity Linear. - - @liveexample{The example demonstrates comparing several JSON - types.,operator__notequal} - - @since version 1.0.0 - */ - friend bool operator!=(const_reference lhs, const_reference rhs) noexcept - { - return not (lhs == rhs); - } - - /*! - @brief comparison: not equal - - The functions compares the given JSON value against a null pointer. As the - null pointer can be used to initialize a JSON value to null, a comparison - of JSON value @a v with a null pointer should be equivalent to call - `not v.is_null()`. - - @param[in] v JSON value to consider - @return whether @a v is not null - - @complexity Constant. - - @liveexample{The example compares several JSON types to the null pointer. - ,operator__notequal__nullptr_t} - - @since version 1.0.0 - */ - friend bool operator!=(const_reference v, std::nullptr_t) noexcept - { - return not v.is_null(); - } - - /*! - @brief comparison: not equal - @copydoc operator!=(const_reference, std::nullptr_t) - */ - friend bool operator!=(std::nullptr_t, const_reference v) noexcept - { - return not v.is_null(); - } - - /*! - @brief comparison: less than - - Compares whether one JSON value @a lhs is less than another JSON value @a - rhs according to the following rules: - - If @a lhs and @a rhs have the same type, the values are compared using - the default `<` operator. - - Integer and floating-point numbers are automatically converted before - comparison - - In case @a lhs and @a rhs have different types, the values are ignored - and the order of the types is considered, see - @ref operator<(const value_t, const value_t). - - @param[in] lhs first JSON value to consider - @param[in] rhs second JSON value to consider - @return whether @a lhs is less than @a rhs - - @complexity Linear. - - @liveexample{The example demonstrates comparing several JSON - types.,operator__less} - - @since version 1.0.0 - */ - friend bool operator<(const_reference lhs, const_reference rhs) noexcept - { - const auto lhs_type = lhs.type(); - const auto rhs_type = rhs.type(); - - if (lhs_type == rhs_type) - { - switch (lhs_type) - { - case value_t::array: - { - return *lhs.m_value.array < *rhs.m_value.array; - } - case value_t::object: - { - return *lhs.m_value.object < *rhs.m_value.object; - } - case value_t::null: - { - return false; - } - case value_t::string: - { - return *lhs.m_value.string < *rhs.m_value.string; - } - case value_t::boolean: - { - return lhs.m_value.boolean < rhs.m_value.boolean; - } - case value_t::number_integer: - { - return lhs.m_value.number_integer < rhs.m_value.number_integer; - } - case value_t::number_unsigned: - { - return lhs.m_value.number_unsigned < rhs.m_value.number_unsigned; - } - case value_t::number_float: - { - return lhs.m_value.number_float < rhs.m_value.number_float; - } - default: - { - return false; - } - } - } - else if (lhs_type == value_t::number_integer and rhs_type == value_t::number_float) - { - return static_cast(lhs.m_value.number_integer) < rhs.m_value.number_float; - } - else if (lhs_type == value_t::number_float and rhs_type == value_t::number_integer) - { - return lhs.m_value.number_float < static_cast(rhs.m_value.number_integer); - } - else if (lhs_type == value_t::number_unsigned and rhs_type == value_t::number_float) - { - return static_cast(lhs.m_value.number_unsigned) < rhs.m_value.number_float; - } - else if (lhs_type == value_t::number_float and rhs_type == value_t::number_unsigned) - { - return lhs.m_value.number_float < static_cast(rhs.m_value.number_unsigned); - } - else if (lhs_type == value_t::number_integer and rhs_type == value_t::number_unsigned) - { - return lhs.m_value.number_integer < static_cast(rhs.m_value.number_unsigned); - } - else if (lhs_type == value_t::number_unsigned and rhs_type == value_t::number_integer) - { - return static_cast(lhs.m_value.number_unsigned) < rhs.m_value.number_integer; - } - - // We only reach this line if we cannot compare values. In that case, - // we compare types. Note we have to call the operator explicitly, - // because MSVC has problems otherwise. - return operator<(lhs_type, rhs_type); - } - - /*! - @brief comparison: less than or equal - - Compares whether one JSON value @a lhs is less than or equal to another - JSON value by calculating `not (rhs < lhs)`. - - @param[in] lhs first JSON value to consider - @param[in] rhs second JSON value to consider - @return whether @a lhs is less than or equal to @a rhs - - @complexity Linear. - - @liveexample{The example demonstrates comparing several JSON - types.,operator__greater} - - @since version 1.0.0 - */ - friend bool operator<=(const_reference lhs, const_reference rhs) noexcept - { - return not (rhs < lhs); - } - - /*! - @brief comparison: greater than - - Compares whether one JSON value @a lhs is greater than another - JSON value by calculating `not (lhs <= rhs)`. - - @param[in] lhs first JSON value to consider - @param[in] rhs second JSON value to consider - @return whether @a lhs is greater than to @a rhs - - @complexity Linear. - - @liveexample{The example demonstrates comparing several JSON - types.,operator__lessequal} - - @since version 1.0.0 - */ - friend bool operator>(const_reference lhs, const_reference rhs) noexcept - { - return not (lhs <= rhs); - } - - /*! - @brief comparison: greater than or equal - - Compares whether one JSON value @a lhs is greater than or equal to another - JSON value by calculating `not (lhs < rhs)`. - - @param[in] lhs first JSON value to consider - @param[in] rhs second JSON value to consider - @return whether @a lhs is greater than or equal to @a rhs - - @complexity Linear. - - @liveexample{The example demonstrates comparing several JSON - types.,operator__greaterequal} - - @since version 1.0.0 - */ - friend bool operator>=(const_reference lhs, const_reference rhs) noexcept - { - return not (lhs < rhs); - } - - /// @} - - - /////////////////// - // serialization // - /////////////////// - - /// @name serialization - /// @{ - - /*! - @brief serialize to stream - - Serialize the given JSON value @a j to the output stream @a o. The JSON - value will be serialized using the @ref dump member function. The - indentation of the output can be controlled with the member variable - `width` of the output stream @a o. For instance, using the manipulator - `std::setw(4)` on @a o sets the indentation level to `4` and the - serialization result is the same as calling `dump(4)`. - - @note During serializaion, the locale and the precision of the output - stream @a o are changed. The original values are restored when the - function returns. - - @param[in,out] o stream to serialize to - @param[in] j JSON value to serialize - - @return the stream @a o - - @complexity Linear. - - @liveexample{The example below shows the serialization with different - parameters to `width` to adjust the indentation level.,operator_serialize} - - @since version 1.0.0 - */ - friend std::ostream& operator<<(std::ostream& o, const basic_json& j) - { - // read width member and use it as indentation parameter if nonzero - const bool pretty_print = (o.width() > 0); - const auto indentation = (pretty_print ? o.width() : 0); - - // reset width to 0 for subsequent calls to this stream - o.width(0); - - // fix locale problems - const auto old_locale = o.imbue(std::locale(std::locale(), new DecimalSeparator)); - // set precision - - // 6, 15 or 16 digits of precision allows round-trip IEEE 754 - // string->float->string, string->double->string or string->long - // double->string; to be safe, we read this value from - // std::numeric_limits::digits10 - const auto old_precision = o.precision(std::numeric_limits::digits10); - - // do the actual serialization - j.dump(o, pretty_print, static_cast(indentation)); - - // reset locale and precision - o.imbue(old_locale); - o.precision(old_precision); - return o; - } - - /*! - @brief serialize to stream - @copydoc operator<<(std::ostream&, const basic_json&) - */ - friend std::ostream& operator>>(const basic_json& j, std::ostream& o) - { - return o << j; - } - - /// @} - - - ///////////////////// - // deserialization // - ///////////////////// - - /// @name deserialization - /// @{ - - /*! - @brief deserialize from an array - - This function reads from an array of 1-byte values. - - @pre Each element of the container has a size of 1 byte. Violating this - precondition yields undefined behavior. **This precondition is enforced - with a static assertion.** - - @param[in] array array to read from - @param[in] cb a parser callback function of type @ref parser_callback_t - which is used to control the deserialization by filtering unwanted values - (optional) - - @return result of the deserialization - - @complexity Linear in the length of the input. The parser is a predictive - LL(1) parser. The complexity can be higher if the parser callback function - @a cb has a super-linear complexity. - - @note A UTF-8 byte order mark is silently ignored. - - @liveexample{The example below demonstrates the `parse()` function reading - from an array.,parse__array__parser_callback_t} - - @since version 2.0.3 - */ - template - static basic_json parse(T (&array)[N], - const parser_callback_t cb = nullptr) - { - // delegate the call to the iterator-range parse overload - return parse(std::begin(array), std::end(array), cb); - } - - /*! - @brief deserialize from string literal - - @tparam CharT character/literal type with size of 1 byte - @param[in] s string literal to read a serialized JSON value from - @param[in] cb a parser callback function of type @ref parser_callback_t - which is used to control the deserialization by filtering unwanted values - (optional) - - @return result of the deserialization - - @complexity Linear in the length of the input. The parser is a predictive - LL(1) parser. The complexity can be higher if the parser callback function - @a cb has a super-linear complexity. - - @note A UTF-8 byte order mark is silently ignored. - @note String containers like `std::string` or @ref string_t can be parsed - with @ref parse(const ContiguousContainer&, const parser_callback_t) - - @liveexample{The example below demonstrates the `parse()` function with - and without callback function.,parse__string__parser_callback_t} - - @sa @ref parse(std::istream&, const parser_callback_t) for a version that - reads from an input stream - - @since version 1.0.0 (originally for @ref string_t) - */ - template::value and - std::is_integral::type>::value and - sizeof(typename std::remove_pointer::type) == 1, int>::type = 0> - static basic_json parse(const CharPT s, - const parser_callback_t cb = nullptr) - { - return parser(reinterpret_cast(s), cb).parse(); - } - - /*! - @brief deserialize from stream - - @param[in,out] i stream to read a serialized JSON value from - @param[in] cb a parser callback function of type @ref parser_callback_t - which is used to control the deserialization by filtering unwanted values - (optional) - - @return result of the deserialization - - @complexity Linear in the length of the input. The parser is a predictive - LL(1) parser. The complexity can be higher if the parser callback function - @a cb has a super-linear complexity. - - @note A UTF-8 byte order mark is silently ignored. - - @liveexample{The example below demonstrates the `parse()` function with - and without callback function.,parse__istream__parser_callback_t} - - @sa @ref parse(const char*, const parser_callback_t) for a version - that reads from a string - - @since version 1.0.0 - */ - static basic_json parse(std::istream& i, - const parser_callback_t cb = nullptr) - { - return parser(i, cb).parse(); - } - - /*! - @copydoc parse(std::istream&, const parser_callback_t) - */ - static basic_json parse(std::istream&& i, - const parser_callback_t cb = nullptr) - { - return parser(i, cb).parse(); - } - - /*! - @brief deserialize from an iterator range with contiguous storage - - This function reads from an iterator range of a container with contiguous - storage of 1-byte values. Compatible container types include - `std::vector`, `std::string`, `std::array`, `std::valarray`, and - `std::initializer_list`. Furthermore, C-style arrays can be used with - `std::begin()`/`std::end()`. User-defined containers can be used as long - as they implement random-access iterators and a contiguous storage. - - @pre The iterator range is contiguous. Violating this precondition yields - undefined behavior. **This precondition is enforced with an assertion.** - @pre Each element in the range has a size of 1 byte. Violating this - precondition yields undefined behavior. **This precondition is enforced - with a static assertion.** - - @warning There is no way to enforce all preconditions at compile-time. If - the function is called with noncompliant iterators and with - assertions switched off, the behavior is undefined and will most - likely yield segmentation violation. - - @tparam IteratorType iterator of container with contiguous storage - @param[in] first begin of the range to parse (included) - @param[in] last end of the range to parse (excluded) - @param[in] cb a parser callback function of type @ref parser_callback_t - which is used to control the deserialization by filtering unwanted values - (optional) - - @return result of the deserialization - - @complexity Linear in the length of the input. The parser is a predictive - LL(1) parser. The complexity can be higher if the parser callback function - @a cb has a super-linear complexity. - - @note A UTF-8 byte order mark is silently ignored. - - @liveexample{The example below demonstrates the `parse()` function reading - from an iterator range.,parse__iteratortype__parser_callback_t} - - @since version 2.0.3 - */ - template::iterator_category>::value, int>::type = 0> - static basic_json parse(IteratorType first, IteratorType last, - const parser_callback_t cb = nullptr) - { - // assertion to check that the iterator range is indeed contiguous, - // see http://stackoverflow.com/a/35008842/266378 for more discussion - assert(std::accumulate(first, last, std::make_pair(true, 0), - [&first](std::pair res, decltype(*first) val) - { - res.first &= (val == *(std::next(std::addressof(*first), res.second++))); - return res; - }).first); - - // assertion to check that each element is 1 byte long - static_assert(sizeof(typename std::iterator_traits::value_type) == 1, - "each element in the iterator range must have the size of 1 byte"); - - // if iterator range is empty, create a parser with an empty string - // to generate "unexpected EOF" error message - if (std::distance(first, last) <= 0) - { - return parser("").parse(); - } - - return parser(first, last, cb).parse(); - } - - /*! - @brief deserialize from a container with contiguous storage - - This function reads from a container with contiguous storage of 1-byte - values. Compatible container types include `std::vector`, `std::string`, - `std::array`, and `std::initializer_list`. User-defined containers can be - used as long as they implement random-access iterators and a contiguous - storage. - - @pre The container storage is contiguous. Violating this precondition - yields undefined behavior. **This precondition is enforced with an - assertion.** - @pre Each element of the container has a size of 1 byte. Violating this - precondition yields undefined behavior. **This precondition is enforced - with a static assertion.** - - @warning There is no way to enforce all preconditions at compile-time. If - the function is called with a noncompliant container and with - assertions switched off, the behavior is undefined and will most - likely yield segmentation violation. - - @tparam ContiguousContainer container type with contiguous storage - @param[in] c container to read from - @param[in] cb a parser callback function of type @ref parser_callback_t - which is used to control the deserialization by filtering unwanted values - (optional) - - @return result of the deserialization - - @complexity Linear in the length of the input. The parser is a predictive - LL(1) parser. The complexity can be higher if the parser callback function - @a cb has a super-linear complexity. - - @note A UTF-8 byte order mark is silently ignored. - - @liveexample{The example below demonstrates the `parse()` function reading - from a contiguous container.,parse__contiguouscontainer__parser_callback_t} - - @since version 2.0.3 - */ - template::value and - std::is_base_of< - std::random_access_iterator_tag, - typename std::iterator_traits()))>::iterator_category>::value - , int>::type = 0> - static basic_json parse(const ContiguousContainer& c, - const parser_callback_t cb = nullptr) - { - // delegate the call to the iterator-range parse overload - return parse(std::begin(c), std::end(c), cb); - } - - /*! - @brief deserialize from stream - - Deserializes an input stream to a JSON value. - - @param[in,out] i input stream to read a serialized JSON value from - @param[in,out] j JSON value to write the deserialized input to - - @throw std::invalid_argument in case of parse errors - - @complexity Linear in the length of the input. The parser is a predictive - LL(1) parser. - - @note A UTF-8 byte order mark is silently ignored. - - @liveexample{The example below shows how a JSON value is constructed by - reading a serialization from a stream.,operator_deserialize} - - @sa parse(std::istream&, const parser_callback_t) for a variant with a - parser callback function to filter values while parsing - - @since version 1.0.0 - */ - friend std::istream& operator<<(basic_json& j, std::istream& i) - { - j = parser(i).parse(); - return i; - } - - /*! - @brief deserialize from stream - @copydoc operator<<(basic_json&, std::istream&) - */ - friend std::istream& operator>>(std::istream& i, basic_json& j) - { - j = parser(i).parse(); - return i; - } - - /// @} - - - private: - /////////////////////////// - // convenience functions // - /////////////////////////// - - /*! - @brief return the type as string - - Returns the type name as string to be used in error messages - usually to - indicate that a function was called on a wrong JSON type. - - @return basically a string representation of a the @a m_type member - - @complexity Constant. - - @since version 1.0.0 - */ - std::string type_name() const - { - switch (m_type) - { - case value_t::null: - return "null"; - case value_t::object: - return "object"; - case value_t::array: - return "array"; - case value_t::string: - return "string"; - case value_t::boolean: - return "boolean"; - case value_t::discarded: - return "discarded"; - default: - return "number"; - } - } - - /*! - @brief calculates the extra space to escape a JSON string - - @param[in] s the string to escape - @return the number of characters required to escape string @a s - - @complexity Linear in the length of string @a s. - */ - static std::size_t extra_space(const string_t& s) noexcept - { - return std::accumulate(s.begin(), s.end(), size_t{}, - [](size_t res, typename string_t::value_type c) - { - switch (c) - { - case '"': - case '\\': - case '\b': - case '\f': - case '\n': - case '\r': - case '\t': - { - // from c (1 byte) to \x (2 bytes) - return res + 1; - } - - default: - { - if (c >= 0x00 and c <= 0x1f) - { - // from c (1 byte) to \uxxxx (6 bytes) - return res + 5; - } - else - { - return res; - } - } - } - }); - } - - /*! - @brief escape a string - - Escape a string by replacing certain special characters by a sequence of - an escape character (backslash) and another character and other control - characters by a sequence of "\u" followed by a four-digit hex - representation. - - @param[in] s the string to escape - @return the escaped string - - @complexity Linear in the length of string @a s. - */ - static string_t escape_string(const string_t& s) - { - const auto space = extra_space(s); - if (space == 0) - { - return s; - } - - // create a result string of necessary size - string_t result(s.size() + space, '\\'); - std::size_t pos = 0; - - for (const auto& c : s) - { - switch (c) - { - // quotation mark (0x22) - case '"': - { - result[pos + 1] = '"'; - pos += 2; - break; - } - - // reverse solidus (0x5c) - case '\\': - { - // nothing to change - pos += 2; - break; - } - - // backspace (0x08) - case '\b': - { - result[pos + 1] = 'b'; - pos += 2; - break; - } - - // formfeed (0x0c) - case '\f': - { - result[pos + 1] = 'f'; - pos += 2; - break; - } - - // newline (0x0a) - case '\n': - { - result[pos + 1] = 'n'; - pos += 2; - break; - } - - // carriage return (0x0d) - case '\r': - { - result[pos + 1] = 'r'; - pos += 2; - break; - } - - // horizontal tab (0x09) - case '\t': - { - result[pos + 1] = 't'; - pos += 2; - break; - } - - default: - { - if (c >= 0x00 and c <= 0x1f) - { - // convert a number 0..15 to its hex representation - // (0..f) - static const char hexify[16] = - { - '0', '1', '2', '3', '4', '5', '6', '7', - '8', '9', 'a', 'b', 'c', 'd', 'e', 'f' - }; - - // print character c as \uxxxx - for (const char m : - { 'u', '0', '0', hexify[c >> 4], hexify[c & 0x0f] - }) - { - result[++pos] = m; - } - - ++pos; - } - else - { - // all other characters are added as-is - result[pos++] = c; - } - break; - } - } - } - - return result; - } - - /*! - @brief internal implementation of the serialization function - - This function is called by the public member function dump and organizes - the serialization internally. The indentation level is propagated as - additional parameter. In case of arrays and objects, the function is - called recursively. Note that - - - strings and object keys are escaped using `escape_string()` - - integer numbers are converted implicitly via `operator<<` - - floating-point numbers are converted to a string using `"%g"` format - - @param[out] o stream to write to - @param[in] pretty_print whether the output shall be pretty-printed - @param[in] indent_step the indent level - @param[in] current_indent the current indent level (only used internally) - */ - void dump(std::ostream& o, - const bool pretty_print, - const unsigned int indent_step, - const unsigned int current_indent = 0) const - { - // variable to hold indentation for recursive calls - unsigned int new_indent = current_indent; - - switch (m_type) - { - case value_t::object: - { - if (m_value.object->empty()) - { - o << "{}"; - return; - } - - o << "{"; - - // increase indentation - if (pretty_print) - { - new_indent += indent_step; - o << "\n"; - } - - for (auto i = m_value.object->cbegin(); i != m_value.object->cend(); ++i) - { - if (i != m_value.object->cbegin()) - { - o << (pretty_print ? ",\n" : ","); - } - o << string_t(new_indent, ' ') << "\"" - << escape_string(i->first) << "\":" - << (pretty_print ? " " : ""); - i->second.dump(o, pretty_print, indent_step, new_indent); - } - - // decrease indentation - if (pretty_print) - { - new_indent -= indent_step; - o << "\n"; - } - - o << string_t(new_indent, ' ') + "}"; - return; - } - - case value_t::array: - { - if (m_value.array->empty()) - { - o << "[]"; - return; - } - - o << "["; - - // increase indentation - if (pretty_print) - { - new_indent += indent_step; - o << "\n"; - } - - for (auto i = m_value.array->cbegin(); i != m_value.array->cend(); ++i) - { - if (i != m_value.array->cbegin()) - { - o << (pretty_print ? ",\n" : ","); - } - o << string_t(new_indent, ' '); - i->dump(o, pretty_print, indent_step, new_indent); - } - - // decrease indentation - if (pretty_print) - { - new_indent -= indent_step; - o << "\n"; - } - - o << string_t(new_indent, ' ') << "]"; - return; - } - - case value_t::string: - { - o << string_t("\"") << escape_string(*m_value.string) << "\""; - return; - } - - case value_t::boolean: - { - o << (m_value.boolean ? "true" : "false"); - return; - } - - case value_t::number_integer: - { - o << m_value.number_integer; - return; - } - - case value_t::number_unsigned: - { - o << m_value.number_unsigned; - return; - } - - case value_t::number_float: - { - if (m_value.number_float == 0) - { - // special case for zero to get "0.0"/"-0.0" - o << (std::signbit(m_value.number_float) ? "-0.0" : "0.0"); - } - else - { - o << m_value.number_float; - } - return; - } - - case value_t::discarded: - { - o << ""; - return; - } - - case value_t::null: - { - o << "null"; - return; - } - } - } - - private: - ////////////////////// - // member variables // - ////////////////////// - - /// the type of the current element - value_t m_type = value_t::null; - - /// the value of the current element - json_value m_value = {}; - - - private: - /////////////// - // iterators // - /////////////// - - /*! - @brief an iterator for primitive JSON types - - This class models an iterator for primitive JSON types (boolean, number, - string). It's only purpose is to allow the iterator/const_iterator classes - to "iterate" over primitive values. Internally, the iterator is modeled by - a `difference_type` variable. Value begin_value (`0`) models the begin, - end_value (`1`) models past the end. - */ - class primitive_iterator_t - { - public: - /// set iterator to a defined beginning - void set_begin() noexcept - { - m_it = begin_value; - } - - /// set iterator to a defined past the end - void set_end() noexcept - { - m_it = end_value; - } - - /// return whether the iterator can be dereferenced - constexpr bool is_begin() const noexcept - { - return (m_it == begin_value); - } - - /// return whether the iterator is at end - constexpr bool is_end() const noexcept - { - return (m_it == end_value); - } - - /// return reference to the value to change and compare - operator difference_type& () noexcept - { - return m_it; - } - - /// return value to compare - constexpr operator difference_type () const noexcept - { - return m_it; - } - - private: - static constexpr difference_type begin_value = 0; - static constexpr difference_type end_value = begin_value + 1; - - /// iterator as signed integer type - difference_type m_it = std::numeric_limits::denorm_min(); - }; - - /*! - @brief an iterator value - - @note This structure could easily be a union, but MSVC currently does not - allow unions members with complex constructors, see - https://github.com/nlohmann/json/pull/105. - */ - struct internal_iterator - { - /// iterator for JSON objects - typename object_t::iterator object_iterator; - /// iterator for JSON arrays - typename array_t::iterator array_iterator; - /// generic iterator for all other types - primitive_iterator_t primitive_iterator; - - /// create an uninitialized internal_iterator - internal_iterator() noexcept - : object_iterator(), array_iterator(), primitive_iterator() - {} - }; - - /// proxy class for the iterator_wrapper functions - template - class iteration_proxy - { - private: - /// helper class for iteration - class iteration_proxy_internal - { - private: - /// the iterator - IteratorType anchor; - /// an index for arrays (used to create key names) - size_t array_index = 0; - - public: - explicit iteration_proxy_internal(IteratorType it) noexcept - : anchor(it) - {} - - /// dereference operator (needed for range-based for) - iteration_proxy_internal& operator*() - { - return *this; - } - - /// increment operator (needed for range-based for) - iteration_proxy_internal& operator++() - { - ++anchor; - ++array_index; - - return *this; - } - - /// inequality operator (needed for range-based for) - bool operator!= (const iteration_proxy_internal& o) const - { - return anchor != o.anchor; - } - - /// return key of the iterator - typename basic_json::string_t key() const - { - assert(anchor.m_object != nullptr); - - switch (anchor.m_object->type()) - { - // use integer array index as key - case value_t::array: - { - return std::to_string(array_index); - } - - // use key from the object - case value_t::object: - { - return anchor.key(); - } - - // use an empty key for all primitive types - default: - { - return ""; - } - } - } - - /// return value of the iterator - typename IteratorType::reference value() const - { - return anchor.value(); - } - }; - - /// the container to iterate - typename IteratorType::reference container; - - public: - /// construct iteration proxy from a container - explicit iteration_proxy(typename IteratorType::reference cont) - : container(cont) - {} - - /// return iterator begin (needed for range-based for) - iteration_proxy_internal begin() noexcept - { - return iteration_proxy_internal(container.begin()); - } - - /// return iterator end (needed for range-based for) - iteration_proxy_internal end() noexcept - { - return iteration_proxy_internal(container.end()); - } - }; - - public: - /*! - @brief a const random access iterator for the @ref basic_json class - - This class implements a const iterator for the @ref basic_json class. From - this class, the @ref iterator class is derived. - - @note An iterator is called *initialized* when a pointer to a JSON value - has been set (e.g., by a constructor or a copy assignment). If the - iterator is default-constructed, it is *uninitialized* and most - methods are undefined. **The library uses assertions to detect calls - on uninitialized iterators.** - - @requirement The class satisfies the following concept requirements: - - [RandomAccessIterator](http://en.cppreference.com/w/cpp/concept/RandomAccessIterator): - The iterator that can be moved to point (forward and backward) to any - element in constant time. - - @since version 1.0.0 - */ - class const_iterator : public std::iterator - { - /// allow basic_json to access private members - friend class basic_json; - - public: - /// the type of the values when the iterator is dereferenced - using value_type = typename basic_json::value_type; - /// a type to represent differences between iterators - using difference_type = typename basic_json::difference_type; - /// defines a pointer to the type iterated over (value_type) - using pointer = typename basic_json::const_pointer; - /// defines a reference to the type iterated over (value_type) - using reference = typename basic_json::const_reference; - /// the category of the iterator - using iterator_category = std::bidirectional_iterator_tag; - - /// default constructor - const_iterator() = default; - - /*! - @brief constructor for a given JSON instance - @param[in] object pointer to a JSON object for this iterator - @pre object != nullptr - @post The iterator is initialized; i.e. `m_object != nullptr`. - */ - explicit const_iterator(pointer object) noexcept - : m_object(object) - { - assert(m_object != nullptr); - - switch (m_object->m_type) - { - case basic_json::value_t::object: - { - m_it.object_iterator = typename object_t::iterator(); - break; - } - - case basic_json::value_t::array: - { - m_it.array_iterator = typename array_t::iterator(); - break; - } - - default: - { - m_it.primitive_iterator = primitive_iterator_t(); - break; - } - } - } - - /*! - @brief copy constructor given a non-const iterator - @param[in] other iterator to copy from - @note It is not checked whether @a other is initialized. - */ - explicit const_iterator(const iterator& other) noexcept - : m_object(other.m_object) - { - if (m_object != nullptr) - { - switch (m_object->m_type) - { - case basic_json::value_t::object: - { - m_it.object_iterator = other.m_it.object_iterator; - break; - } - - case basic_json::value_t::array: - { - m_it.array_iterator = other.m_it.array_iterator; - break; - } - - default: - { - m_it.primitive_iterator = other.m_it.primitive_iterator; - break; - } - } - } - } - - /*! - @brief copy constructor - @param[in] other iterator to copy from - @note It is not checked whether @a other is initialized. - */ - const_iterator(const const_iterator& other) noexcept - : m_object(other.m_object), m_it(other.m_it) - {} - - /*! - @brief copy assignment - @param[in,out] other iterator to copy from - @note It is not checked whether @a other is initialized. - */ - const_iterator& operator=(const_iterator other) noexcept( - std::is_nothrow_move_constructible::value and - std::is_nothrow_move_assignable::value and - std::is_nothrow_move_constructible::value and - std::is_nothrow_move_assignable::value - ) - { - std::swap(m_object, other.m_object); - std::swap(m_it, other.m_it); - return *this; - } - - private: - /*! - @brief set the iterator to the first value - @pre The iterator is initialized; i.e. `m_object != nullptr`. - */ - void set_begin() noexcept - { - assert(m_object != nullptr); - - switch (m_object->m_type) - { - case basic_json::value_t::object: - { - m_it.object_iterator = m_object->m_value.object->begin(); - break; - } - - case basic_json::value_t::array: - { - m_it.array_iterator = m_object->m_value.array->begin(); - break; - } - - case basic_json::value_t::null: - { - // set to end so begin()==end() is true: null is empty - m_it.primitive_iterator.set_end(); - break; - } - - default: - { - m_it.primitive_iterator.set_begin(); - break; - } - } - } - - /*! - @brief set the iterator past the last value - @pre The iterator is initialized; i.e. `m_object != nullptr`. - */ - void set_end() noexcept - { - assert(m_object != nullptr); - - switch (m_object->m_type) - { - case basic_json::value_t::object: - { - m_it.object_iterator = m_object->m_value.object->end(); - break; - } - - case basic_json::value_t::array: - { - m_it.array_iterator = m_object->m_value.array->end(); - break; - } - - default: - { - m_it.primitive_iterator.set_end(); - break; - } - } - } - - public: - /*! - @brief return a reference to the value pointed to by the iterator - @pre The iterator is initialized; i.e. `m_object != nullptr`. - */ - reference operator*() const - { - assert(m_object != nullptr); - - switch (m_object->m_type) - { - case basic_json::value_t::object: - { - assert(m_it.object_iterator != m_object->m_value.object->end()); - return m_it.object_iterator->second; - } - - case basic_json::value_t::array: - { - assert(m_it.array_iterator != m_object->m_value.array->end()); - return *m_it.array_iterator; - } - - case basic_json::value_t::null: - { - throw std::out_of_range("cannot get value"); - } - - default: - { - if (m_it.primitive_iterator.is_begin()) - { - return *m_object; - } - else - { - throw std::out_of_range("cannot get value"); - } - } - } - } - - /*! - @brief dereference the iterator - @pre The iterator is initialized; i.e. `m_object != nullptr`. - */ - pointer operator->() const - { - assert(m_object != nullptr); - - switch (m_object->m_type) - { - case basic_json::value_t::object: - { - assert(m_it.object_iterator != m_object->m_value.object->end()); - return &(m_it.object_iterator->second); - } - - case basic_json::value_t::array: - { - assert(m_it.array_iterator != m_object->m_value.array->end()); - return &*m_it.array_iterator; - } - - default: - { - if (m_it.primitive_iterator.is_begin()) - { - return m_object; - } - else - { - throw std::out_of_range("cannot get value"); - } - } - } - } - - /*! - @brief post-increment (it++) - @pre The iterator is initialized; i.e. `m_object != nullptr`. - */ - const_iterator operator++(int) - { - auto result = *this; - ++(*this); - return result; - } - - /*! - @brief pre-increment (++it) - @pre The iterator is initialized; i.e. `m_object != nullptr`. - */ - const_iterator& operator++() - { - assert(m_object != nullptr); - - switch (m_object->m_type) - { - case basic_json::value_t::object: - { - std::advance(m_it.object_iterator, 1); - break; - } - - case basic_json::value_t::array: - { - std::advance(m_it.array_iterator, 1); - break; - } - - default: - { - ++m_it.primitive_iterator; - break; - } - } - - return *this; - } - - /*! - @brief post-decrement (it--) - @pre The iterator is initialized; i.e. `m_object != nullptr`. - */ - const_iterator operator--(int) - { - auto result = *this; - --(*this); - return result; - } - - /*! - @brief pre-decrement (--it) - @pre The iterator is initialized; i.e. `m_object != nullptr`. - */ - const_iterator& operator--() - { - assert(m_object != nullptr); - - switch (m_object->m_type) - { - case basic_json::value_t::object: - { - std::advance(m_it.object_iterator, -1); - break; - } - - case basic_json::value_t::array: - { - std::advance(m_it.array_iterator, -1); - break; - } - - default: - { - --m_it.primitive_iterator; - break; - } - } - - return *this; - } - - /*! - @brief comparison: equal - @pre The iterator is initialized; i.e. `m_object != nullptr`. - */ - bool operator==(const const_iterator& other) const - { - // if objects are not the same, the comparison is undefined - if (m_object != other.m_object) - { - throw std::domain_error("cannot compare iterators of different containers"); - } - - assert(m_object != nullptr); - - switch (m_object->m_type) - { - case basic_json::value_t::object: - { - return (m_it.object_iterator == other.m_it.object_iterator); - } - - case basic_json::value_t::array: - { - return (m_it.array_iterator == other.m_it.array_iterator); - } - - default: - { - return (m_it.primitive_iterator == other.m_it.primitive_iterator); - } - } - } - - /*! - @brief comparison: not equal - @pre The iterator is initialized; i.e. `m_object != nullptr`. - */ - bool operator!=(const const_iterator& other) const - { - return not operator==(other); - } - - /*! - @brief comparison: smaller - @pre The iterator is initialized; i.e. `m_object != nullptr`. - */ - bool operator<(const const_iterator& other) const - { - // if objects are not the same, the comparison is undefined - if (m_object != other.m_object) - { - throw std::domain_error("cannot compare iterators of different containers"); - } - - assert(m_object != nullptr); - - switch (m_object->m_type) - { - case basic_json::value_t::object: - { - throw std::domain_error("cannot compare order of object iterators"); - } - - case basic_json::value_t::array: - { - return (m_it.array_iterator < other.m_it.array_iterator); - } - - default: - { - return (m_it.primitive_iterator < other.m_it.primitive_iterator); - } - } - } - - /*! - @brief comparison: less than or equal - @pre The iterator is initialized; i.e. `m_object != nullptr`. - */ - bool operator<=(const const_iterator& other) const - { - return not other.operator < (*this); - } - - /*! - @brief comparison: greater than - @pre The iterator is initialized; i.e. `m_object != nullptr`. - */ - bool operator>(const const_iterator& other) const - { - return not operator<=(other); - } - - /*! - @brief comparison: greater than or equal - @pre The iterator is initialized; i.e. `m_object != nullptr`. - */ - bool operator>=(const const_iterator& other) const - { - return not operator<(other); - } - - /*! - @brief add to iterator - @pre The iterator is initialized; i.e. `m_object != nullptr`. - */ - const_iterator& operator+=(difference_type i) - { - assert(m_object != nullptr); - - switch (m_object->m_type) - { - case basic_json::value_t::object: - { - throw std::domain_error("cannot use offsets with object iterators"); - } - - case basic_json::value_t::array: - { - std::advance(m_it.array_iterator, i); - break; - } - - default: - { - m_it.primitive_iterator += i; - break; - } - } - - return *this; - } - - /*! - @brief subtract from iterator - @pre The iterator is initialized; i.e. `m_object != nullptr`. - */ - const_iterator& operator-=(difference_type i) - { - return operator+=(-i); - } - - /*! - @brief add to iterator - @pre The iterator is initialized; i.e. `m_object != nullptr`. - */ - const_iterator operator+(difference_type i) - { - auto result = *this; - result += i; - return result; - } - - /*! - @brief subtract from iterator - @pre The iterator is initialized; i.e. `m_object != nullptr`. - */ - const_iterator operator-(difference_type i) - { - auto result = *this; - result -= i; - return result; - } - - /*! - @brief return difference - @pre The iterator is initialized; i.e. `m_object != nullptr`. - */ - difference_type operator-(const const_iterator& other) const - { - assert(m_object != nullptr); - - switch (m_object->m_type) - { - case basic_json::value_t::object: - { - throw std::domain_error("cannot use offsets with object iterators"); - } - - case basic_json::value_t::array: - { - return m_it.array_iterator - other.m_it.array_iterator; - } - - default: - { - return m_it.primitive_iterator - other.m_it.primitive_iterator; - } - } - } - - /*! - @brief access to successor - @pre The iterator is initialized; i.e. `m_object != nullptr`. - */ - reference operator[](difference_type n) const - { - assert(m_object != nullptr); - - switch (m_object->m_type) - { - case basic_json::value_t::object: - { - throw std::domain_error("cannot use operator[] for object iterators"); - } - - case basic_json::value_t::array: - { - return *std::next(m_it.array_iterator, n); - } - - case basic_json::value_t::null: - { - throw std::out_of_range("cannot get value"); - } - - default: - { - if (m_it.primitive_iterator == -n) - { - return *m_object; - } - else - { - throw std::out_of_range("cannot get value"); - } - } - } - } - - /*! - @brief return the key of an object iterator - @pre The iterator is initialized; i.e. `m_object != nullptr`. - */ - typename object_t::key_type key() const - { - assert(m_object != nullptr); - - if (m_object->is_object()) - { - return m_it.object_iterator->first; - } - else - { - throw std::domain_error("cannot use key() for non-object iterators"); - } - } - - /*! - @brief return the value of an iterator - @pre The iterator is initialized; i.e. `m_object != nullptr`. - */ - reference value() const - { - return operator*(); - } - - private: - /// associated JSON instance - pointer m_object = nullptr; - /// the actual iterator of the associated instance - internal_iterator m_it = internal_iterator(); - }; - - /*! - @brief a mutable random access iterator for the @ref basic_json class - - @requirement The class satisfies the following concept requirements: - - [RandomAccessIterator](http://en.cppreference.com/w/cpp/concept/RandomAccessIterator): - The iterator that can be moved to point (forward and backward) to any - element in constant time. - - [OutputIterator](http://en.cppreference.com/w/cpp/concept/OutputIterator): - It is possible to write to the pointed-to element. - - @since version 1.0.0 - */ - class iterator : public const_iterator - { - public: - using base_iterator = const_iterator; - using pointer = typename basic_json::pointer; - using reference = typename basic_json::reference; - - /// default constructor - iterator() = default; - - /// constructor for a given JSON instance - explicit iterator(pointer object) noexcept - : base_iterator(object) - {} - - /// copy constructor - iterator(const iterator& other) noexcept - : base_iterator(other) - {} - - /// copy assignment - iterator& operator=(iterator other) noexcept( - std::is_nothrow_move_constructible::value and - std::is_nothrow_move_assignable::value and - std::is_nothrow_move_constructible::value and - std::is_nothrow_move_assignable::value - ) - { - base_iterator::operator=(other); - return *this; - } - - /// return a reference to the value pointed to by the iterator - reference operator*() const - { - return const_cast(base_iterator::operator*()); - } - - /// dereference the iterator - pointer operator->() const - { - return const_cast(base_iterator::operator->()); - } - - /// post-increment (it++) - iterator operator++(int) - { - iterator result = *this; - base_iterator::operator++(); - return result; - } - - /// pre-increment (++it) - iterator& operator++() - { - base_iterator::operator++(); - return *this; - } - - /// post-decrement (it--) - iterator operator--(int) - { - iterator result = *this; - base_iterator::operator--(); - return result; - } - - /// pre-decrement (--it) - iterator& operator--() - { - base_iterator::operator--(); - return *this; - } - - /// add to iterator - iterator& operator+=(difference_type i) - { - base_iterator::operator+=(i); - return *this; - } - - /// subtract from iterator - iterator& operator-=(difference_type i) - { - base_iterator::operator-=(i); - return *this; - } - - /// add to iterator - iterator operator+(difference_type i) - { - auto result = *this; - result += i; - return result; - } - - /// subtract from iterator - iterator operator-(difference_type i) - { - auto result = *this; - result -= i; - return result; - } - - /// return difference - difference_type operator-(const iterator& other) const - { - return base_iterator::operator-(other); - } - - /// access to successor - reference operator[](difference_type n) const - { - return const_cast(base_iterator::operator[](n)); - } - - /// return the value of an iterator - reference value() const - { - return const_cast(base_iterator::value()); - } - }; - - /*! - @brief a template for a reverse iterator class - - @tparam Base the base iterator type to reverse. Valid types are @ref - iterator (to create @ref reverse_iterator) and @ref const_iterator (to - create @ref const_reverse_iterator). - - @requirement The class satisfies the following concept requirements: - - [RandomAccessIterator](http://en.cppreference.com/w/cpp/concept/RandomAccessIterator): - The iterator that can be moved to point (forward and backward) to any - element in constant time. - - [OutputIterator](http://en.cppreference.com/w/cpp/concept/OutputIterator): - It is possible to write to the pointed-to element (only if @a Base is - @ref iterator). - - @since version 1.0.0 - */ - template - class json_reverse_iterator : public std::reverse_iterator - { - public: - /// shortcut to the reverse iterator adaptor - using base_iterator = std::reverse_iterator; - /// the reference type for the pointed-to element - using reference = typename Base::reference; - - /// create reverse iterator from iterator - json_reverse_iterator(const typename base_iterator::iterator_type& it) noexcept - : base_iterator(it) - {} - - /// create reverse iterator from base class - json_reverse_iterator(const base_iterator& it) noexcept - : base_iterator(it) - {} - - /// post-increment (it++) - json_reverse_iterator operator++(int) - { - return base_iterator::operator++(1); - } - - /// pre-increment (++it) - json_reverse_iterator& operator++() - { - base_iterator::operator++(); - return *this; - } - - /// post-decrement (it--) - json_reverse_iterator operator--(int) - { - return base_iterator::operator--(1); - } - - /// pre-decrement (--it) - json_reverse_iterator& operator--() - { - base_iterator::operator--(); - return *this; - } - - /// add to iterator - json_reverse_iterator& operator+=(difference_type i) - { - base_iterator::operator+=(i); - return *this; - } - - /// add to iterator - json_reverse_iterator operator+(difference_type i) const - { - auto result = *this; - result += i; - return result; - } - - /// subtract from iterator - json_reverse_iterator operator-(difference_type i) const - { - auto result = *this; - result -= i; - return result; - } - - /// return difference - difference_type operator-(const json_reverse_iterator& other) const - { - return this->base() - other.base(); - } - - /// access to successor - reference operator[](difference_type n) const - { - return *(this->operator+(n)); - } - - /// return the key of an object iterator - typename object_t::key_type key() const - { - auto it = --this->base(); - return it.key(); - } - - /// return the value of an iterator - reference value() const - { - auto it = --this->base(); - return it.operator * (); - } - }; - - - private: - ////////////////////// - // lexer and parser // - ////////////////////// - - /*! - @brief lexical analysis - - This class organizes the lexical analysis during JSON deserialization. The - core of it is a scanner generated by [re2c](http://re2c.org) that - processes a buffer and recognizes tokens according to RFC 7159. - */ - class lexer - { - public: - /// token types for the parser - enum class token_type - { - uninitialized, ///< indicating the scanner is uninitialized - literal_true, ///< the `true` literal - literal_false, ///< the `false` literal - literal_null, ///< the `null` literal - value_string, ///< a string -- use get_string() for actual value - value_number, ///< a number -- use get_number() for actual value - begin_array, ///< the character for array begin `[` - begin_object, ///< the character for object begin `{` - end_array, ///< the character for array end `]` - end_object, ///< the character for object end `}` - name_separator, ///< the name separator `:` - value_separator, ///< the value separator `,` - parse_error, ///< indicating a parse error - end_of_input ///< indicating the end of the input buffer - }; - - /// the char type to use in the lexer - using lexer_char_t = unsigned char; - - /// a lexer from a buffer with given length - lexer(const lexer_char_t* buff, const size_t len) noexcept - : m_content(buff) - { - assert(m_content != nullptr); - m_start = m_cursor = m_content; - m_limit = m_content + len; - } - - /// a lexer from an input stream - explicit lexer(std::istream& s) - : m_stream(&s), m_line_buffer() - { - // fill buffer - fill_line_buffer(); - } - - // switch off unwanted functions (due to pointer members) - lexer() = delete; - lexer(const lexer&) = delete; - lexer operator=(const lexer&) = delete; - - /*! - @brief create a string from one or two Unicode code points - - There are two cases: (1) @a codepoint1 is in the Basic Multilingual - Plane (U+0000 through U+FFFF) and @a codepoint2 is 0, or (2) - @a codepoint1 and @a codepoint2 are a UTF-16 surrogate pair to - represent a code point above U+FFFF. - - @param[in] codepoint1 the code point (can be high surrogate) - @param[in] codepoint2 the code point (can be low surrogate or 0) - - @return string representation of the code point; the length of the - result string is between 1 and 4 characters. - - @throw std::out_of_range if code point is > 0x10ffff; example: `"code - points above 0x10FFFF are invalid"` - @throw std::invalid_argument if the low surrogate is invalid; example: - `""missing or wrong low surrogate""` - - @complexity Constant. - - @see - */ - static string_t to_unicode(const std::size_t codepoint1, - const std::size_t codepoint2 = 0) - { - // calculate the code point from the given code points - std::size_t codepoint = codepoint1; - - // check if codepoint1 is a high surrogate - if (codepoint1 >= 0xD800 and codepoint1 <= 0xDBFF) - { - // check if codepoint2 is a low surrogate - if (codepoint2 >= 0xDC00 and codepoint2 <= 0xDFFF) - { - codepoint = - // high surrogate occupies the most significant 22 bits - (codepoint1 << 10) - // low surrogate occupies the least significant 15 bits - + codepoint2 - // there is still the 0xD800, 0xDC00 and 0x10000 noise - // in the result so we have to subtract with: - // (0xD800 << 10) + DC00 - 0x10000 = 0x35FDC00 - - 0x35FDC00; - } - else - { - throw std::invalid_argument("missing or wrong low surrogate"); - } - } - - string_t result; - - if (codepoint < 0x80) - { - // 1-byte characters: 0xxxxxxx (ASCII) - result.append(1, static_cast(codepoint)); - } - else if (codepoint <= 0x7ff) - { - // 2-byte characters: 110xxxxx 10xxxxxx - result.append(1, static_cast(0xC0 | ((codepoint >> 6) & 0x1F))); - result.append(1, static_cast(0x80 | (codepoint & 0x3F))); - } - else if (codepoint <= 0xffff) - { - // 3-byte characters: 1110xxxx 10xxxxxx 10xxxxxx - result.append(1, static_cast(0xE0 | ((codepoint >> 12) & 0x0F))); - result.append(1, static_cast(0x80 | ((codepoint >> 6) & 0x3F))); - result.append(1, static_cast(0x80 | (codepoint & 0x3F))); - } - else if (codepoint <= 0x10ffff) - { - // 4-byte characters: 11110xxx 10xxxxxx 10xxxxxx 10xxxxxx - result.append(1, static_cast(0xF0 | ((codepoint >> 18) & 0x07))); - result.append(1, static_cast(0x80 | ((codepoint >> 12) & 0x3F))); - result.append(1, static_cast(0x80 | ((codepoint >> 6) & 0x3F))); - result.append(1, static_cast(0x80 | (codepoint & 0x3F))); - } - else - { - throw std::out_of_range("code points above 0x10FFFF are invalid"); - } - - return result; - } - - /// return name of values of type token_type (only used for errors) - static std::string token_type_name(const token_type t) - { - switch (t) - { - case token_type::uninitialized: - return ""; - case token_type::literal_true: - return "true literal"; - case token_type::literal_false: - return "false literal"; - case token_type::literal_null: - return "null literal"; - case token_type::value_string: - return "string literal"; - case token_type::value_number: - return "number literal"; - case token_type::begin_array: - return "'['"; - case token_type::begin_object: - return "'{'"; - case token_type::end_array: - return "']'"; - case token_type::end_object: - return "'}'"; - case token_type::name_separator: - return "':'"; - case token_type::value_separator: - return "','"; - case token_type::parse_error: - return ""; - case token_type::end_of_input: - return "end of input"; - default: - { - // catch non-enum values - return "unknown token"; // LCOV_EXCL_LINE - } - } - } - - /*! - This function implements a scanner for JSON. It is specified using - regular expressions that try to follow RFC 7159 as close as possible. - These regular expressions are then translated into a minimized - deterministic finite automaton (DFA) by the tool - [re2c](http://re2c.org). As a result, the translated code for this - function consists of a large block of code with `goto` jumps. - - @return the class of the next token read from the buffer - - @complexity Linear in the length of the input.\n - - Proposition: The loop below will always terminate for finite input.\n - - Proof (by contradiction): Assume a finite input. To loop forever, the - loop must never hit code with a `break` statement. The only code - snippets without a `break` statement are the continue statements for - whitespace and byte-order-marks. To loop forever, the input must be an - infinite sequence of whitespace or byte-order-marks. This contradicts - the assumption of finite input, q.e.d. - */ - token_type scan() - { - while (true) - { - // pointer for backtracking information - m_marker = nullptr; - - // remember the begin of the token - m_start = m_cursor; - assert(m_start != nullptr); - - - { - lexer_char_t yych; - unsigned int yyaccept = 0; - static const unsigned char yybm[] = - { - 0, 0, 0, 0, 0, 0, 0, 0, - 0, 32, 32, 0, 0, 32, 0, 0, - 0, 0, 0, 0, 0, 0, 0, 0, - 0, 0, 0, 0, 0, 0, 0, 0, - 160, 128, 0, 128, 128, 128, 128, 128, - 128, 128, 128, 128, 128, 128, 128, 128, - 192, 192, 192, 192, 192, 192, 192, 192, - 192, 192, 128, 128, 128, 128, 128, 128, - 128, 128, 128, 128, 128, 128, 128, 128, - 128, 128, 128, 128, 128, 128, 128, 128, - 128, 128, 128, 128, 128, 128, 128, 128, - 128, 128, 128, 128, 0, 128, 128, 128, - 128, 128, 128, 128, 128, 128, 128, 128, - 128, 128, 128, 128, 128, 128, 128, 128, - 128, 128, 128, 128, 128, 128, 128, 128, - 128, 128, 128, 128, 128, 128, 128, 128, - 128, 128, 128, 128, 128, 128, 128, 128, - 128, 128, 128, 128, 128, 128, 128, 128, - 128, 128, 128, 128, 128, 128, 128, 128, - 128, 128, 128, 128, 128, 128, 128, 128, - 128, 128, 128, 128, 128, 128, 128, 128, - 128, 128, 128, 128, 128, 128, 128, 128, - 128, 128, 128, 128, 128, 128, 128, 128, - 128, 128, 128, 128, 128, 128, 128, 128, - 128, 128, 128, 128, 128, 128, 128, 128, - 128, 128, 128, 128, 128, 128, 128, 128, - 128, 128, 128, 128, 128, 128, 128, 128, - 128, 128, 128, 128, 128, 128, 128, 128, - 128, 128, 128, 128, 128, 128, 128, 128, - 128, 128, 128, 128, 128, 128, 128, 128, - 128, 128, 128, 128, 128, 128, 128, 128, - 128, 128, 128, 128, 128, 128, 128, 128, - }; - if ((m_limit - m_cursor) < 5) - { - fill_line_buffer(); - } - yych = *m_cursor; - if (yybm[0 + yych] & 32) - { - goto basic_json_parser_6; - } - if (yych <= '\\') - { - if (yych <= '-') - { - if (yych <= '"') - { - if (yych <= 0x00) - { - goto basic_json_parser_2; - } - if (yych <= '!') - { - goto basic_json_parser_4; - } - goto basic_json_parser_9; - } - else - { - if (yych <= '+') - { - goto basic_json_parser_4; - } - if (yych <= ',') - { - goto basic_json_parser_10; - } - goto basic_json_parser_12; - } - } - else - { - if (yych <= '9') - { - if (yych <= '/') - { - goto basic_json_parser_4; - } - if (yych <= '0') - { - goto basic_json_parser_13; - } - goto basic_json_parser_15; - } - else - { - if (yych <= ':') - { - goto basic_json_parser_17; - } - if (yych == '[') - { - goto basic_json_parser_19; - } - goto basic_json_parser_4; - } - } - } - else - { - if (yych <= 't') - { - if (yych <= 'f') - { - if (yych <= ']') - { - goto basic_json_parser_21; - } - if (yych <= 'e') - { - goto basic_json_parser_4; - } - goto basic_json_parser_23; - } - else - { - if (yych == 'n') - { - goto basic_json_parser_24; - } - if (yych <= 's') - { - goto basic_json_parser_4; - } - goto basic_json_parser_25; - } - } - else - { - if (yych <= '|') - { - if (yych == '{') - { - goto basic_json_parser_26; - } - goto basic_json_parser_4; - } - else - { - if (yych <= '}') - { - goto basic_json_parser_28; - } - if (yych == 0xEF) - { - goto basic_json_parser_30; - } - goto basic_json_parser_4; - } - } - } -basic_json_parser_2: - ++m_cursor; - { - last_token_type = token_type::end_of_input; - break; - } -basic_json_parser_4: - ++m_cursor; -basic_json_parser_5: - { - last_token_type = token_type::parse_error; - break; - } -basic_json_parser_6: - ++m_cursor; - if (m_limit <= m_cursor) - { - fill_line_buffer(); - } - yych = *m_cursor; - if (yybm[0 + yych] & 32) - { - goto basic_json_parser_6; - } - { - continue; - } -basic_json_parser_9: - yyaccept = 0; - yych = *(m_marker = ++m_cursor); - if (yych <= 0x1F) - { - goto basic_json_parser_5; - } - goto basic_json_parser_32; -basic_json_parser_10: - ++m_cursor; - { - last_token_type = token_type::value_separator; - break; - } -basic_json_parser_12: - yych = *++m_cursor; - if (yych <= '/') - { - goto basic_json_parser_5; - } - if (yych <= '0') - { - goto basic_json_parser_13; - } - if (yych <= '9') - { - goto basic_json_parser_15; - } - goto basic_json_parser_5; -basic_json_parser_13: - yyaccept = 1; - yych = *(m_marker = ++m_cursor); - if (yych <= 'D') - { - if (yych == '.') - { - goto basic_json_parser_37; - } - } - else - { - if (yych <= 'E') - { - goto basic_json_parser_38; - } - if (yych == 'e') - { - goto basic_json_parser_38; - } - } -basic_json_parser_14: - { - last_token_type = token_type::value_number; - break; - } -basic_json_parser_15: - yyaccept = 1; - m_marker = ++m_cursor; - if ((m_limit - m_cursor) < 3) - { - fill_line_buffer(); - } - yych = *m_cursor; - if (yybm[0 + yych] & 64) - { - goto basic_json_parser_15; - } - if (yych <= 'D') - { - if (yych == '.') - { - goto basic_json_parser_37; - } - goto basic_json_parser_14; - } - else - { - if (yych <= 'E') - { - goto basic_json_parser_38; - } - if (yych == 'e') - { - goto basic_json_parser_38; - } - goto basic_json_parser_14; - } -basic_json_parser_17: - ++m_cursor; - { - last_token_type = token_type::name_separator; - break; - } -basic_json_parser_19: - ++m_cursor; - { - last_token_type = token_type::begin_array; - break; - } -basic_json_parser_21: - ++m_cursor; - { - last_token_type = token_type::end_array; - break; - } -basic_json_parser_23: - yyaccept = 0; - yych = *(m_marker = ++m_cursor); - if (yych == 'a') - { - goto basic_json_parser_39; - } - goto basic_json_parser_5; -basic_json_parser_24: - yyaccept = 0; - yych = *(m_marker = ++m_cursor); - if (yych == 'u') - { - goto basic_json_parser_40; - } - goto basic_json_parser_5; -basic_json_parser_25: - yyaccept = 0; - yych = *(m_marker = ++m_cursor); - if (yych == 'r') - { - goto basic_json_parser_41; - } - goto basic_json_parser_5; -basic_json_parser_26: - ++m_cursor; - { - last_token_type = token_type::begin_object; - break; - } -basic_json_parser_28: - ++m_cursor; - { - last_token_type = token_type::end_object; - break; - } -basic_json_parser_30: - yyaccept = 0; - yych = *(m_marker = ++m_cursor); - if (yych == 0xBB) - { - goto basic_json_parser_42; - } - goto basic_json_parser_5; -basic_json_parser_31: - ++m_cursor; - if (m_limit <= m_cursor) - { - fill_line_buffer(); - } - yych = *m_cursor; -basic_json_parser_32: - if (yybm[0 + yych] & 128) - { - goto basic_json_parser_31; - } - if (yych <= 0x1F) - { - goto basic_json_parser_33; - } - if (yych <= '"') - { - goto basic_json_parser_34; - } - goto basic_json_parser_36; -basic_json_parser_33: - m_cursor = m_marker; - if (yyaccept == 0) - { - goto basic_json_parser_5; - } - else - { - goto basic_json_parser_14; - } -basic_json_parser_34: - ++m_cursor; - { - last_token_type = token_type::value_string; - break; - } -basic_json_parser_36: - ++m_cursor; - if (m_limit <= m_cursor) - { - fill_line_buffer(); - } - yych = *m_cursor; - if (yych <= 'e') - { - if (yych <= '/') - { - if (yych == '"') - { - goto basic_json_parser_31; - } - if (yych <= '.') - { - goto basic_json_parser_33; - } - goto basic_json_parser_31; - } - else - { - if (yych <= '\\') - { - if (yych <= '[') - { - goto basic_json_parser_33; - } - goto basic_json_parser_31; - } - else - { - if (yych == 'b') - { - goto basic_json_parser_31; - } - goto basic_json_parser_33; - } - } - } - else - { - if (yych <= 'q') - { - if (yych <= 'f') - { - goto basic_json_parser_31; - } - if (yych == 'n') - { - goto basic_json_parser_31; - } - goto basic_json_parser_33; - } - else - { - if (yych <= 's') - { - if (yych <= 'r') - { - goto basic_json_parser_31; - } - goto basic_json_parser_33; - } - else - { - if (yych <= 't') - { - goto basic_json_parser_31; - } - if (yych <= 'u') - { - goto basic_json_parser_43; - } - goto basic_json_parser_33; - } - } - } -basic_json_parser_37: - yych = *++m_cursor; - if (yych <= '/') - { - goto basic_json_parser_33; - } - if (yych <= '9') - { - goto basic_json_parser_44; - } - goto basic_json_parser_33; -basic_json_parser_38: - yych = *++m_cursor; - if (yych <= ',') - { - if (yych == '+') - { - goto basic_json_parser_46; - } - goto basic_json_parser_33; - } - else - { - if (yych <= '-') - { - goto basic_json_parser_46; - } - if (yych <= '/') - { - goto basic_json_parser_33; - } - if (yych <= '9') - { - goto basic_json_parser_47; - } - goto basic_json_parser_33; - } -basic_json_parser_39: - yych = *++m_cursor; - if (yych == 'l') - { - goto basic_json_parser_49; - } - goto basic_json_parser_33; -basic_json_parser_40: - yych = *++m_cursor; - if (yych == 'l') - { - goto basic_json_parser_50; - } - goto basic_json_parser_33; -basic_json_parser_41: - yych = *++m_cursor; - if (yych == 'u') - { - goto basic_json_parser_51; - } - goto basic_json_parser_33; -basic_json_parser_42: - yych = *++m_cursor; - if (yych == 0xBF) - { - goto basic_json_parser_52; - } - goto basic_json_parser_33; -basic_json_parser_43: - ++m_cursor; - if (m_limit <= m_cursor) - { - fill_line_buffer(); - } - yych = *m_cursor; - if (yych <= '@') - { - if (yych <= '/') - { - goto basic_json_parser_33; - } - if (yych <= '9') - { - goto basic_json_parser_54; - } - goto basic_json_parser_33; - } - else - { - if (yych <= 'F') - { - goto basic_json_parser_54; - } - if (yych <= '`') - { - goto basic_json_parser_33; - } - if (yych <= 'f') - { - goto basic_json_parser_54; - } - goto basic_json_parser_33; - } -basic_json_parser_44: - yyaccept = 1; - m_marker = ++m_cursor; - if ((m_limit - m_cursor) < 3) - { - fill_line_buffer(); - } - yych = *m_cursor; - if (yych <= 'D') - { - if (yych <= '/') - { - goto basic_json_parser_14; - } - if (yych <= '9') - { - goto basic_json_parser_44; - } - goto basic_json_parser_14; - } - else - { - if (yych <= 'E') - { - goto basic_json_parser_38; - } - if (yych == 'e') - { - goto basic_json_parser_38; - } - goto basic_json_parser_14; - } -basic_json_parser_46: - yych = *++m_cursor; - if (yych <= '/') - { - goto basic_json_parser_33; - } - if (yych >= ':') - { - goto basic_json_parser_33; - } -basic_json_parser_47: - ++m_cursor; - if (m_limit <= m_cursor) - { - fill_line_buffer(); - } - yych = *m_cursor; - if (yych <= '/') - { - goto basic_json_parser_14; - } - if (yych <= '9') - { - goto basic_json_parser_47; - } - goto basic_json_parser_14; -basic_json_parser_49: - yych = *++m_cursor; - if (yych == 's') - { - goto basic_json_parser_55; - } - goto basic_json_parser_33; -basic_json_parser_50: - yych = *++m_cursor; - if (yych == 'l') - { - goto basic_json_parser_56; - } - goto basic_json_parser_33; -basic_json_parser_51: - yych = *++m_cursor; - if (yych == 'e') - { - goto basic_json_parser_58; - } - goto basic_json_parser_33; -basic_json_parser_52: - ++m_cursor; - { - continue; - } -basic_json_parser_54: - ++m_cursor; - if (m_limit <= m_cursor) - { - fill_line_buffer(); - } - yych = *m_cursor; - if (yych <= '@') - { - if (yych <= '/') - { - goto basic_json_parser_33; - } - if (yych <= '9') - { - goto basic_json_parser_60; - } - goto basic_json_parser_33; - } - else - { - if (yych <= 'F') - { - goto basic_json_parser_60; - } - if (yych <= '`') - { - goto basic_json_parser_33; - } - if (yych <= 'f') - { - goto basic_json_parser_60; - } - goto basic_json_parser_33; - } -basic_json_parser_55: - yych = *++m_cursor; - if (yych == 'e') - { - goto basic_json_parser_61; - } - goto basic_json_parser_33; -basic_json_parser_56: - ++m_cursor; - { - last_token_type = token_type::literal_null; - break; - } -basic_json_parser_58: - ++m_cursor; - { - last_token_type = token_type::literal_true; - break; - } -basic_json_parser_60: - ++m_cursor; - if (m_limit <= m_cursor) - { - fill_line_buffer(); - } - yych = *m_cursor; - if (yych <= '@') - { - if (yych <= '/') - { - goto basic_json_parser_33; - } - if (yych <= '9') - { - goto basic_json_parser_63; - } - goto basic_json_parser_33; - } - else - { - if (yych <= 'F') - { - goto basic_json_parser_63; - } - if (yych <= '`') - { - goto basic_json_parser_33; - } - if (yych <= 'f') - { - goto basic_json_parser_63; - } - goto basic_json_parser_33; - } -basic_json_parser_61: - ++m_cursor; - { - last_token_type = token_type::literal_false; - break; - } -basic_json_parser_63: - ++m_cursor; - if (m_limit <= m_cursor) - { - fill_line_buffer(); - } - yych = *m_cursor; - if (yych <= '@') - { - if (yych <= '/') - { - goto basic_json_parser_33; - } - if (yych <= '9') - { - goto basic_json_parser_31; - } - goto basic_json_parser_33; - } - else - { - if (yych <= 'F') - { - goto basic_json_parser_31; - } - if (yych <= '`') - { - goto basic_json_parser_33; - } - if (yych <= 'f') - { - goto basic_json_parser_31; - } - goto basic_json_parser_33; - } - } - - } - - return last_token_type; - } - - /*! - @brief append data from the stream to the line buffer - - This function is called by the scan() function when the end of the - buffer (`m_limit`) is reached and the `m_cursor` pointer cannot be - incremented without leaving the limits of the line buffer. Note re2c - decides when to call this function. - - If the lexer reads from contiguous storage, there is no trailing null - byte. Therefore, this function must make sure to add these padding - null bytes. - - If the lexer reads from an input stream, this function reads the next - line of the input. - - @pre - p p p p p p u u u u u x . . . . . . - ^ ^ ^ ^ - m_content m_start | m_limit - m_cursor - - @post - u u u u u x x x x x x x . . . . . . - ^ ^ ^ - | m_cursor m_limit - m_start - m_content - */ - void fill_line_buffer() - { - // number of processed characters (p) - const auto offset_start = m_start - m_content; - // offset for m_marker wrt. to m_start - const auto offset_marker = (m_marker == nullptr) ? 0 : m_marker - m_start; - // number of unprocessed characters (u) - const auto offset_cursor = m_cursor - m_start; - - // no stream is used or end of file is reached - if (m_stream == nullptr or m_stream->eof()) - { - // copy unprocessed characters to line buffer - m_line_buffer.clear(); - for (m_cursor = m_start; m_cursor != m_limit; ++m_cursor) - { - m_line_buffer.append(1, static_cast(*m_cursor)); - } - - // append 5 characters (size of longest keyword "false") to - // make sure that there is sufficient space between m_cursor - // and m_limit - m_line_buffer.append(5, '\0'); - } - else - { - // delete processed characters from line buffer - m_line_buffer.erase(0, static_cast(offset_start)); - // read next line from input stream - std::string line; - std::getline(*m_stream, line); - // add line with newline symbol to the line buffer - m_line_buffer += line + "\n"; - } - - // set pointers - m_content = reinterpret_cast(m_line_buffer.c_str()); - assert(m_content != nullptr); - m_start = m_content; - m_marker = m_start + offset_marker; - m_cursor = m_start + offset_cursor; - m_limit = m_start + m_line_buffer.size(); - } - - /// return string representation of last read token - string_t get_token_string() const - { - assert(m_start != nullptr); - return string_t(reinterpret_cast(m_start), - static_cast(m_cursor - m_start)); - } - - /*! - @brief return string value for string tokens - - The function iterates the characters between the opening and closing - quotes of the string value. The complete string is the range - [m_start,m_cursor). Consequently, we iterate from m_start+1 to - m_cursor-1. - - We differentiate two cases: - - 1. Escaped characters. In this case, a new character is constructed - according to the nature of the escape. Some escapes create new - characters (e.g., `"\\n"` is replaced by `"\n"`), some are copied - as is (e.g., `"\\\\"`). Furthermore, Unicode escapes of the shape - `"\\uxxxx"` need special care. In this case, to_unicode takes care - of the construction of the values. - 2. Unescaped characters are copied as is. - - @pre `m_cursor - m_start >= 2`, meaning the length of the last token - is at least 2 bytes which is trivially true for any string (which - consists of at least two quotes). - - " c1 c2 c3 ... " - ^ ^ - m_start m_cursor - - @complexity Linear in the length of the string.\n - - Lemma: The loop body will always terminate.\n - - Proof (by contradiction): Assume the loop body does not terminate. As - the loop body does not contain another loop, one of the called - functions must never return. The called functions are `std::strtoul` - and to_unicode. Neither function can loop forever, so the loop body - will never loop forever which contradicts the assumption that the loop - body does not terminate, q.e.d.\n - - Lemma: The loop condition for the for loop is eventually false.\n - - Proof (by contradiction): Assume the loop does not terminate. Due to - the above lemma, this can only be due to a tautological loop - condition; that is, the loop condition i < m_cursor - 1 must always be - true. Let x be the change of i for any loop iteration. Then - m_start + 1 + x < m_cursor - 1 must hold to loop indefinitely. This - can be rephrased to m_cursor - m_start - 2 > x. With the - precondition, we x <= 0, meaning that the loop condition holds - indefinitly if i is always decreased. However, observe that the value - of i is strictly increasing with each iteration, as it is incremented - by 1 in the iteration expression and never decremented inside the loop - body. Hence, the loop condition will eventually be false which - contradicts the assumption that the loop condition is a tautology, - q.e.d. - - @return string value of current token without opening and closing - quotes - @throw std::out_of_range if to_unicode fails - */ - string_t get_string() const - { - assert(m_cursor - m_start >= 2); - - string_t result; - result.reserve(static_cast(m_cursor - m_start - 2)); - - // iterate the result between the quotes - for (const lexer_char_t* i = m_start + 1; i < m_cursor - 1; ++i) - { - // process escaped characters - if (*i == '\\') - { - // read next character - ++i; - - switch (*i) - { - // the default escapes - case 't': - { - result += "\t"; - break; - } - case 'b': - { - result += "\b"; - break; - } - case 'f': - { - result += "\f"; - break; - } - case 'n': - { - result += "\n"; - break; - } - case 'r': - { - result += "\r"; - break; - } - case '\\': - { - result += "\\"; - break; - } - case '/': - { - result += "/"; - break; - } - case '"': - { - result += "\""; - break; - } - - // unicode - case 'u': - { - // get code xxxx from uxxxx - auto codepoint = std::strtoul(std::string(reinterpret_cast(i + 1), - 4).c_str(), nullptr, 16); - - // check if codepoint is a high surrogate - if (codepoint >= 0xD800 and codepoint <= 0xDBFF) - { - // make sure there is a subsequent unicode - if ((i + 6 >= m_limit) or * (i + 5) != '\\' or * (i + 6) != 'u') - { - throw std::invalid_argument("missing low surrogate"); - } - - // get code yyyy from uxxxx\uyyyy - auto codepoint2 = std::strtoul(std::string(reinterpret_cast - (i + 7), 4).c_str(), nullptr, 16); - result += to_unicode(codepoint, codepoint2); - // skip the next 10 characters (xxxx\uyyyy) - i += 10; - } - else - { - // add unicode character(s) - result += to_unicode(codepoint); - // skip the next four characters (xxxx) - i += 4; - } - break; - } - } - } - else - { - // all other characters are just copied to the end of the - // string - result.append(1, static_cast(*i)); - } - } - - return result; - } - - /*! - @brief parse floating point number - - This function (and its overloads) serves to select the most approprate - standard floating point number parsing function based on the type - supplied via the first parameter. Set this to @a - static_cast(nullptr). - - @param[in] type the @ref number_float_t in use - - @param[in,out] endptr recieves a pointer to the first character after - the number - - @return the floating point number - */ - long double str_to_float_t(long double* /* type */, char** endptr) const - { - return std::strtold(reinterpret_cast(m_start), endptr); - } - - /*! - @brief parse floating point number - - This function (and its overloads) serves to select the most approprate - standard floating point number parsing function based on the type - supplied via the first parameter. Set this to @a - static_cast(nullptr). - - @param[in] type the @ref number_float_t in use - - @param[in,out] endptr recieves a pointer to the first character after - the number - - @return the floating point number - */ - double str_to_float_t(double* /* type */, char** endptr) const - { - return std::strtod(reinterpret_cast(m_start), endptr); - } - - /*! - @brief parse floating point number - - This function (and its overloads) serves to select the most approprate - standard floating point number parsing function based on the type - supplied via the first parameter. Set this to @a - static_cast(nullptr). - - @param[in] type the @ref number_float_t in use - - @param[in,out] endptr recieves a pointer to the first character after - the number - - @return the floating point number - */ - float str_to_float_t(float* /* type */, char** endptr) const - { - return std::strtof(reinterpret_cast(m_start), endptr); - } - - /*! - @brief return number value for number tokens - - This function translates the last token into the most appropriate - number type (either integer, unsigned integer or floating point), - which is passed back to the caller via the result parameter. - - This function parses the integer component up to the radix point or - exponent while collecting information about the 'floating point - representation', which it stores in the result parameter. If there is - no radix point or exponent, and the number can fit into a @ref - number_integer_t or @ref number_unsigned_t then it sets the result - parameter accordingly. - - If the number is a floating point number the number is then parsed - using @a std:strtod (or @a std:strtof or @a std::strtold). - - @param[out] result @ref basic_json object to receive the number, or - NAN if the conversion read past the current token. The latter case - needs to be treated by the caller function. - */ - void get_number(basic_json& result) const - { - assert(m_start != nullptr); - - const lexer::lexer_char_t* curptr = m_start; - - // accumulate the integer conversion result (unsigned for now) - number_unsigned_t value = 0; - - // maximum absolute value of the relevant integer type - number_unsigned_t max; - - // temporarily store the type to avoid unecessary bitfield access - value_t type; - - // look for sign - if (*curptr == '-') - { - type = value_t::number_integer; - max = static_cast((std::numeric_limits::max)()) + 1; - curptr++; - } - else - { - type = value_t::number_unsigned; - max = static_cast((std::numeric_limits::max)()); - } - - // count the significant figures - for (; curptr < m_cursor; curptr++) - { - // quickly skip tests if a digit - if (*curptr < '0' || *curptr > '9') - { - if (*curptr == '.') - { - // don't count '.' but change to float - type = value_t::number_float; - continue; - } - // assume exponent (if not then will fail parse): change to - // float, stop counting and record exponent details - type = value_t::number_float; - break; - } - - // skip if definitely not an integer - if (type != value_t::number_float) - { - // multiply last value by ten and add the new digit - auto temp = value * 10 + *curptr - '0'; - - // test for overflow - if (temp < value || temp > max) - { - // overflow - type = value_t::number_float; - } - else - { - // no overflow - save it - value = temp; - } - } - } - - // save the value (if not a float) - if (type == value_t::number_unsigned) - { - result.m_value.number_unsigned = value; - } - else if (type == value_t::number_integer) - { - result.m_value.number_integer = -static_cast(value); - } - else - { - // parse with strtod - result.m_value.number_float = str_to_float_t(static_cast(nullptr), NULL); - } - - // save the type - result.m_type = type; - } - - private: - /// optional input stream - std::istream* m_stream = nullptr; - /// line buffer buffer for m_stream - string_t m_line_buffer {}; - /// the buffer pointer - const lexer_char_t* m_content = nullptr; - /// pointer to the beginning of the current symbol - const lexer_char_t* m_start = nullptr; - /// pointer for backtracking information - const lexer_char_t* m_marker = nullptr; - /// pointer to the current symbol - const lexer_char_t* m_cursor = nullptr; - /// pointer to the end of the buffer - const lexer_char_t* m_limit = nullptr; - /// the last token type - token_type last_token_type = token_type::end_of_input; - }; - - /*! - @brief syntax analysis - - This class implements a recursive decent parser. - */ - class parser - { - public: - /// a parser reading from a string literal - parser(const char* buff, const parser_callback_t cb = nullptr) - : callback(cb), - m_lexer(reinterpret_cast(buff), strlen(buff)) - {} - - /// a parser reading from an input stream - parser(std::istream& is, const parser_callback_t cb = nullptr) - : callback(cb), m_lexer(is) - {} - - /// a parser reading from an iterator range with contiguous storage - template::iterator_category, std::random_access_iterator_tag>::value - , int>::type - = 0> - parser(IteratorType first, IteratorType last, const parser_callback_t cb = nullptr) - : callback(cb), - m_lexer(reinterpret_cast(&(*first)), - static_cast(std::distance(first, last))) - {} - - /// public parser interface - basic_json parse() - { - // read first token - get_token(); - - basic_json result = parse_internal(true); - result.assert_invariant(); - - expect(lexer::token_type::end_of_input); - - // return parser result and replace it with null in case the - // top-level value was discarded by the callback function - return result.is_discarded() ? basic_json() : std::move(result); - } - - private: - /// the actual parser - basic_json parse_internal(bool keep) - { - auto result = basic_json(value_t::discarded); - - switch (last_token) - { - case lexer::token_type::begin_object: - { - if (keep and (not callback - or ((keep = callback(depth++, parse_event_t::object_start, result)) != 0))) - { - // explicitly set result to object to cope with {} - result.m_type = value_t::object; - result.m_value = value_t::object; - } - - // read next token - get_token(); - - // closing } -> we are done - if (last_token == lexer::token_type::end_object) - { - get_token(); - if (keep and callback and not callback(--depth, parse_event_t::object_end, result)) - { - result = basic_json(value_t::discarded); - } - return result; - } - - // no comma is expected here - unexpect(lexer::token_type::value_separator); - - // otherwise: parse key-value pairs - do - { - // ugly, but could be fixed with loop reorganization - if (last_token == lexer::token_type::value_separator) - { - get_token(); - } - - // store key - expect(lexer::token_type::value_string); - const auto key = m_lexer.get_string(); - - bool keep_tag = false; - if (keep) - { - if (callback) - { - basic_json k(key); - keep_tag = callback(depth, parse_event_t::key, k); - } - else - { - keep_tag = true; - } - } - - // parse separator (:) - get_token(); - expect(lexer::token_type::name_separator); - - // parse and add value - get_token(); - auto value = parse_internal(keep); - if (keep and keep_tag and not value.is_discarded()) - { - result[key] = std::move(value); - } - } - while (last_token == lexer::token_type::value_separator); - - // closing } - expect(lexer::token_type::end_object); - get_token(); - if (keep and callback and not callback(--depth, parse_event_t::object_end, result)) - { - result = basic_json(value_t::discarded); - } - - return result; - } - - case lexer::token_type::begin_array: - { - if (keep and (not callback - or ((keep = callback(depth++, parse_event_t::array_start, result)) != 0))) - { - // explicitly set result to object to cope with [] - result.m_type = value_t::array; - result.m_value = value_t::array; - } - - // read next token - get_token(); - - // closing ] -> we are done - if (last_token == lexer::token_type::end_array) - { - get_token(); - if (callback and not callback(--depth, parse_event_t::array_end, result)) - { - result = basic_json(value_t::discarded); - } - return result; - } - - // no comma is expected here - unexpect(lexer::token_type::value_separator); - - // otherwise: parse values - do - { - // ugly, but could be fixed with loop reorganization - if (last_token == lexer::token_type::value_separator) - { - get_token(); - } - - // parse value - auto value = parse_internal(keep); - if (keep and not value.is_discarded()) - { - result.push_back(std::move(value)); - } - } - while (last_token == lexer::token_type::value_separator); - - // closing ] - expect(lexer::token_type::end_array); - get_token(); - if (keep and callback and not callback(--depth, parse_event_t::array_end, result)) - { - result = basic_json(value_t::discarded); - } - - return result; - } - - case lexer::token_type::literal_null: - { - get_token(); - result.m_type = value_t::null; - break; - } - - case lexer::token_type::value_string: - { - const auto s = m_lexer.get_string(); - get_token(); - result = basic_json(s); - break; - } - - case lexer::token_type::literal_true: - { - get_token(); - result.m_type = value_t::boolean; - result.m_value = true; - break; - } - - case lexer::token_type::literal_false: - { - get_token(); - result.m_type = value_t::boolean; - result.m_value = false; - break; - } - - case lexer::token_type::value_number: - { - m_lexer.get_number(result); - get_token(); - break; - } - - default: - { - // the last token was unexpected - unexpect(last_token); - } - } - - if (keep and callback and not callback(depth, parse_event_t::value, result)) - { - result = basic_json(value_t::discarded); - } - return result; - } - - /// get next token from lexer - typename lexer::token_type get_token() - { - last_token = m_lexer.scan(); - return last_token; - } - - void expect(typename lexer::token_type t) const - { - if (t != last_token) - { - std::string error_msg = "parse error - unexpected "; - error_msg += (last_token == lexer::token_type::parse_error ? ("'" + m_lexer.get_token_string() + - "'") : - lexer::token_type_name(last_token)); - error_msg += "; expected " + lexer::token_type_name(t); - throw std::invalid_argument(error_msg); - } - } - - void unexpect(typename lexer::token_type t) const - { - if (t == last_token) - { - std::string error_msg = "parse error - unexpected "; - error_msg += (last_token == lexer::token_type::parse_error ? ("'" + m_lexer.get_token_string() + - "'") : - lexer::token_type_name(last_token)); - throw std::invalid_argument(error_msg); - } - } - - private: - /// current level of recursion - int depth = 0; - /// callback function - const parser_callback_t callback = nullptr; - /// the type of the last read token - typename lexer::token_type last_token = lexer::token_type::uninitialized; - /// the lexer - lexer m_lexer; - }; - - public: - /*! - @brief JSON Pointer - - A JSON pointer defines a string syntax for identifying a specific value - within a JSON document. It can be used with functions `at` and - `operator[]`. Furthermore, JSON pointers are the base for JSON patches. - - @sa [RFC 6901](https://tools.ietf.org/html/rfc6901) - - @since version 2.0.0 - */ - class json_pointer - { - /// allow basic_json to access private members - friend class basic_json; - - public: - /*! - @brief create JSON pointer - - Create a JSON pointer according to the syntax described in - [Section 3 of RFC6901](https://tools.ietf.org/html/rfc6901#section-3). - - @param[in] s string representing the JSON pointer; if omitted, the - empty string is assumed which references the whole JSON - value - - @throw std::domain_error if reference token is nonempty and does not - begin with a slash (`/`); example: `"JSON pointer must be empty or - begin with /"` - @throw std::domain_error if a tilde (`~`) is not followed by `0` - (representing `~`) or `1` (representing `/`); example: `"escape error: - ~ must be followed with 0 or 1"` - - @liveexample{The example shows the construction several valid JSON - pointers as well as the exceptional behavior.,json_pointer} - - @since version 2.0.0 - */ - explicit json_pointer(const std::string& s = "") - : reference_tokens(split(s)) - {} - - /*! - @brief return a string representation of the JSON pointer - - @invariant For each JSON pointer `ptr`, it holds: - @code {.cpp} - ptr == json_pointer(ptr.to_string()); - @endcode - - @return a string representation of the JSON pointer - - @liveexample{The example shows the result of `to_string`., - json_pointer__to_string} - - @since version 2.0.0 - */ - std::string to_string() const noexcept - { - return std::accumulate(reference_tokens.begin(), - reference_tokens.end(), std::string{}, - [](const std::string & a, const std::string & b) - { - return a + "/" + escape(b); - }); - } - - /// @copydoc to_string() - operator std::string() const - { - return to_string(); - } - - private: - /// remove and return last reference pointer - std::string pop_back() - { - if (is_root()) - { - throw std::domain_error("JSON pointer has no parent"); - } - - auto last = reference_tokens.back(); - reference_tokens.pop_back(); - return last; - } - - /// return whether pointer points to the root document - bool is_root() const - { - return reference_tokens.empty(); - } - - json_pointer top() const - { - if (is_root()) - { - throw std::domain_error("JSON pointer has no parent"); - } - - json_pointer result = *this; - result.reference_tokens = {reference_tokens[0]}; - return result; - } - - /*! - @brief create and return a reference to the pointed to value - - @complexity Linear in the number of reference tokens. - */ - reference get_and_create(reference j) const - { - pointer result = &j; - - // in case no reference tokens exist, return a reference to the - // JSON value j which will be overwritten by a primitive value - for (const auto& reference_token : reference_tokens) - { - switch (result->m_type) - { - case value_t::null: - { - if (reference_token == "0") - { - // start a new array if reference token is 0 - result = &result->operator[](0); - } - else - { - // start a new object otherwise - result = &result->operator[](reference_token); - } - break; - } - - case value_t::object: - { - // create an entry in the object - result = &result->operator[](reference_token); - break; - } - - case value_t::array: - { - // create an entry in the array - result = &result->operator[](static_cast(std::stoi(reference_token))); - break; - } - - /* - The following code is only reached if there exists a - reference token _and_ the current value is primitive. In - this case, we have an error situation, because primitive - values may only occur as single value; that is, with an - empty list of reference tokens. - */ - default: - { - throw std::domain_error("invalid value to unflatten"); - } - } - } - - return *result; - } - - /*! - @brief return a reference to the pointed to value - - @param[in] ptr a JSON value - - @return reference to the JSON value pointed to by the JSON pointer - - @complexity Linear in the length of the JSON pointer. - - @throw std::out_of_range if the JSON pointer can not be resolved - @throw std::domain_error if an array index begins with '0' - @throw std::invalid_argument if an array index was not a number - */ - reference get_unchecked(pointer ptr) const - { - for (const auto& reference_token : reference_tokens) - { - switch (ptr->m_type) - { - case value_t::object: - { - // use unchecked object access - ptr = &ptr->operator[](reference_token); - break; - } - - case value_t::array: - { - // error condition (cf. RFC 6901, Sect. 4) - if (reference_token.size() > 1 and reference_token[0] == '0') - { - throw std::domain_error("array index must not begin with '0'"); - } - - if (reference_token == "-") - { - // explicityly treat "-" as index beyond the end - ptr = &ptr->operator[](ptr->m_value.array->size()); - } - else - { - // convert array index to number; unchecked access - ptr = &ptr->operator[](static_cast(std::stoi(reference_token))); - } - break; - } - - default: - { - throw std::out_of_range("unresolved reference token '" + reference_token + "'"); - } - } - } - - return *ptr; - } - - reference get_checked(pointer ptr) const - { - for (const auto& reference_token : reference_tokens) - { - switch (ptr->m_type) - { - case value_t::object: - { - // note: at performs range check - ptr = &ptr->at(reference_token); - break; - } - - case value_t::array: - { - if (reference_token == "-") - { - // "-" always fails the range check - throw std::out_of_range("array index '-' (" + - std::to_string(ptr->m_value.array->size()) + - ") is out of range"); - } - - // error condition (cf. RFC 6901, Sect. 4) - if (reference_token.size() > 1 and reference_token[0] == '0') - { - throw std::domain_error("array index must not begin with '0'"); - } - - // note: at performs range check - ptr = &ptr->at(static_cast(std::stoi(reference_token))); - break; - } - - default: - { - throw std::out_of_range("unresolved reference token '" + reference_token + "'"); - } - } - } - - return *ptr; - } - - /*! - @brief return a const reference to the pointed to value - - @param[in] ptr a JSON value - - @return const reference to the JSON value pointed to by the JSON - pointer - */ - const_reference get_unchecked(const_pointer ptr) const - { - for (const auto& reference_token : reference_tokens) - { - switch (ptr->m_type) - { - case value_t::object: - { - // use unchecked object access - ptr = &ptr->operator[](reference_token); - break; - } - - case value_t::array: - { - if (reference_token == "-") - { - // "-" cannot be used for const access - throw std::out_of_range("array index '-' (" + - std::to_string(ptr->m_value.array->size()) + - ") is out of range"); - } - - // error condition (cf. RFC 6901, Sect. 4) - if (reference_token.size() > 1 and reference_token[0] == '0') - { - throw std::domain_error("array index must not begin with '0'"); - } - - // use unchecked array access - ptr = &ptr->operator[](static_cast(std::stoi(reference_token))); - break; - } - - default: - { - throw std::out_of_range("unresolved reference token '" + reference_token + "'"); - } - } - } - - return *ptr; - } - - const_reference get_checked(const_pointer ptr) const - { - for (const auto& reference_token : reference_tokens) - { - switch (ptr->m_type) - { - case value_t::object: - { - // note: at performs range check - ptr = &ptr->at(reference_token); - break; - } - - case value_t::array: - { - if (reference_token == "-") - { - // "-" always fails the range check - throw std::out_of_range("array index '-' (" + - std::to_string(ptr->m_value.array->size()) + - ") is out of range"); - } - - // error condition (cf. RFC 6901, Sect. 4) - if (reference_token.size() > 1 and reference_token[0] == '0') - { - throw std::domain_error("array index must not begin with '0'"); - } - - // note: at performs range check - ptr = &ptr->at(static_cast(std::stoi(reference_token))); - break; - } - - default: - { - throw std::out_of_range("unresolved reference token '" + reference_token + "'"); - } - } - } - - return *ptr; - } - - /// split the string input to reference tokens - static std::vector split(const std::string& reference_string) - { - std::vector result; - - // special case: empty reference string -> no reference tokens - if (reference_string.empty()) - { - return result; - } - - // check if nonempty reference string begins with slash - if (reference_string[0] != '/') - { - throw std::domain_error("JSON pointer must be empty or begin with '/'"); - } - - // extract the reference tokens: - // - slash: position of the last read slash (or end of string) - // - start: position after the previous slash - for ( - // search for the first slash after the first character - size_t slash = reference_string.find_first_of("/", 1), - // set the beginning of the first reference token - start = 1; - // we can stop if start == string::npos+1 = 0 - start != 0; - // set the beginning of the next reference token - // (will eventually be 0 if slash == std::string::npos) - start = slash + 1, - // find next slash - slash = reference_string.find_first_of("/", start)) - { - // use the text between the beginning of the reference token - // (start) and the last slash (slash). - auto reference_token = reference_string.substr(start, slash - start); - - // check reference tokens are properly escaped - for (size_t pos = reference_token.find_first_of("~"); - pos != std::string::npos; - pos = reference_token.find_first_of("~", pos + 1)) - { - assert(reference_token[pos] == '~'); - - // ~ must be followed by 0 or 1 - if (pos == reference_token.size() - 1 or - (reference_token[pos + 1] != '0' and - reference_token[pos + 1] != '1')) - { - throw std::domain_error("escape error: '~' must be followed with '0' or '1'"); - } - } - - // finally, store the reference token - unescape(reference_token); - result.push_back(reference_token); - } - - return result; - } - - private: - /*! - @brief replace all occurrences of a substring by another string - - @param[in,out] s the string to manipulate - @param[in] f the substring to replace with @a t - @param[in] t the string to replace @a f - - @return The string @a s where all occurrences of @a f are replaced - with @a t. - - @pre The search string @a f must not be empty. - - @since version 2.0.0 - */ - static void replace_substring(std::string& s, - const std::string& f, - const std::string& t) - { - assert(not f.empty()); - - for ( - size_t pos = s.find(f); // find first occurrence of f - pos != std::string::npos; // make sure f was found - s.replace(pos, f.size(), t), // replace with t - pos = s.find(f, pos + t.size()) // find next occurrence of f - ); - } - - /// escape tilde and slash - static std::string escape(std::string s) - { - // escape "~"" to "~0" and "/" to "~1" - replace_substring(s, "~", "~0"); - replace_substring(s, "/", "~1"); - return s; - } - - /// unescape tilde and slash - static void unescape(std::string& s) - { - // first transform any occurrence of the sequence '~1' to '/' - replace_substring(s, "~1", "/"); - // then transform any occurrence of the sequence '~0' to '~' - replace_substring(s, "~0", "~"); - } - - /*! - @param[in] reference_string the reference string to the current value - @param[in] value the value to consider - @param[in,out] result the result object to insert values to - - @note Empty objects or arrays are flattened to `null`. - */ - static void flatten(const std::string& reference_string, - const basic_json& value, - basic_json& result) - { - switch (value.m_type) - { - case value_t::array: - { - if (value.m_value.array->empty()) - { - // flatten empty array as null - result[reference_string] = nullptr; - } - else - { - // iterate array and use index as reference string - for (size_t i = 0; i < value.m_value.array->size(); ++i) - { - flatten(reference_string + "/" + std::to_string(i), - value.m_value.array->operator[](i), result); - } - } - break; - } - - case value_t::object: - { - if (value.m_value.object->empty()) - { - // flatten empty object as null - result[reference_string] = nullptr; - } - else - { - // iterate object and use keys as reference string - for (const auto& element : *value.m_value.object) - { - flatten(reference_string + "/" + escape(element.first), - element.second, result); - } - } - break; - } - - default: - { - // add primitive value with its reference string - result[reference_string] = value; - break; - } - } - } - - /*! - @param[in] value flattened JSON - - @return unflattened JSON - */ - static basic_json unflatten(const basic_json& value) - { - if (not value.is_object()) - { - throw std::domain_error("only objects can be unflattened"); - } - - basic_json result; - - // iterate the JSON object values - for (const auto& element : *value.m_value.object) - { - if (not element.second.is_primitive()) - { - throw std::domain_error("values in object must be primitive"); - } - - // assign value to reference pointed to by JSON pointer; Note - // that if the JSON pointer is "" (i.e., points to the whole - // value), function get_and_create returns a reference to - // result itself. An assignment will then create a primitive - // value. - json_pointer(element.first).get_and_create(result) = element.second; - } - - return result; - } - - private: - /// the reference tokens - std::vector reference_tokens {}; - }; - - ////////////////////////// - // JSON Pointer support // - ////////////////////////// - - /// @name JSON Pointer functions - /// @{ - - /*! - @brief access specified element via JSON Pointer - - Uses a JSON pointer to retrieve a reference to the respective JSON value. - No bound checking is performed. Similar to @ref operator[](const typename - object_t::key_type&), `null` values are created in arrays and objects if - necessary. - - In particular: - - If the JSON pointer points to an object key that does not exist, it - is created an filled with a `null` value before a reference to it - is returned. - - If the JSON pointer points to an array index that does not exist, it - is created an filled with a `null` value before a reference to it - is returned. All indices between the current maximum and the given - index are also filled with `null`. - - The special value `-` is treated as a synonym for the index past the - end. - - @param[in] ptr a JSON pointer - - @return reference to the element pointed to by @a ptr - - @complexity Constant. - - @throw std::out_of_range if the JSON pointer can not be resolved - @throw std::domain_error if an array index begins with '0' - @throw std::invalid_argument if an array index was not a number - - @liveexample{The behavior is shown in the example.,operatorjson_pointer} - - @since version 2.0.0 - */ - reference operator[](const json_pointer& ptr) - { - return ptr.get_unchecked(this); - } - - /*! - @brief access specified element via JSON Pointer - - Uses a JSON pointer to retrieve a reference to the respective JSON value. - No bound checking is performed. The function does not change the JSON - value; no `null` values are created. In particular, the the special value - `-` yields an exception. - - @param[in] ptr JSON pointer to the desired element - - @return const reference to the element pointed to by @a ptr - - @complexity Constant. - - @throw std::out_of_range if the JSON pointer can not be resolved - @throw std::domain_error if an array index begins with '0' - @throw std::invalid_argument if an array index was not a number - - @liveexample{The behavior is shown in the example.,operatorjson_pointer_const} - - @since version 2.0.0 - */ - const_reference operator[](const json_pointer& ptr) const - { - return ptr.get_unchecked(this); - } - - /*! - @brief access specified element via JSON Pointer - - Returns a reference to the element at with specified JSON pointer @a ptr, - with bounds checking. - - @param[in] ptr JSON pointer to the desired element - - @return reference to the element pointed to by @a ptr - - @complexity Constant. - - @throw std::out_of_range if the JSON pointer can not be resolved - @throw std::domain_error if an array index begins with '0' - @throw std::invalid_argument if an array index was not a number - - @liveexample{The behavior is shown in the example.,at_json_pointer} - - @since version 2.0.0 - */ - reference at(const json_pointer& ptr) - { - return ptr.get_checked(this); - } - - /*! - @brief access specified element via JSON Pointer - - Returns a const reference to the element at with specified JSON pointer @a - ptr, with bounds checking. - - @param[in] ptr JSON pointer to the desired element - - @return reference to the element pointed to by @a ptr - - @complexity Constant. - - @throw std::out_of_range if the JSON pointer can not be resolved - @throw std::domain_error if an array index begins with '0' - @throw std::invalid_argument if an array index was not a number - - @liveexample{The behavior is shown in the example.,at_json_pointer_const} - - @since version 2.0.0 - */ - const_reference at(const json_pointer& ptr) const - { - return ptr.get_checked(this); - } - - /*! - @brief return flattened JSON value - - The function creates a JSON object whose keys are JSON pointers (see [RFC - 6901](https://tools.ietf.org/html/rfc6901)) and whose values are all - primitive. The original JSON value can be restored using the @ref - unflatten() function. - - @return an object that maps JSON pointers to primitve values - - @note Empty objects and arrays are flattened to `null` and will not be - reconstructed correctly by the @ref unflatten() function. - - @complexity Linear in the size the JSON value. - - @liveexample{The following code shows how a JSON object is flattened to an - object whose keys consist of JSON pointers.,flatten} - - @sa @ref unflatten() for the reverse function - - @since version 2.0.0 - */ - basic_json flatten() const - { - basic_json result(value_t::object); - json_pointer::flatten("", *this, result); - return result; - } - - /*! - @brief unflatten a previously flattened JSON value - - The function restores the arbitrary nesting of a JSON value that has been - flattened before using the @ref flatten() function. The JSON value must - meet certain constraints: - 1. The value must be an object. - 2. The keys must be JSON pointers (see - [RFC 6901](https://tools.ietf.org/html/rfc6901)) - 3. The mapped values must be primitive JSON types. - - @return the original JSON from a flattened version - - @note Empty objects and arrays are flattened by @ref flatten() to `null` - values and can not unflattened to their original type. Apart from - this example, for a JSON value `j`, the following is always true: - `j == j.flatten().unflatten()`. - - @complexity Linear in the size the JSON value. - - @liveexample{The following code shows how a flattened JSON object is - unflattened into the original nested JSON object.,unflatten} - - @sa @ref flatten() for the reverse function - - @since version 2.0.0 - */ - basic_json unflatten() const - { - return json_pointer::unflatten(*this); - } - - /// @} - - ////////////////////////// - // JSON Patch functions // - ////////////////////////// - - /// @name JSON Patch functions - /// @{ - - /*! - @brief applies a JSON patch - - [JSON Patch](http://jsonpatch.com) defines a JSON document structure for - expressing a sequence of operations to apply to a JSON) document. With - this funcion, a JSON Patch is applied to the current JSON value by - executing all operations from the patch. - - @param[in] json_patch JSON patch document - @return patched document - - @note The application of a patch is atomic: Either all operations succeed - and the patched document is returned or an exception is thrown. In - any case, the original value is not changed: the patch is applied - to a copy of the value. - - @throw std::out_of_range if a JSON pointer inside the patch could not - be resolved successfully in the current JSON value; example: `"key baz - not found"` - @throw invalid_argument if the JSON patch is malformed (e.g., mandatory - attributes are missing); example: `"operation add must have member path"` - - @complexity Linear in the size of the JSON value and the length of the - JSON patch. As usually only a fraction of the JSON value is affected by - the patch, the complexity can usually be neglected. - - @liveexample{The following code shows how a JSON patch is applied to a - value.,patch} - - @sa @ref diff -- create a JSON patch by comparing two JSON values - - @sa [RFC 6902 (JSON Patch)](https://tools.ietf.org/html/rfc6902) - @sa [RFC 6901 (JSON Pointer)](https://tools.ietf.org/html/rfc6901) - - @since version 2.0.0 - */ - basic_json patch(const basic_json& json_patch) const - { - // make a working copy to apply the patch to - basic_json result = *this; - - // the valid JSON Patch operations - enum class patch_operations {add, remove, replace, move, copy, test, invalid}; - - const auto get_op = [](const std::string op) - { - if (op == "add") - { - return patch_operations::add; - } - if (op == "remove") - { - return patch_operations::remove; - } - if (op == "replace") - { - return patch_operations::replace; - } - if (op == "move") - { - return patch_operations::move; - } - if (op == "copy") - { - return patch_operations::copy; - } - if (op == "test") - { - return patch_operations::test; - } - - return patch_operations::invalid; - }; - - // wrapper for "add" operation; add value at ptr - const auto operation_add = [&result](json_pointer & ptr, basic_json val) - { - // adding to the root of the target document means replacing it - if (ptr.is_root()) - { - result = val; - } - else - { - // make sure the top element of the pointer exists - json_pointer top_pointer = ptr.top(); - if (top_pointer != ptr) - { - result.at(top_pointer); - } - - // get reference to parent of JSON pointer ptr - const auto last_path = ptr.pop_back(); - basic_json& parent = result[ptr]; - - switch (parent.m_type) - { - case value_t::null: - case value_t::object: - { - // use operator[] to add value - parent[last_path] = val; - break; - } - - case value_t::array: - { - if (last_path == "-") - { - // special case: append to back - parent.push_back(val); - } - else - { - const auto idx = std::stoi(last_path); - if (static_cast(idx) > parent.size()) - { - // avoid undefined behavior - throw std::out_of_range("array index " + std::to_string(idx) + " is out of range"); - } - else - { - // default case: insert add offset - parent.insert(parent.begin() + static_cast(idx), val); - } - } - break; - } - - default: - { - // if there exists a parent it cannot be primitive - assert(false); // LCOV_EXCL_LINE - } - } - } - }; - - // wrapper for "remove" operation; remove value at ptr - const auto operation_remove = [&result](json_pointer & ptr) - { - // get reference to parent of JSON pointer ptr - const auto last_path = ptr.pop_back(); - basic_json& parent = result.at(ptr); - - // remove child - if (parent.is_object()) - { - // perform range check - auto it = parent.find(last_path); - if (it != parent.end()) - { - parent.erase(it); - } - else - { - throw std::out_of_range("key '" + last_path + "' not found"); - } - } - else if (parent.is_array()) - { - // note erase performs range check - parent.erase(static_cast(std::stoi(last_path))); - } - }; - - // type check - if (not json_patch.is_array()) - { - // a JSON patch must be an array of objects - throw std::invalid_argument("JSON patch must be an array of objects"); - } - - // iterate and apply th eoperations - for (const auto& val : json_patch) - { - // wrapper to get a value for an operation - const auto get_value = [&val](const std::string & op, - const std::string & member, - bool string_type) -> basic_json& - { - // find value - auto it = val.m_value.object->find(member); - - // context-sensitive error message - const auto error_msg = (op == "op") ? "operation" : "operation '" + op + "'"; - - // check if desired value is present - if (it == val.m_value.object->end()) - { - throw std::invalid_argument(error_msg + " must have member '" + member + "'"); - } - - // check if result is of type string - if (string_type and not it->second.is_string()) - { - throw std::invalid_argument(error_msg + " must have string member '" + member + "'"); - } - - // no error: return value - return it->second; - }; - - // type check - if (not val.is_object()) - { - throw std::invalid_argument("JSON patch must be an array of objects"); - } - - // collect mandatory members - const std::string op = get_value("op", "op", true); - const std::string path = get_value(op, "path", true); - json_pointer ptr(path); - - switch (get_op(op)) - { - case patch_operations::add: - { - operation_add(ptr, get_value("add", "value", false)); - break; - } - - case patch_operations::remove: - { - operation_remove(ptr); - break; - } - - case patch_operations::replace: - { - // the "path" location must exist - use at() - result.at(ptr) = get_value("replace", "value", false); - break; - } - - case patch_operations::move: - { - const std::string from_path = get_value("move", "from", true); - json_pointer from_ptr(from_path); - - // the "from" location must exist - use at() - basic_json v = result.at(from_ptr); - - // The move operation is functionally identical to a - // "remove" operation on the "from" location, followed - // immediately by an "add" operation at the target - // location with the value that was just removed. - operation_remove(from_ptr); - operation_add(ptr, v); - break; - } - - case patch_operations::copy: - { - const std::string from_path = get_value("copy", "from", true); - const json_pointer from_ptr(from_path); - - // the "from" location must exist - use at() - result[ptr] = result.at(from_ptr); - break; - } - - case patch_operations::test: - { - bool success = false; - try - { - // check if "value" matches the one at "path" - // the "path" location must exist - use at() - success = (result.at(ptr) == get_value("test", "value", false)); - } - catch (std::out_of_range&) - { - // ignore out of range errors: success remains false - } - - // throw an exception if test fails - if (not success) - { - throw std::domain_error("unsuccessful: " + val.dump()); - } - - break; - } - - case patch_operations::invalid: - { - // op must be "add", "remove", "replace", "move", "copy", or - // "test" - throw std::invalid_argument("operation value '" + op + "' is invalid"); - } - } - } - - return result; - } - - /*! - @brief creates a diff as a JSON patch - - Creates a [JSON Patch](http://jsonpatch.com) so that value @a source can - be changed into the value @a target by calling @ref patch function. - - @invariant For two JSON values @a source and @a target, the following code - yields always `true`: - @code {.cpp} - source.patch(diff(source, target)) == target; - @endcode - - @note Currently, only `remove`, `add`, and `replace` operations are - generated. - - @param[in] source JSON value to copare from - @param[in] target JSON value to copare against - @param[in] path helper value to create JSON pointers - - @return a JSON patch to convert the @a source to @a target - - @complexity Linear in the lengths of @a source and @a target. - - @liveexample{The following code shows how a JSON patch is created as a - diff for two JSON values.,diff} - - @sa @ref patch -- apply a JSON patch - - @sa [RFC 6902 (JSON Patch)](https://tools.ietf.org/html/rfc6902) - - @since version 2.0.0 - */ - static basic_json diff(const basic_json& source, - const basic_json& target, - const std::string& path = "") - { - // the patch - basic_json result(value_t::array); - - // if the values are the same, return empty patch - if (source == target) - { - return result; - } - - if (source.type() != target.type()) - { - // different types: replace value - result.push_back( - { - {"op", "replace"}, - {"path", path}, - {"value", target} - }); - } - else - { - switch (source.type()) - { - case value_t::array: - { - // first pass: traverse common elements - size_t i = 0; - while (i < source.size() and i < target.size()) - { - // recursive call to compare array values at index i - auto temp_diff = diff(source[i], target[i], path + "/" + std::to_string(i)); - result.insert(result.end(), temp_diff.begin(), temp_diff.end()); - ++i; - } - - // i now reached the end of at least one array - // in a second pass, traverse the remaining elements - - // remove my remaining elements - const auto end_index = static_cast(result.size()); - while (i < source.size()) - { - // add operations in reverse order to avoid invalid - // indices - result.insert(result.begin() + end_index, object( - { - {"op", "remove"}, - {"path", path + "/" + std::to_string(i)} - })); - ++i; - } - - // add other remaining elements - while (i < target.size()) - { - result.push_back( - { - {"op", "add"}, - {"path", path + "/" + std::to_string(i)}, - {"value", target[i]} - }); - ++i; - } - - break; - } - - case value_t::object: - { - // first pass: traverse this object's elements - for (auto it = source.begin(); it != source.end(); ++it) - { - // escape the key name to be used in a JSON patch - const auto key = json_pointer::escape(it.key()); - - if (target.find(it.key()) != target.end()) - { - // recursive call to compare object values at key it - auto temp_diff = diff(it.value(), target[it.key()], path + "/" + key); - result.insert(result.end(), temp_diff.begin(), temp_diff.end()); - } - else - { - // found a key that is not in o -> remove it - result.push_back(object( - { - {"op", "remove"}, - {"path", path + "/" + key} - })); - } - } - - // second pass: traverse other object's elements - for (auto it = target.begin(); it != target.end(); ++it) - { - if (source.find(it.key()) == source.end()) - { - // found a key that is not in this -> add it - const auto key = json_pointer::escape(it.key()); - result.push_back( - { - {"op", "add"}, - {"path", path + "/" + key}, - {"value", it.value()} - }); - } - } - - break; - } - - default: - { - // both primitive type: replace value - result.push_back( - { - {"op", "replace"}, - {"path", path}, - {"value", target} - }); - break; - } - } - } - - return result; - } - - /// @} -}; - - -///////////// -// presets // -///////////// - -/*! -@brief default JSON class - -This type is the default specialization of the @ref basic_json class which -uses the standard template types. - -@since version 1.0.0 -*/ -using json = basic_json<>; -} - - -/////////////////////// -// nonmember support // -/////////////////////// - -// specialization of std::swap, and std::hash -namespace std -{ -/*! -@brief exchanges the values of two JSON objects - -@since version 1.0.0 -*/ -template<> -inline void swap(nlohmann::json& j1, - nlohmann::json& j2) noexcept( - is_nothrow_move_constructible::value and - is_nothrow_move_assignable::value - ) -{ - j1.swap(j2); -} - -/// hash value for JSON objects -template<> -struct hash -{ - /*! - @brief return a hash value for a JSON object - - @since version 1.0.0 - */ - std::size_t operator()(const nlohmann::json& j) const - { - // a naive hashing via the string representation - const auto& h = hash(); - return h(j.dump()); - } -}; -} - -/*! -@brief user-defined string literal for JSON values - -This operator implements a user-defined string literal for JSON objects. It -can be used by adding `"_json"` to a string literal and returns a JSON object -if no parse error occurred. - -@param[in] s a string representation of a JSON object -@return a JSON object - -@since version 1.0.0 -*/ -inline nlohmann::json operator "" _json(const char* s, std::size_t) -{ - return nlohmann::json::parse(s); -} - -/*! -@brief user-defined string literal for JSON pointer - -This operator implements a user-defined string literal for JSON Pointers. It -can be used by adding `"_json_pointer"` to a string literal and returns a JSON pointer -object if no parse error occurred. - -@param[in] s a string representation of a JSON Pointer -@return a JSON pointer object - -@since version 2.0.0 -*/ -inline nlohmann::json::json_pointer operator "" _json_pointer(const char* s, std::size_t) -{ - return nlohmann::json::json_pointer(s); -} - -// restore GCC/clang diagnostic settings -#if defined(__clang__) || defined(__GNUC__) || defined(__GNUG__) - #pragma GCC diagnostic pop -#endif - -#endif diff --git a/XcodeZombieCodeAnalyzer/XcodeZombieCodeAnalyzer/main.cpp b/XcodeZombieCodeAnalyzer/XcodeZombieCodeAnalyzer/main.cpp index c4be717..bd0e4bf 100644 --- a/XcodeZombieCodeAnalyzer/XcodeZombieCodeAnalyzer/main.cpp +++ b/XcodeZombieCodeAnalyzer/XcodeZombieCodeAnalyzer/main.cpp @@ -8,22 +8,32 @@ #include #include #include +#include +#include "clang/Tooling/CommonOptionsParser.h" +#include "clang/tooling/Tooling.h" +#include "llvm/support/CommandLine.h" #include "json.hpp" #include "ZombieCodeUtil.hpp" +#include "ClangZombieCodePlugin.hpp" using namespace std; using namespace nlohmann; +using namespace clang::tooling; +using llvm::cl::OptionCategory; +using namespace ZombieCode; + +extern vector getSourcesByProject(string prjPath); void appendUsedClsMethodCall(string clsMethod,string callerClsMethod,json &clsMethodJson,json &clsInterfHierachyJson,json &clsWithAnySrcCodeJson,json &usedClsMethodJson,json &unusedClsMethodJson); -string kKeyInterfSelDictFilename = "filename"; -string kKeyInterfSelDictSourceCode = "sourceCode"; -string kKeyInterfSelDictRange = "range"; -string kKeyInterfSelDictCallees = "callee"; -string kKeyInterfSelDictSuperClass = "superClass"; -string kKeyInterfSelDictProtos = "protos"; -string kKeyInterfSelDictInterfs = "interfs"; -string kKeyInterfSelDictIsInSrcDir = "isInSrcDir"; +extern string kKeyInterfSelDictFilename; +extern string kKeyInterfSelDictSourceCode; +extern string kKeyInterfSelDictRange; +extern string kKeyInterfSelDictCallees; +extern string kKeyInterfSelDictSuperClass; +extern string kKeyInterfSelDictProtos; +extern string kKeyInterfSelDictInterfs; +extern string kKeyInterfSelDictIsInSrcDir; string kKeyInterfSelDictNotifCallers = "notifCallers"; json implicitCallStackJson = { @@ -174,14 +184,14 @@ string joinedClsMethod(bool isInstanceMethod,string cls,string sel){ } string getClsMethodToken(string clsMethod,ClsMethodTokenType tokenType){ - clsMethod = trim(clsMethod); + clsMethod = trim_copy(clsMethod); switch (tokenType) { case ClsMethodTokenMethodType: { size_t pos = clsMethod.find("["); string str = clsMethod.substr(0,pos); if(pos != string::npos) - return trim(str); + return trim_copy(str); break; } case ClsMethodTokenClass: @@ -192,7 +202,7 @@ string getClsMethodToken(string clsMethod,ClsMethodTokenType tokenType){ pos = str.find(" "); if(pos != string::npos){ str = str.substr(0,pos); - return trim(str); + return trim_copy(str); } } break; @@ -202,7 +212,7 @@ string getClsMethodToken(string clsMethod,ClsMethodTokenType tokenType){ size_t pos = clsMethod.rfind(" "); if(pos != string::npos){ string str = clsMethod.substr(pos+1,clsMethod.length()-pos-2); - return trim(str); + return trim_copy(str); } break; } @@ -224,17 +234,6 @@ string exec(const char* cmd) { return result; } -vector split(const string &s, char delim) { - vector elems; - stringstream ss; - ss.str(s); - string item; - while (getline(ss, item, delim)) { - elems.push_back(item); - } - return elems; -} - json readJsonFromFile(const string& file){ ifstream ifs(file); string str((istreambuf_iterator(ifs)), @@ -654,13 +653,69 @@ string searchUsedCallerByNotification(string& clsMethod,json& clsMethodAddNotifs return ""; } +int generateJsonWithLibTooling(int argc,char *argv[]){ + OptionCategory oc("",""); + const char **pargv = const_cast(argv); + CommonOptionsParser op(argc,pargv,oc); + // create a new Clang Tool instance (a LibTooling environment) + string wspprjFile = op.getSourcePathList().at(0); + size_t npos = wspprjFile.rfind("/"); + if(npos==string::npos){ + return 1; + } + string directory = wspprjFile.substr(0,npos),wspprjFilename = wspprjFile.substr(npos+1,wspprjFile.length()-npos); + + vector projsVec; + //Parse Workspace + if(wspprjFilename.find(".xcworkspace") == wspprjFilename.length()-string(".xcworkspace").length()){ + ifstream ifs(directory+"/"+wspprjFilename+"/contents.xcworkspacedata"); + string content((istreambuf_iterator(ifs)),(istreambuf_iterator() ) ); + vector lineVec = split(content,'\n'); + + regex prjRegex("^[\\s]+location = \"group:[\\w\\/]+[\\.]xcodeproj\">$"); + for(string line : lineVec){ + smatch sm; + regex_match(line,sm,prjRegex); + for (std::smatch::iterator it = sm.begin(); it!=sm.end(); ++it) { + string projFile = *it; + string preFlagStr("group:"); + size_t pos1 = projFile.find(preFlagStr),pos2 = projFile.find("\">"); + if(pos1!=string::npos && pos2!=string::npos){ + projsVec.push_back(projFile.substr(pos1+preFlagStr.length(),pos2-pos1-preFlagStr.length())); + } + } + } + } + else if(wspprjFilename.find(".xcodeproj") == wspprjFilename.length()-string(".xcodeproj").length()){ + projsVec.push_back(wspprjFilename); + } + + vector sourceFiles; + //Parse Project + for(string projFileRelPath : projsVec){ + string pbxprojFile = directory+"/"+projFileRelPath; + if(!file_exists(pbxprojFile)) + continue; + vector srcFilesVec = getSourcesByProject(pbxprojFile); + sourceFiles.insert(sourceFiles.end(),srcFilesVec.begin(),srcFilesVec.end()); + } + + ClangTool Tool(op.getCompilations(), sourceFiles); + + // run the Clang Tool, creating a new FrontendAction (explained below) + std::unique_ptr FrontendFactory=newFrontendActionFactory(); + int result = Tool.run(FrontendFactory.get()); + return 0; +} int main(int argc,char *argv[]){ - if(argc!=3){ - cout<<"Usage:"<<"postanalyze your-path-of-jsonparts-for-analyzing your-appdelegate-name"<