1// 2// KBFormatter.m 3// Keybase 4// 5// Created by Gabriel on 5/20/15. 6// Copyright (c) 2015 Gabriel Handford. All rights reserved. 7// 8 9#import "KBFormatter.h" 10 11#import <ObjectiveSugar/ObjectiveSugar.h> 12#import <objc/objc-runtime.h> 13 14@interface KBFormatter (NSDictionaryCreation) 15- (NSDictionary *)toDictionary; 16@end 17 18 19@implementation KBFormatter 20 21- (NSString *)format:(id)obj { 22 return [self format:obj level:-1]; 23} 24 25- (NSString *)format:(id)obj level:(NSInteger)level { 26 if (!obj) { 27 return @"null"; 28 } else if (obj == (void*)kCFBooleanTrue) { 29 return @"true"; 30 } else if (obj == (void*)kCFBooleanFalse) { 31 return @"false"; 32 } else if ([obj isKindOfClass:NSNull.class]) { 33 return @"null"; 34 } else if ([obj isKindOfClass:NSString.class]) { 35 return NSStringWithFormat(@"\"%@\"", obj); 36 } else if ([obj isKindOfClass:NSArray.class]) { 37 return [self formatArray:obj level:level]; 38 } else if ([obj isKindOfClass:GHODictionary.class]) { 39 return [self formatDictionary:obj level:level+1]; 40 } else if ([obj isKindOfClass:NSDictionary.class]) { 41 return [self formatDictionary:obj level:level+1]; 42 } else if ([obj respondsToSelector:@selector(toDictionary)]) { 43 id dict = [obj toDictionary]; 44 return [self formatDictionary:dict level:level+1]; 45 //} else if ([obj isKindOfClass:NSData.class]) { 46 // return KBHexString(obj, @""); 47 } else if ([obj conformsToProtocol:@protocol(NSCopying)]) { // Default for core types 48 return [obj description]; 49 } else { 50 GHODictionary *dict = KBObjectToDictionary(obj, YES); 51 if ([dict count] == 0) return [obj description]; 52 return [self formatDictionary:dict level:level+1]; 53 } 54} 55 56- (NSString *)formatArray:(NSArray *)array level:(NSInteger)level { 57 if (!array) return @"null"; 58 if ([array count] == 0) return @"[]"; 59 60 NSString *astr = [[array map:^(id obj) { return [self format:obj level:level]; }] join:@", "]; 61 return NSStringWithFormat(@"[%@]", astr); 62} 63 64- (NSString *)formatDictionary:(id)dict level:(NSInteger)level { 65 if (!dict) return @""; 66 if ([dict count] == 0) return @"{}"; 67 NSString *prefix = [@"" stringByPaddingToLength:(level+1)*2 withString:@" " startingAtIndex:0]; 68 NSString *endPrefix = [@"" stringByPaddingToLength:level*2 withString:@" " startingAtIndex:0]; 69 70 NSArray *dictStr = [dict map:^id(id key, id value) { 71 return NSStringWithFormat(@"%@: %@", [self format:key level:level], [self format:value level:level]); 72 }]; 73 74 NSString *str = NSStringWithFormat(@"{\n%@%@\n%@}", prefix, [dictStr join:NSStringWithFormat(@",\n%@", prefix)], endPrefix); 75 return str; 76} 77 78@end 79 80NSString *KBDescription(id obj) { 81 KBFormatter *formatter = [[KBFormatter alloc] init]; 82 return [formatter format:obj]; 83} 84 85GHODictionary *KBObjectToDictionary(id obj, BOOL includeNull) { 86 NSArray *propertyNames = KBPropertyNames([obj class]); 87 GHODictionary *odict = [[GHODictionary alloc] initWithCapacity:[propertyNames count]]; 88 for (NSString *propertyName in propertyNames) { 89 id value = [obj valueForKey:propertyName]; 90 if (!value && includeNull) odict[propertyName] = [NSNull null]; 91 else if (value) odict[propertyName] = value; 92 } 93 return odict; 94} 95 96NSString *KBClassNameOfPropertyNamed(Class clazz, NSString *propertyName) { 97 objc_property_t property = class_getProperty(clazz, propertyName.UTF8String); 98 NSString *propertyAttributes = [NSString stringWithCString:property_getAttributes(property) encoding:NSUTF8StringEncoding]; 99 NSArray *splitPropertyAttributes = [propertyAttributes componentsSeparatedByString:@","]; 100 if (splitPropertyAttributes.count > 0) { 101 // xcdoc://ios//library/prerelease/ios/documentation/Cocoa/Conceptual/ObjCRuntimeGuide/Articles/ocrtPropertyIntrospection.html 102 NSString *encodeType = splitPropertyAttributes[0]; 103 NSArray *splitEncodeType = [encodeType componentsSeparatedByString:@"\""]; 104 if (splitEncodeType.count > 1) { 105 NSString *className = splitEncodeType[1]; 106 return className; 107 } 108 } 109 return nil; 110} 111 112NSArray *KBPropertyNames(Class clazz) { 113 unsigned int count; 114 objc_property_t *properties = class_copyPropertyList(clazz, &count); 115 NSMutableArray *propertyNames = [NSMutableArray arrayWithCapacity:count]; 116 for (NSUInteger i = 0; i < count; i++) { 117 objc_property_t property = properties[i]; 118 const char *propName = property_getName(property); 119 NSString *propertyName = [NSString stringWithCString:propName encoding:NSUTF8StringEncoding]; 120 [propertyNames addObject:propertyName]; 121 } 122 free(properties); 123 return propertyNames; 124} 125 126NSString *KBHexString(NSData *data, NSString *defaultValue) { 127 if (!data) return defaultValue; 128 if ([data length] == 0) return defaultValue; 129 NSMutableString *hexString = [NSMutableString stringWithCapacity:[data length] * 2]; 130 for (NSUInteger i = 0; i < [data length]; ++i) { 131 [hexString appendFormat:@"%02X", *((uint8_t *)[data bytes] + i)]; 132 } 133 return [hexString lowercaseString]; 134} 135 136NSData *KBHexData(NSString *s) { 137 if ((s.length % 2) != 0) { 138 return nil; 139 } 140 141 const char *chars = [s UTF8String]; 142 NSMutableData *data = [NSMutableData dataWithCapacity:s.length / 2]; 143 char byteChars[3] = {0, 0, 0}; 144 unsigned long wholeByte; 145 146 for (int i = 0; i < s.length; i += 2) { 147 byteChars[0] = chars[i]; 148 byteChars[1] = chars[i + 1]; 149 wholeByte = strtoul(byteChars, NULL, 16); 150 [data appendBytes:&wholeByte length:1]; 151 } 152 153 return data; 154} 155