Bgerstle has uploaded a new change for review. https://gerrit.wikimedia.org/r/215114
Change subject: hygiene: hash & equality utils plus title & site cleanup ...................................................................... hygiene: hash & equality utils plus title & site cleanup This is a boyscout patch in preparation for T100687. - Update bitwise rotation implementation to something that performs better - Add macro to remove potential for error when duplicating properties (WMF_EQUAL_PROPERTIES) - Integrate bitwise rotation & comparison updates to MWKSite & MWKTitle - Deprecate & clean up MWKSite & MWKTitle cruft Change-Id: If5906e1ad8f6f39899e3efbb703ce660bceeb6d9 --- M MediaWikiKit/MediaWikiKit/MWKImageInfo.m M MediaWikiKit/MediaWikiKit/MWKSite.h M MediaWikiKit/MediaWikiKit/MWKSite.m M MediaWikiKit/MediaWikiKit/MWKTitle.h M MediaWikiKit/MediaWikiKit/MWKTitle.m M MediaWikiKit/MediaWikiKitTests/MWKTitleTests.m M Wikipedia.xcodeproj/project.pbxproj M Wikipedia/Categories/MWKArticle+WMFSharing.m M Wikipedia/Categories/NSArray+WMFExtensions.h M Wikipedia/Categories/NSArray+WMFExtensions.m M Wikipedia/Categories/NSString+Extras.h M Wikipedia/Categories/NSString+Extras.m M Wikipedia/Networking/Fetchers/ArticleFetcher.m M Wikipedia/View Controllers/History/HistoryViewController.m M Wikipedia/View Controllers/Navigation/Center/CenterNavController.h M Wikipedia/View Controllers/Navigation/Center/CenterNavController.m M Wikipedia/View Controllers/SavedPages/SavedPagesViewController.m M Wikipedia/View Controllers/WebView/WebViewController.m A Wikipedia/mw-utils/NSObjectUtilities.h A Wikipedia/mw-utils/WMFComparison.h A Wikipedia/mw-utils/WMFHashing.h A Wikipedia/mw-utils/WMFPageUtilities.h A Wikipedia/mw-utils/WMFPageUtilities.m M Wikipedia/mw-utils/WikipediaAppUtils.h M Wikipedia/mw-utils/WikipediaAppUtils.m M WikipediaUnitTests/CircularBitwiseRotationTests.m M WikipediaUnitTests/MWKArticle+WMFSharingTests.m 27 files changed, 390 insertions(+), 253 deletions(-) git pull ssh://gerrit.wikimedia.org:29418/apps/ios/wikipedia refs/changes/14/215114/1 diff --git a/MediaWikiKit/MediaWikiKit/MWKImageInfo.m b/MediaWikiKit/MediaWikiKit/MWKImageInfo.m index 023ebb3..5d64ec1 100644 --- a/MediaWikiKit/MediaWikiKit/MWKImageInfo.m +++ b/MediaWikiKit/MediaWikiKit/MWKImageInfo.m @@ -133,7 +133,7 @@ } - (NSUInteger)hash { - return [self.canonicalPageTitle hash] ^ CircularBitwiseRotation([self.imageURL hash], 1); + return self.canonicalPageTitle.hash ^ flipBitsWithAdditionalRotation(self.imageURL.hash, 1); } - (NSString*)description { diff --git a/MediaWikiKit/MediaWikiKit/MWKSite.h b/MediaWikiKit/MediaWikiKit/MWKSite.h index f57e3b3..4b8ea70 100644 --- a/MediaWikiKit/MediaWikiKit/MWKSite.h +++ b/MediaWikiKit/MediaWikiKit/MWKSite.h @@ -1,26 +1,49 @@ // Created by Brion on 11/6/13. // Copyright (c) 2013 Wikimedia Foundation. Provided under MIT-style license; please copy and modify! -#pragma once - #import <Foundation/Foundation.h> -// forward decl +NS_ASSUME_NONNULL_BEGIN + +extern NSString* const WMFDefaultSiteDomain; + @class MWKTitle; @class MWKUser; @interface MWKSite : NSObject -@property (readonly, copy, nonatomic) NSString* domain; -@property (readonly, copy, nonatomic) NSString* language; +@property (nonatomic, copy, readonly) NSString* domain; +@property (nonatomic, copy, readonly) NSString* language; -- (instancetype)initWithDomain:(NSString*)domain language:(NSString*)language; +- (instancetype)initWithDomain:(NSString*)domain language:(NSString*)language NS_DESIGNATED_INITIALIZER; +/// Convenience factory method wrapping the designated initializer. ++ (instancetype)siteWithDomain:(NSString*)domain language:(NSString*)language; + +/// @return A site with the default domain and the language code returned by @c locale. ++ (instancetype)siteWithLocale:(NSLocale*)locale; + +/// @return A site with the default domain and the current locale's language code. ++ (instancetype)siteWithCurrentLocale; + +- (BOOL)isEqualToSite:(MWKSite* __nullable)other; + +/// +/// @name Title Factory Convenience Methods +/// + +/** + * @return A title initialized with the receiver as its @c site. + * @see -[MWKTitle initWithString:site:] + */ - (MWKTitle*)titleWithString:(NSString*)string; + +/** + * @return A title initialized with the receiver as its @c site. + * @see -[MWKTitle initWithString:site:] + */ - (MWKTitle*)titleWithInternalLink:(NSString*)path; -+ (MWKSite*)siteWithDomain:(NSString*)domain language:(NSString*)language; - -- (BOOL)isEqualToSite:(MWKSite*)other; - @end + +NS_ASSUME_NONNULL_END diff --git a/MediaWikiKit/MediaWikiKit/MWKSite.m b/MediaWikiKit/MediaWikiKit/MWKSite.m index aa33a74..ecfd382 100644 --- a/MediaWikiKit/MediaWikiKit/MWKSite.m +++ b/MediaWikiKit/MediaWikiKit/MWKSite.m @@ -2,7 +2,9 @@ // Copyright (c) 2013 Wikimedia Foundation. Provided under MIT-style license; please copy and modify! #import "MediaWikiKit.h" -#import "WikipediaAppUtils.h" +#import "NSObjectUtilities.h" + +NSString* const WMFDefaultSiteDomain = @"wikipedia.org"; @interface MWKSite () @@ -13,23 +15,9 @@ @implementation MWKSite -#pragma mark - Setup - -+ (MWKSite*)siteWithDomain:(NSString*)domain language:(NSString*)language { - static NSMutableDictionary* cachedSites = nil; - if (cachedSites == nil) { - cachedSites = [[NSMutableDictionary alloc] init]; - } - NSString* key = [NSString stringWithFormat:@"%@:%@", domain, language]; - MWKSite* site = cachedSites[key]; - if (site == nil) { - site = [[MWKSite alloc] initWithDomain:domain language:language]; - cachedSites[key] = site; - } - return site; -} - - (instancetype)initWithDomain:(NSString*)domain language:(NSString*)language { + NSParameterAssert(domain.length); + NSParameterAssert(language.length); self = [super init]; if (self) { self.domain = domain; @@ -38,22 +26,26 @@ return self; } ++ (MWKSite*)siteWithDomain:(NSString*)domain language:(NSString*)language { + return [[MWKSite alloc] initWithDomain:domain language:language]; +} + ++ (instancetype)siteWithCurrentLocale { + return [self siteWithLocale:[NSLocale currentLocale]]; +} + ++ (instancetype)siteWithLocale:(NSLocale*)locale { + return [self siteWithDomain:WMFDefaultSiteDomain language:[locale objectForKey:NSLocaleLanguageCode]]; +} + #pragma mark - Title Helpers - (MWKTitle*)titleWithString:(NSString*)string { return [MWKTitle titleWithString:string site:self]; } -static NSString* localLinkPrefix = @"/wiki/"; - - (MWKTitle*)titleWithInternalLink:(NSString*)path { - if ([path hasPrefix:localLinkPrefix]) { - NSString* remainder = [[path substringFromIndex:localLinkPrefix.length] - stringByReplacingPercentEscapesUsingEncoding:NSUTF8StringEncoding]; - return [self titleWithString:remainder]; - } else { - @throw [NSException exceptionWithName:@"SiteBadLinkFormatException" reason:@"unexpected local link format" userInfo:nil]; - } + return [[MWKTitle alloc] initWithInternalLink:path site:self]; } #pragma mark - NSObject @@ -69,12 +61,12 @@ } - (BOOL)isEqualToSite:(MWKSite*)other { - return [self.domain isEqualToString:other.domain] - && [self.language isEqualToString:other.language]; + return WMF_EQUAL_PROPERTIES(self, language, isEqualToString:, other) + && WMF_EQUAL_PROPERTIES(self, domain, isEqualToString:, other); } - (NSUInteger)hash { - return [self.domain hash] ^ CircularBitwiseRotation([self.language hash], 1); + return self.domain.hash ^ flipBitsWithAdditionalRotation(self.language.hash, 1); } @end diff --git a/MediaWikiKit/MediaWikiKit/MWKTitle.h b/MediaWikiKit/MediaWikiKit/MWKTitle.h index 05f6a3a..1c9faab 100644 --- a/MediaWikiKit/MediaWikiKit/MWKTitle.h +++ b/MediaWikiKit/MediaWikiKit/MWKTitle.h @@ -1,78 +1,75 @@ // Created by Brion on 11/1/13. // Copyright (c) 2013 Wikimedia Foundation. Provided under MIT-style license; please copy and modify! -#pragma once - #import <Foundation/Foundation.h> - #import "MWKSite.h" + +NS_ASSUME_NONNULL_BEGIN @interface MWKTitle : NSObject <NSCopying> -/** - * Initialize a new MWKTitle object from string input - */ -- (instancetype)initWithString:(NSString*)str site:(MWKSite*)site; - -/** - * Create a new MWKTitle object from string input - */ -+ (MWKTitle*)titleWithString:(NSString*)str site:(MWKSite*)site; - -/** - * Normalize a title string portion to text form - */ -+ (NSString*)normalize:(NSString*)str; - -/** - * The site this title belongs to - */ +/// The site this title belongs to @property (readonly, strong, nonatomic) MWKSite* site; -/** - * Normalized title component only (decoded, no underscores) - */ +/// Normalized title component only (decoded, no underscores) @property (readonly, copy, nonatomic) NSString* text; -/** - * Fragment (component after the '#') - * Warning: fragment may be nil! - */ -@property (readonly, copy, nonatomic) NSString* fragment; +/// The fragment component of the string used to initialize the receiver, if present. +@property (readonly, copy, nonatomic, nullable) NSString* fragment; +/// Percent-escaped fragment, prefixed with @c #, or an empty string if absent. +@property (readonly, copy, nonatomic) NSString* encodedFragment; -/** - * Full text-normalized namespace+title - * Decoded, with spaces - */ -@property (readonly, copy, nonatomic) NSString* prefixedText; - -/** - * Full DB-normalized namespace+title - * Decoded, with underscores - */ -@property (readonly, copy, nonatomic) NSString* prefixedDBKey; - -/** - * Full URL-normalized namespace+title - * Encoded, with underscores - */ -@property (readonly, copy, nonatomic) NSString* prefixedURL; - -/** - * URL-normalized fragment, including the # if applicable - * Always returns a string, may be empty string. - */ -@property (readonly, copy, nonatomic) NSString* fragmentForURL; - -/** - * Absolute URL to mobile view of this article - */ +/// Absolute URL to mobile view of this article @property (readonly, copy, nonatomic) NSURL* mobileURL; -/** - * Absolute URL to desktop view of this article - */ +/// Absolute URL to desktop view of this article @property (readonly, copy, nonatomic) NSURL* desktopURL; +/// +/// @name Deprecated Properties +/// + +/// Full text-normalized namespace+title decoded, with spaces +/// @warning This method was added prematurely and never supported, so it's effectively an alias for `text`. +@property (readonly, copy, nonatomic) NSString* prefixedText __deprecated; + +/// Full DB-normalized namespace+title +/// @see prefixedText +@property (readonly, copy, nonatomic) NSString* prefixedDBKey __deprecated; + +/// Full URL-normalized namespace+title +/// @see prefixedText +@property (readonly, copy, nonatomic) NSString* prefixedURL __deprecated; + +/** + * Initializes a new title belonging to @c site with an optional fragment. + * + * The preferred initializer is @c initWithString:site:, which parses components in the string. + * + * @param site The site to which this title belongs. + * @param text The text which makes up the title. + * @param fragment An optional fragment, e.g. @"#section". + * + * @return A new title. + */ +- (instancetype)initWithSite:(MWKSite*)site + normalizedTitle:(NSString*)text + fragment:(NSString* __nullable)fragment NS_DESIGNATED_INITIALIZER; + +// TODO: implement "fuzzy string" initializer which can take a title or an internal link? + +/// Initialize a new title from `str`, from which the title & an optional fragment are parsed. +- (instancetype)initWithString:(NSString*)str site:(MWKSite*)site; + +/// Initialize a new title with `relativeInternalLink`, which is parsed after removing the `/wiki/` prefix. +- (instancetype)initWithInternalLink:(NSString*)relativeInternalLink site:(MWKSite*)site; + +/// Convenience factory method wrapping `initWithString:site:`. ++ (MWKTitle*)titleWithString:(NSString*)str site:(MWKSite*)site; + +- (BOOL)isEqualToTitle:(MWKTitle*)title; + @end + +NS_ASSUME_NONNULL_END diff --git a/MediaWikiKit/MediaWikiKit/MWKTitle.m b/MediaWikiKit/MediaWikiKit/MWKTitle.m index 6e05c71..114b141 100644 --- a/MediaWikiKit/MediaWikiKit/MWKTitle.m +++ b/MediaWikiKit/MediaWikiKit/MWKTitle.m @@ -2,6 +2,11 @@ // Copyright (c) 2013 Wikimedia Foundation. Provided under MIT-style license; please copy and modify! #import "MediaWikiKit.h" +#import "WMFPageUtilities.h" +#import "NSArray+WMFExtensions.h" +#import "NSObjectUtilities.h" + +NS_ASSUME_NONNULL_BEGIN @interface MWKTitle () @@ -11,57 +16,47 @@ @property (readwrite, copy, nonatomic) NSString* prefixedText; @property (readwrite, copy, nonatomic) NSString* prefixedDBKey; @property (readwrite, copy, nonatomic) NSString* prefixedURL; -@property (readwrite, copy, nonatomic) NSString* fragmentForURL; +@property (readwrite, copy, nonatomic) NSString* encodedFragment; @property (readwrite, copy, nonatomic) NSURL* mobileURL; @property (readwrite, copy, nonatomic) NSURL* desktopURL; - @end @implementation MWKTitle -#pragma mark - Class methods +- (instancetype)initWithSite:(MWKSite*)site normalizedTitle:(NSString*)text fragment:(NSString* __nullable)fragment { + NSParameterAssert(site); + NSParameterAssert(text.length); + self = [super init]; + if (self) { + self.site = site; + self.text = text; + self.fragment = fragment; + } + return self; +} + +- (instancetype)initWithInternalLink:(NSString*)relativeInternalLink site:(MWKSite*)site { + return [self initWithString:WMFInternalLinkPath(relativeInternalLink) site:site]; +} + +- (instancetype)initWithString:(NSString*)str site:(MWKSite*)site { + NSAssert(!WMFIsInternalLink(str), + @"Didn't expect %@ to be an internal link. Use initWithInternalLink:site: instead.", + str); + NSArray* bits = [str componentsSeparatedByString:@"#"]; + NSParameterAssert(bits.count >= 1); + return [self initWithSite:site + normalizedTitle:WMFNormalizedPageTitle(bits[0]) + fragment:[bits wmf_safeObjectAtIndex:1]]; +} + (MWKTitle*)titleWithString:(NSString*)str site:(MWKSite*)site { return [[MWKTitle alloc] initWithString:str site:site]; } -+ (NSString*)normalize:(NSString*)str { - // @todo implement fuller normalization? - return [str stringByReplacingOccurrencesOfString:@"_" withString:@" "]; -} - -#pragma mark - Initializers - -- (instancetype)initWithString:(NSString*)str site:(MWKSite*)site { - self = [self init]; - if (self) { - self.site = site; - NSArray* bits = [str componentsSeparatedByString:@"#"]; - self.text = [MWKTitle normalize:bits[0]]; - if (bits.count > 1) { - self.fragment = bits[1]; - } - } - return self; -} - -#pragma mark - Property getters - -- (NSString*)namespace { - // @todo implement namespace detection and normalization - // rename the property from a reserved language name - // doing this right requires some site info - return nil; -} - -- (NSString*)_prefix { - // @todo implement namespace prefixing once namespaces are handled - return @""; -} - - (NSString*)prefixedText { - return [[self _prefix] stringByAppendingString:self.text]; + return self.text; } - (NSString*)prefixedDBKey { @@ -72,7 +67,7 @@ return [self.prefixedDBKey stringByAddingPercentEscapesUsingEncoding:NSUTF8StringEncoding]; } -- (NSString*)fragmentForURL { +- (NSString*)encodedFragment { if (self.fragment) { // @fixme we use some weird escaping system...? return [@"#" stringByAppendingString:[self.fragment stringByAddingPercentEscapesUsingEncoding:NSUTF8StringEncoding]]; @@ -81,34 +76,36 @@ } } -- (NSURL*)mobileURL; -{ - return [NSURL URLWithString:[NSString stringWithFormat:@"https://%@.m.%@/wiki/%@", +- (NSURL*)mobileURL { + return [NSURL URLWithString:[NSString stringWithFormat:@"https://%@.m.%@%@%@", self.site.language, self.site.domain, + WMFInternalLinkPathPrefix, self.prefixedURL]]; } -- (NSURL*)desktopURL; -{ - return [NSURL URLWithString:[NSString stringWithFormat:@"https://%@.%@/wiki/%@", +- (NSURL*)desktopURL { + return [NSURL URLWithString:[NSString stringWithFormat:@"https://%@.%@%@%@", self.site.language, self.site.domain, + WMFInternalLinkPathPrefix, self.prefixedURL]]; } - - (BOOL)isEqual:(id)object { - if (object == nil) { - return NO; - } else if (![object isKindOfClass:[MWKTitle class]]) { - return NO; + if (self == object) { + return YES; + } else if ([object isKindOfClass:[MWKTitle class]]) { + return [self isEqualToTitle:object]; } else { - MWKTitle* other = object; - return [self.site isEqual:other.site] && - [self.prefixedText isEqualToString:other.prefixedText] && - ((self.fragment == nil && other.fragment == nil) || [self.fragment isEqualToString:other.fragment]); + return NO; } +} + +- (BOOL)isEqualToTitle:(MWKTitle*)otherTitle { + return WMF_IS_EQUAL_PROPERTIES(self, site, otherTitle) + && WMF_EQUAL_PROPERTIES(self, prefixedText, isEqualToString:, otherTitle) + && WMF_EQUAL_PROPERTIES(self, fragment, isEqualToString:, otherTitle); } - (NSString*)description { @@ -119,15 +116,17 @@ } } -#pragma mark - NSCopying protocol methods +- (NSUInteger)hash { + return self.site.hash + ^ flipBitsWithAdditionalRotation(self.prefixedText.hash, 1) + ^ flipBitsWithAdditionalRotation(self.fragment.hash, 2); +} -- (id)copyWithZone:(NSZone*)zone { - // Titles are immutable +- (instancetype)copyWithZone:(NSZone*)zone { + // immutable return self; } -- (NSUInteger)hash { - return [self.site hash] ^ [self.prefixedText hash] ^ [self.fragment hash]; -} - @end + +NS_ASSUME_NONNULL_END \ No newline at end of file diff --git a/MediaWikiKit/MediaWikiKitTests/MWKTitleTests.m b/MediaWikiKit/MediaWikiKitTests/MWKTitleTests.m index ccb522b..6addca6 100644 --- a/MediaWikiKit/MediaWikiKitTests/MWKTitleTests.m +++ b/MediaWikiKit/MediaWikiKitTests/MWKTitleTests.m @@ -36,7 +36,7 @@ XCTAssertEqualObjects(title.prefixedText, @"Simple", @"Text form is full"); XCTAssertEqualObjects(title.prefixedURL, @"Simple", @"URL form is full"); XCTAssertNil(title.fragment, @"Fragment is nil"); - XCTAssertEqualObjects(title.fragmentForURL, @"", @"Fragment for URL is empty string"); + XCTAssertEqualObjects(title.encodedFragment, @"", @"Fragment for URL is empty string"); } - (void)testFancy { @@ -48,7 +48,7 @@ XCTAssertEqualObjects(title.prefixedText, @"Fancy title with spaces", @"Text form has spaces"); XCTAssertEqualObjects(title.prefixedURL, @"Fancy_title_with_spaces", @"URL form has underscores"); XCTAssertNil(title.fragment, @"Fragment is nil"); - XCTAssertEqualObjects(title.fragmentForURL, @"", @"Fragment for URL is empty string"); + XCTAssertEqualObjects(title.encodedFragment, @"", @"Fragment for URL is empty string"); } } @@ -58,7 +58,7 @@ XCTAssertEqualObjects(title.prefixedText, @"Éclair", @"Text form has unicode"); XCTAssertEqualObjects(title.prefixedURL, @"%C3%89clair", @"URL form has percent encoding"); XCTAssertNil(title.fragment, @"Fragment is nil"); - XCTAssertEqualObjects(title.fragmentForURL, @"", @"Fragment for URL is empty string"); + XCTAssertEqualObjects(title.encodedFragment, @"", @"Fragment for URL is empty string"); } - (void)testEquals { diff --git a/Wikipedia.xcodeproj/project.pbxproj b/Wikipedia.xcodeproj/project.pbxproj index 0f7ef2b..4b463f0 100644 --- a/Wikipedia.xcodeproj/project.pbxproj +++ b/Wikipedia.xcodeproj/project.pbxproj @@ -192,6 +192,7 @@ 0EE768811AFD25CC00A5D046 /* WMFSearchFunnel.m in Sources */ = {isa = PBXBuildFile; fileRef = 0EE768801AFD25CC00A5D046 /* WMFSearchFunnel.m */; }; 701FF5EE601DEA3FCAB7EFD3 /* libPods.a in Frameworks */ = {isa = PBXBuildFile; fileRef = D82982ED992F47428037BDF2 /* libPods.a */; }; 954BA118838BF8BA6B01C34A /* libPods-WikipediaUnitTests.a in Frameworks */ = {isa = PBXBuildFile; fileRef = 8CE61C6963F825760822A28A /* libPods-WikipediaUnitTests.a */; }; + BC092B961B18E89200093C59 /* WMFPageUtilities.m in Sources */ = {isa = PBXBuildFile; fileRef = BC092B951B18E89200093C59 /* WMFPageUtilities.m */; }; BC0FED621AAA0263002488D7 /* WMFCodingStyle.m in Sources */ = {isa = PBXBuildFile; fileRef = BC6FEAE01A9B7EFD00A1D890 /* WMFCodingStyle.m */; }; BC0FED631AAA0263002488D7 /* MWKTestCase.m in Sources */ = {isa = PBXBuildFile; fileRef = BCB669BB1A83F6D300C7B1FE /* MWKTestCase.m */; }; BC0FED641AAA0263002488D7 /* MWKArticleStoreTestCase.m in Sources */ = {isa = PBXBuildFile; fileRef = BCB669BD1A83F6D300C7B1FE /* MWKArticleStoreTestCase.m */; }; @@ -281,7 +282,6 @@ BCC185E01A9EC836005378F8 /* UIButton+FrameUtils.m in Sources */ = {isa = PBXBuildFile; fileRef = BCC185DF1A9EC836005378F8 /* UIButton+FrameUtils.m */; }; BCC185E81A9FA498005378F8 /* UICollectionViewFlowLayout+AttributeUtils.m in Sources */ = {isa = PBXBuildFile; fileRef = BCC185E71A9FA498005378F8 /* UICollectionViewFlowLayout+AttributeUtils.m */; }; BCCED2D01AE03BE20094EB7E /* MWKSectionListTests.m in Sources */ = {isa = PBXBuildFile; fileRef = BCCED2CF1AE03BE20094EB7E /* MWKSectionListTests.m */; }; - BCD41DDC1B11CBD400231BB1 /* (null) in Sources */ = {isa = PBXBuildFile; }; BCD41DEA1B11CC5800231BB1 /* golden-gate.jpg in Resources */ = {isa = PBXBuildFile; fileRef = BCD41DDE1B11CC5800231BB1 /* golden-gate.jpg */; }; BCD41DEB1B11CC5800231BB1 /* MainPageMobileView.json in Resources */ = {isa = PBXBuildFile; fileRef = BCD41DDF1B11CC5800231BB1 /* MainPageMobileView.json */; }; BCD41DEC1B11CC5800231BB1 /* Obama.json in Resources */ = {isa = PBXBuildFile; fileRef = BCD41DE01B11CC5800231BB1 /* Obama.json */; }; @@ -734,6 +734,8 @@ 357504E50DA104E39C6ACFEB /* Pods.release.xcconfig */ = {isa = PBXFileReference; includeInIndex = 1; lastKnownFileType = text.xcconfig; name = Pods.release.xcconfig; path = "Pods/Target Support Files/Pods/Pods.release.xcconfig"; sourceTree = "<group>"; }; 429C152FC8B093B59D18CAD3 /* Pods-WikipediaUnitTests.beta.xcconfig */ = {isa = PBXFileReference; includeInIndex = 1; lastKnownFileType = text.xcconfig; name = "Pods-WikipediaUnitTests.beta.xcconfig"; path = "Pods/Target Support Files/Pods-WikipediaUnitTests/Pods-WikipediaUnitTests.beta.xcconfig"; sourceTree = "<group>"; }; 8CE61C6963F825760822A28A /* libPods-WikipediaUnitTests.a */ = {isa = PBXFileReference; explicitFileType = archive.ar; includeInIndex = 0; path = "libPods-WikipediaUnitTests.a"; sourceTree = BUILT_PRODUCTS_DIR; }; + BC092B951B18E89200093C59 /* WMFPageUtilities.m */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.objc; path = WMFPageUtilities.m; sourceTree = "<group>"; }; + BC092B971B18E8AF00093C59 /* WMFPageUtilities.h */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.c.h; path = WMFPageUtilities.h; sourceTree = "<group>"; }; BC2375981AB78D8A00B0BAA8 /* NSParagraphStyle+WMFNaturalAlignmentStyle.h */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; path = "NSParagraphStyle+WMFNaturalAlignmentStyle.h"; sourceTree = "<group>"; }; BC2375991AB78D8A00B0BAA8 /* NSParagraphStyle+WMFNaturalAlignmentStyle.m */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.objc; path = "NSParagraphStyle+WMFNaturalAlignmentStyle.m"; sourceTree = "<group>"; }; BC23759D1AB8928600B0BAA8 /* WMFDateFormatterTests.m */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.objc; path = WMFDateFormatterTests.m; sourceTree = "<group>"; }; @@ -752,6 +754,7 @@ BC69C3121AB0C1FF0090B039 /* WMFImageInfoController.h */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; name = WMFImageInfoController.h; path = "Image Gallery/WMFImageInfoController.h"; sourceTree = "<group>"; }; BC69C3131AB0C1FF0090B039 /* WMFImageInfoController.m */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.objc; name = WMFImageInfoController.m; path = "Image Gallery/WMFImageInfoController.m"; sourceTree = "<group>"; }; BC6FEAE01A9B7EFD00A1D890 /* WMFCodingStyle.m */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.objc; path = WMFCodingStyle.m; sourceTree = "<group>"; }; + BC7A4A231B17B3510003E73E /* NSObjectUtilities.h */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.c.h; path = NSObjectUtilities.h; sourceTree = "<group>"; }; BC7ACB621AB34C9C00791497 /* WMFAsyncTestCase.h */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; name = WMFAsyncTestCase.h; path = ../WMFAsyncTestCase.h; sourceTree = "<group>"; }; BC7ACB631AB34C9C00791497 /* WMFAsyncTestCase.m */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.objc; name = WMFAsyncTestCase.m; path = ../WMFAsyncTestCase.m; sourceTree = "<group>"; }; BC7DFCCB1AA4BA8A000035C3 /* WMFCodingStyle.h */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.c.h; path = WMFCodingStyle.h; sourceTree = "<group>"; }; @@ -906,6 +909,8 @@ BCD41DE91B11CC5800231BB1 /* user-loggedin.json */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = text.json; path = "user-loggedin.json"; sourceTree = "<group>"; }; BCD41DFD1B11D17100231BB1 /* XCTestCase+MWKFixtures.h */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; path = "XCTestCase+MWKFixtures.h"; sourceTree = "<group>"; }; BCD41DFE1B11D17100231BB1 /* XCTestCase+MWKFixtures.m */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.objc; path = "XCTestCase+MWKFixtures.m"; sourceTree = "<group>"; }; + BCDA2F411B17A02A002FEB6A /* WMFComparison.h */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.c.h; path = WMFComparison.h; sourceTree = "<group>"; }; + BCDA2F421B17A056002FEB6A /* WMFHashing.h */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.c.h; path = WMFHashing.h; sourceTree = "<group>"; }; BCDB75BC1AB0D3DE0005593F /* WMFImageInfoController_Private.h */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; name = WMFImageInfoController_Private.h; path = "Image Gallery/WMFImageInfoController_Private.h"; sourceTree = "<group>"; }; BCDB75BD1AB0DFC40005593F /* WMFRangeUtils.h */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; path = WMFRangeUtils.h; sourceTree = "<group>"; }; BCDB75C31AB0E8300005593F /* WMFSubstringUtilsTests.m */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.objc; path = WMFSubstringUtilsTests.m; sourceTree = "<group>"; }; @@ -2347,6 +2352,11 @@ BCB848761AAAABF80077EC24 /* WMFRoundingUtilities.h */, BCB848771AAAABF80077EC24 /* WMFRoundingUtilities.c */, BCDB75BD1AB0DFC40005593F /* WMFRangeUtils.h */, + BCDA2F411B17A02A002FEB6A /* WMFComparison.h */, + BCDA2F421B17A056002FEB6A /* WMFHashing.h */, + BC7A4A231B17B3510003E73E /* NSObjectUtilities.h */, + BC092B971B18E8AF00093C59 /* WMFPageUtilities.h */, + BC092B951B18E89200093C59 /* WMFPageUtilities.m */, ); path = "mw-utils"; sourceTree = "<group>"; @@ -2928,7 +2938,6 @@ 0484B9071ABB50FA00874073 /* WMFArticleParsing.m in Sources */, BC0FED751AAA026C002488D7 /* NSArray+BKIndexTests.m in Sources */, BC31B2521AB1D9DC008138CA /* WMFImageInfoControllerTests.m in Sources */, - BCD41DDC1B11CBD400231BB1 /* (null) in Sources */, BC0FED641AAA0263002488D7 /* MWKArticleStoreTestCase.m in Sources */, BCA676561AC05FE200A16160 /* NSBundle+TestAssets.m in Sources */, BCB8487B1AAAADF90077EC24 /* WMFRoundingUtilitiesTests.m in Sources */, @@ -3074,6 +3083,7 @@ 047801BE18AE987900DBB747 /* UIButton+ColorMask.m in Sources */, BC86B93D1A929CC500B4C039 /* UICollectionViewFlowLayout+NSCopying.m in Sources */, 04D686C91AB28FE40009B44A /* UIImage+WMFFocalImageDrawing.m in Sources */, + BC092B961B18E89200093C59 /* WMFPageUtilities.m in Sources */, 04490FD51AF16A83009FAB52 /* WMFBundledImageProtocol.m in Sources */, 0429300A18604898002A13FC /* SavedPagesResultCell.m in Sources */, 0487048419F8262600B7D307 /* CaptchaResetter.m in Sources */, diff --git a/Wikipedia/Categories/MWKArticle+WMFSharing.m b/Wikipedia/Categories/MWKArticle+WMFSharing.m index b6a8bc0..d949873 100644 --- a/Wikipedia/Categories/MWKArticle+WMFSharing.m +++ b/Wikipedia/Categories/MWKArticle+WMFSharing.m @@ -7,9 +7,9 @@ // #import "MWKArticle+WMFSharing.h" +#import "MWKArticle+isMain.h" #import "NSString+WMFHTMLParsing.h" #import "MWKSection+WMFSharing.h" -#import "MWKArticle+isMain.h" #import <BlocksKit/BlocksKit.h> @implementation MWKArticle (WMFSharing) diff --git a/Wikipedia/Categories/NSArray+WMFExtensions.h b/Wikipedia/Categories/NSArray+WMFExtensions.h index 88d5afb..32409ff 100644 --- a/Wikipedia/Categories/NSArray+WMFExtensions.h +++ b/Wikipedia/Categories/NSArray+WMFExtensions.h @@ -3,6 +3,9 @@ @interface NSArray (WMFExtensions) +/// @return The object at `index` if it's within range of the receiver, otherwise `nil`. +- (id)wmf_safeObjectAtIndex:(NSUInteger)index; + /** * Safely trim an array to a specified length. * Will not throw an exception if diff --git a/Wikipedia/Categories/NSArray+WMFExtensions.m b/Wikipedia/Categories/NSArray+WMFExtensions.m index 6ebec9c..4b2a40f 100644 --- a/Wikipedia/Categories/NSArray+WMFExtensions.m +++ b/Wikipedia/Categories/NSArray+WMFExtensions.m @@ -10,6 +10,10 @@ @implementation NSArray (WMFExtensions) +- (id)wmf_safeObjectAtIndex:(NSUInteger)index { + return index < self.count ? self[index] : nil; +} + - (instancetype)wmf_arrayByTrimmingToLength:(NSUInteger)length { if ([self count] == 0) { return self; diff --git a/Wikipedia/Categories/NSString+Extras.h b/Wikipedia/Categories/NSString+Extras.h index d7bef31..2632500 100644 --- a/Wikipedia/Categories/NSString+Extras.h +++ b/Wikipedia/Categories/NSString+Extras.h @@ -6,6 +6,9 @@ /// @return A substring of the receiver going up to @c index, or @c length, whichever is shorter. - (NSString*)wmf_safeSubstringToIndex:(NSUInteger)index; +/// @return A substring of the receiver starting at @c index or an empty string if the recevier is too short. +- (NSString*)wmf_safeSubstringFromIndex:(NSUInteger)index; + - (NSString*)wmf_UTF8StringWithPercentEscapes; - (NSString*)wmf_schemelessURL; diff --git a/Wikipedia/Categories/NSString+Extras.m b/Wikipedia/Categories/NSString+Extras.m index 42f90c9..0c524f6 100644 --- a/Wikipedia/Categories/NSString+Extras.m +++ b/Wikipedia/Categories/NSString+Extras.m @@ -14,6 +14,10 @@ return [self substringToIndex:MIN(self.length, index)]; } +- (NSString*)wmf_safeSubstringFromIndex:(NSUInteger)index { + return [self substringFromIndex:MIN(index, self.length - 1)]; +} + - (NSString*)wmf_UTF8StringWithPercentEscapes { return (__bridge_transfer id)CFURLCreateStringByAddingPercentEscapes(0, (__bridge CFStringRef)self, diff --git a/Wikipedia/Networking/Fetchers/ArticleFetcher.m b/Wikipedia/Networking/Fetchers/ArticleFetcher.m index 320b68a..2ea00a2 100644 --- a/Wikipedia/Networking/Fetchers/ArticleFetcher.m +++ b/Wikipedia/Networking/Fetchers/ArticleFetcher.m @@ -143,6 +143,7 @@ - (NSDictionary*)getParamsForTitle:(NSString*)title { NSMutableDictionary* params = @{ @"format": @"json", + @"formatversion": @2, @"action": @"mobileview", @"sectionprop": WMFJoinedPropertyParameters(@[@"toclevel", @"line", @"anchor", @"level", @"number", @"fromtitle", @"index"]), diff --git a/Wikipedia/View Controllers/History/HistoryViewController.m b/Wikipedia/View Controllers/History/HistoryViewController.m index f20e5a5..5f29daa 100644 --- a/Wikipedia/View Controllers/History/HistoryViewController.m +++ b/Wikipedia/View Controllers/History/HistoryViewController.m @@ -251,7 +251,7 @@ DataHousekeeping* dataHouseKeeping = [[DataHousekeeping alloc] init]; [dataHouseKeeping performHouseKeeping]; - [NAV loadTodaysArticleIfNoCoreDataForCurrentArticle]; + [NAV loadTodaysArticle]; } #pragma mark - History section titles @@ -459,7 +459,7 @@ DataHousekeeping* dataHouseKeeping = [[DataHousekeeping alloc] init]; [dataHouseKeeping performHouseKeeping]; - [NAV loadTodaysArticleIfNoCoreDataForCurrentArticle]; + [NAV loadTodaysArticle]; } #pragma mark - Discovery method icons @@ -504,7 +504,7 @@ [self setEmptyOverlayAndTrashIconVisibility]; - [NAV loadTodaysArticleIfNoCoreDataForCurrentArticle]; + [NAV loadTodaysArticle]; } - (void)setEmptyOverlayAndTrashIconVisibility { diff --git a/Wikipedia/View Controllers/Navigation/Center/CenterNavController.h b/Wikipedia/View Controllers/Navigation/Center/CenterNavController.h index 845a22b..e896395 100644 --- a/Wikipedia/View Controllers/Navigation/Center/CenterNavController.h +++ b/Wikipedia/View Controllers/Navigation/Center/CenterNavController.h @@ -17,7 +17,6 @@ popToWebVC:(BOOL)popToWebVC; - (void)loadTodaysArticle; -- (void)loadTodaysArticleIfNoCoreDataForCurrentArticle; - (void)loadRandomArticle; - (void)promptFirstTimeZeroOnWithTitleIfAppropriate:(NSString*)title; diff --git a/Wikipedia/View Controllers/Navigation/Center/CenterNavController.m b/Wikipedia/View Controllers/Navigation/Center/CenterNavController.m index 402180d..44aab88 100644 --- a/Wikipedia/View Controllers/Navigation/Center/CenterNavController.m +++ b/Wikipedia/View Controllers/Navigation/Center/CenterNavController.m @@ -144,10 +144,6 @@ popToWebVC:NO]; } -- (void)loadTodaysArticleIfNoCoreDataForCurrentArticle { - [self loadTodaysArticle]; -} - - (void)loadRandomArticle { [[QueuesSingleton sharedInstance].articleFetchManager.operationQueue cancelAllOperations]; diff --git a/Wikipedia/View Controllers/SavedPages/SavedPagesViewController.m b/Wikipedia/View Controllers/SavedPages/SavedPagesViewController.m index 2db94ae..1edbc6f 100644 --- a/Wikipedia/View Controllers/SavedPages/SavedPagesViewController.m +++ b/Wikipedia/View Controllers/SavedPages/SavedPagesViewController.m @@ -388,7 +388,7 @@ DataHousekeeping* dataHouseKeeping = [[DataHousekeeping alloc] init]; [dataHouseKeeping performHouseKeeping]; - [NAV loadTodaysArticleIfNoCoreDataForCurrentArticle]; + [NAV loadTodaysArticle]; } - (void)deleteAllSavedPages { @@ -403,7 +403,7 @@ [self setEmptyOverlayAndTrashIconVisibility]; - [NAV loadTodaysArticleIfNoCoreDataForCurrentArticle]; + [NAV loadTodaysArticle]; } - (void)alertView:(UIAlertView*)alertView clickedButtonAtIndex:(NSInteger)buttonIndex { diff --git a/Wikipedia/View Controllers/WebView/WebViewController.m b/Wikipedia/View Controllers/WebView/WebViewController.m index f31777c..5e0e1d9 100644 --- a/Wikipedia/View Controllers/WebView/WebViewController.m +++ b/Wikipedia/View Controllers/WebView/WebViewController.m @@ -825,8 +825,7 @@ [strSelf referencesHide]; } - // @todo merge this link title extraction into MWSite - if ([href hasPrefix:@"/wiki/"]) { + if ([href hasPrefix:WMFInternalLinkPathPrefix]) { // Ensure the menu is visible when navigating to new page. [strSelf animateTopAndBottomMenuReveal]; diff --git a/Wikipedia/mw-utils/NSObjectUtilities.h b/Wikipedia/mw-utils/NSObjectUtilities.h new file mode 100644 index 0000000..c06bd49 --- /dev/null +++ b/Wikipedia/mw-utils/NSObjectUtilities.h @@ -0,0 +1,12 @@ +// +// NSObjectUtilities.h +// Wikipedia +// +// Created by Brian Gerstle on 5/28/15. +// Copyright (c) 2015 Wikimedia Foundation. All rights reserved. +// + +// Collection of headers for implementing NSObject methods. + +#import "WMFComparison.h" +#import "WMFHashing.h" diff --git a/Wikipedia/mw-utils/WMFComparison.h b/Wikipedia/mw-utils/WMFComparison.h new file mode 100644 index 0000000..2f43df9 --- /dev/null +++ b/Wikipedia/mw-utils/WMFComparison.h @@ -0,0 +1,54 @@ +// +// WMFComparison.h +// Wikipedia +// +// Created by Brian Gerstle on 5/28/15. +// Copyright (c) 2015 Wikimedia Foundation. All rights reserved. +// + +#ifndef Wikipedia_WMFComparison_h +#define Wikipedia_WMFComparison_h + +/** + * Provides compile time checking for keypaths on a given object. + * @discussion Example usage: + * + * WMF_SAFE_KEYPATH([NSString new], lowercaseString); //< @"lowercaseString" + * WMF_SAFE_KEYPATH([NSString new], fooBar); //< compiler error! + * + * @note Inspired by [EXTKeypathCoding.h](https://github.com/jspahrsummers/libextobjc/blob/master/extobjc/EXTKeyPathCoding.h#L14) + */ +#define WMF_SAFE_KEYPATH(obj, keyp) ((NO, (void)obj.keyp), @#keyp) + +/** + * Compare two *objects* using `==` and <code>[a sel b]</code>, where `sel` is an equality selector + * (e.g. `isEqualToString:`). + * @param a First object, can be `nil`. + * @param sel The selector used to compare @c a to @c b, if <code>a == b</code> is @c false. + * @param b Second object, can be `nil`. + * @return `YES` if the objects are the same pointer or invoking @c sel returns @c YES, otherwise @c NO. + */ +#define WMF_EQUAL(a, sel, b) (((a) == (b)) || ([(a) sel (b)])) + +/** + * Check if two objects have the same value for given property. + * @param a First object, can be @c nil. + * @param prop The property whose value is accessed from `a` and `b`, e.g. `count`. + * @param sel The selector used to compare `a.prop` to `b.prop`. + * @param b Second object, can be @c nil. + * @return `YES` if the values are equal or both `nil`, otherwise `NO`. + * @see WMF_EQUAL + */ +#define WMF_EQUAL_PROPERTIES(a, prop, sel, b) WMF_EQUAL([(a) prop], sel, [(b) prop]) + +/// Convenience for `WMF_EQUAL_PROPERTIES` which passes `isEqual:` for the equality selector. +#define WMF_IS_EQUAL_PROPERTIES(a, prop, b) WMF_EQUAL_PROPERTIES(a, prop, isEqual:, b) + + +/** + * Compare two objects using `==` and `isEqual:`. + * @see WMF_EQUAL + */ +#define WMF_IS_EQUAL(a, b) (WMF_EQUAL(a, isEqual:, b)) + +#endif diff --git a/Wikipedia/mw-utils/WMFHashing.h b/Wikipedia/mw-utils/WMFHashing.h new file mode 100644 index 0000000..6785532 --- /dev/null +++ b/Wikipedia/mw-utils/WMFHashing.h @@ -0,0 +1,23 @@ +// +// WMFHashing.h +// Wikipedia +// +// Created by Brian Gerstle on 5/28/15. +// Copyright (c) 2015 Wikimedia Foundation. All rights reserved. +// + +#ifndef Wikipedia_WMFHashing_h +#define Wikipedia_WMFHashing_h + +static NSUInteger const NSUINT_BIT = sizeof(NSUInteger) * CHAR_BIT; +static NSUInteger const NSUINT_BIT_2 = NSUINT_BIT / 2; + +// taken from MA's blog: +// https://www.mikeash.com/pyblog/friday-qa-2010-06-18-implementing-equality-and-hashing.html +static NSUInteger flipBitsWithAdditionalRotation(NSUInteger x, NSUInteger rotation) { + // take the amount and adjust it by half the size of x, so an s of 0 results in a "flip" of the bits + rotation += NSUINT_BIT_2; + return (x << rotation) | (x >> (NSUINT_BIT - rotation)); +} + +#endif diff --git a/Wikipedia/mw-utils/WMFPageUtilities.h b/Wikipedia/mw-utils/WMFPageUtilities.h new file mode 100644 index 0000000..b6e1b8b --- /dev/null +++ b/Wikipedia/mw-utils/WMFPageUtilities.h @@ -0,0 +1,26 @@ +// +// WMFPageUtilities.h +// Wikipedia +// +// Created by Brian Gerstle on 5/29/15. +// Copyright (c) 2015 Wikimedia Foundation. All rights reserved. +// + +#import <Foundation/Foundation.h> + +/// Expected prefix for links to pages from the wiki that the link's page belongs to. +extern NSString* const WMFInternalLinkPathPrefix; + +/** + * @return Whether a URL is an internal link. + * @see WMFInternalLinkPrefix + */ +extern BOOL WMFIsInternalLink(NSString* urlString); + +/** + * Strips the internal link prefix from @c urlString, if present. + */ +extern NSString* WMFInternalLinkPath(NSString* urlString); + +/// Normalizes page titles extracted from URLs, replacing percent escapes and underscores. +extern NSString* WMFNormalizedPageTitle(NSString* rawPageTitle); diff --git a/Wikipedia/mw-utils/WMFPageUtilities.m b/Wikipedia/mw-utils/WMFPageUtilities.m new file mode 100644 index 0000000..b8dcd24 --- /dev/null +++ b/Wikipedia/mw-utils/WMFPageUtilities.m @@ -0,0 +1,29 @@ +// +// WMFPageUtilities.m +// Wikipedia +// +// Created by Brian Gerstle on 5/29/15. +// Copyright (c) 2015 Wikimedia Foundation. All rights reserved. +// + +#import "WMFPageUtilities.h" +#import "NSString+Extras.h" +#import "WMFRangeUtils.h" + +NSString* const WMFInternalLinkPathPrefix = @"/wiki/"; + +BOOL WMFIsInternalLink(NSString* urlString) { + return [urlString rangeOfString:WMFInternalLinkPathPrefix].location != NSNotFound; +} + +NSString* WMFInternalLinkPath(NSString* urlString) { + NSRange internalLinkRange = [urlString rangeOfString:WMFInternalLinkPathPrefix]; + return internalLinkRange.location == NSNotFound ? + urlString + : [urlString wmf_safeSubstringFromIndex:WMFRangeGetMaxIndex(internalLinkRange)]; +} + +NSString* WMFNormalizedPageTitle(NSString* rawPageTitle) { + return [[rawPageTitle stringByReplacingPercentEscapesUsingEncoding:NSUTF8StringEncoding] + stringByReplacingOccurrencesOfString:@"_" withString:@" "]; +} \ No newline at end of file diff --git a/Wikipedia/mw-utils/WikipediaAppUtils.h b/Wikipedia/mw-utils/WikipediaAppUtils.h index 5d82f3b..5e5d145 100644 --- a/Wikipedia/mw-utils/WikipediaAppUtils.h +++ b/Wikipedia/mw-utils/WikipediaAppUtils.h @@ -3,59 +3,19 @@ #import <Foundation/Foundation.h> #import <UIKit/UIKit.h> - -// TODO: use developer constants? -//extern NSString * const WMFHockeyAppDeveloperXcodeCFBundleIdentifier; -//extern NSString * const WMFHockeyAppDeveloperXcodeAppId; -extern NSString* const WMFHockeyAppAlphaHockeyCFBundleIdentifier; -extern NSString* const WMFHockeyAppAlphaHockeyAppId; -extern NSString* const WMFHockeyAppBetaTestFlightCFBundleIdentifier; -extern NSString* const WMFHockeyAppBetaTestFlightAppId; -//extern NSString * const WMFHockeyAppStableCFBundleIdentifier; -//extern NSString * const WMFHockeyAppStableAppId; -// TODO: use stable channel constants +#import "NSObjectUtilities.h" +#import "WMFPageUtilities.h" #define MWLocalizedString(key, throwaway) [WikipediaAppUtils localizedStringForKey : key] #define MWCurrentArticleLanguageLocalizedString(key, throwaway) [WikipediaAppUtils currentArticleLanguageLocalizedString : key] -/** - * Provides compile time checking for keypaths on a given object. - * @discussion Example usage: - * - * WMF_SAFE_KEYPATH([NSString new], lowercaseString); //< @"lowercaseString" - * WMF_SAFE_KEYPATH([NSString new], fooBar); //< compiler error! - * - * @note Inspired by [EXTKeypathCoding.h](https://github.com/jspahrsummers/libextobjc/blob/master/extobjc/EXTKeyPathCoding.h#L14) - */ -#define WMF_SAFE_KEYPATH(obj, keyp) ((NO, (void)obj.keyp), @#keyp) +/// @return Number of bytes equivalent to `m` megabytes. +static NSUInteger MegabytesToBytes(NSUInteger m) { + static NSUInteger const MEGABYTE = 1 << 20; + return m * MEGABYTE; +} -/** - * Compare two *objects* using @c == and <code>[a sel b]</code>, where @c sel is a equality selector - * (e.g. @c isEqualToString:). - * @param a First object, can be @c nil. - * @param sel The selector used to compare @c a to @c b, if <code>a == b</code> is @c false. - * @param b Second object, can be @c nil. - * @return @c YES if the objects are the same pointer or invoking @c sel returns @c YES, otherwise @c NO. - */ -#define WMF_EQUAL(a, sel, b) (((a) == (b)) || ([(a) sel (b)])) - -/** - * Compare two objects using `==` and `isEqual:`. - * @see WMF_EQUAL - */ -#define WMF_IS_EQUAL(a, b) (WMF_EQUAL(a, isEqual :, b)) - -/// Circularly rotate an unsigned int (useful when implementing <code>-[NSObject hash]</code>). -FOUNDATION_EXPORT NSUInteger CircularBitwiseRotation(NSUInteger x, NSUInteger s) -__attribute__((pure, always_inline, const)); - -/// Conert @c m megabytes to bytes. -FOUNDATION_EXPORT NSUInteger MegabytesToBytes(NSUInteger m) -__attribute__((pure, always_inline, const)); - -/// Normalizes page titles extracted from URLs, replacing percent escapes and underscores. -FOUNDATION_EXPORT NSString* WMFNormalizedPageTitle(NSString* rawPageTitle); - +/// Expected prefix for links pointing to pages within the current page's wiki. @interface WikipediaAppUtils : NSObject + (NSString*)appVersion; diff --git a/Wikipedia/mw-utils/WikipediaAppUtils.m b/Wikipedia/mw-utils/WikipediaAppUtils.m index bdfec2c..74011c5 100644 --- a/Wikipedia/mw-utils/WikipediaAppUtils.m +++ b/Wikipedia/mw-utils/WikipediaAppUtils.m @@ -7,20 +7,6 @@ #import "NSBundle+WMFInfoUtils.h" #import <BlocksKit/BlocksKit.h> -NSUInteger MegabytesToBytes(NSUInteger m){ - static NSUInteger const MEGABYTE = 1 << 20; - return m * MEGABYTE; -} - -NSUInteger CircularBitwiseRotation(NSUInteger x, NSUInteger s) { - return (x << s) | (x >> (sizeof(x) * CHAR_BIT - s)); -} - -NSString* WMFNormalizedPageTitle(NSString* rawPageTitle) { - return [[rawPageTitle stringByReplacingPercentEscapesUsingEncoding:NSUTF8StringEncoding] - stringByReplacingOccurrencesOfString:@"_" withString:@" "]; -} - @implementation WikipediaAppUtils + (void)load { diff --git a/WikipediaUnitTests/CircularBitwiseRotationTests.m b/WikipediaUnitTests/CircularBitwiseRotationTests.m index 0f9fcdd..3124f4a 100644 --- a/WikipediaUnitTests/CircularBitwiseRotationTests.m +++ b/WikipediaUnitTests/CircularBitwiseRotationTests.m @@ -8,8 +8,7 @@ #import <UIKit/UIKit.h> #import <XCTest/XCTest.h> - -#import "WikipediaAppUtils.h" +#import "WMFHashing.h" @interface CircularBitwiseRotationTests : XCTestCase @@ -17,14 +16,31 @@ @implementation CircularBitwiseRotationTests -- (void)testExamples { - NSUInteger testValue = 0b00000001; - NSUInteger len = sizeof(testValue) * CHAR_BIT; - XCTAssertEqual(CircularBitwiseRotation(testValue, 0), 0b001); - XCTAssertEqual(CircularBitwiseRotation(testValue, 1), 0b00000010); - XCTAssertEqual(CircularBitwiseRotation(testValue, 2), 0b00000100); - XCTAssertEqual(CircularBitwiseRotation(testValue, len), 0b00000001); - XCTAssertEqual(CircularBitwiseRotation(testValue, len + 1), 0b00000010); +- (void)testMatchesCorrespondingPowerOfTwo { + for (NSUInteger rotation; rotation < NSUINT_BIT; rotation++) { + NSUInteger actualResult = flipBitsWithAdditionalRotation(1, rotation); + // add by NSUINT_BIT_2 to model the "flipping," then modulo for rotation + NSUInteger exponent = (rotation + NSUINT_BIT_2) % NSUINT_BIT; + NSUInteger expectedResult = powl(2, exponent); + XCTAssertEqual(actualResult, expectedResult, + @"Rotating 1 by %lu should be equal to 2^%lu (%lu). Got %lu instead", + rotation, exponent, expectedResult, actualResult); + } +} + +- (void)testSymmetrical { + for (NSUInteger i; i < 50; i++) { + NSUInteger testValue = arc4random(); + for (NSUInteger rotation; rotation < NSUINT_BIT; rotation++) { + NSUInteger symmetricalRotation = rotation + NSUINT_BIT; + NSUInteger original = flipBitsWithAdditionalRotation(testValue, rotation); + NSUInteger symmetrical = flipBitsWithAdditionalRotation(testValue, symmetricalRotation); + XCTAssertEqual(original, symmetrical, + @"Rotating %lu by %lu should be the same as rotating by %lu + NSUINT_BIT (%lu)." + "Got %lu expected %lu", + testValue, rotation, rotation, symmetricalRotation, symmetrical, original); + } + } } @end diff --git a/WikipediaUnitTests/MWKArticle+WMFSharingTests.m b/WikipediaUnitTests/MWKArticle+WMFSharingTests.m index 745bb02..52274c4 100644 --- a/WikipediaUnitTests/MWKArticle+WMFSharingTests.m +++ b/WikipediaUnitTests/MWKArticle+WMFSharingTests.m @@ -10,11 +10,11 @@ #import <XCTest/XCTest.h> #import "SessionSingleton.h" -#import "MWKArticle+isMain.h" #import "WMFTestFixtureUtilities.h" #import "MWKTitle.h" #import "MWKSite.h" #import "MWKArticle+WMFSharing.h" +#import "MWKArticle+isMain.h" #import "MWKDataStore+TemporaryDataStore.h" #define HC_SHORTHAND 1 @@ -28,13 +28,14 @@ - (void)testMainPage { self.article = - [[MWKArticle alloc] initWithTitle:[[SessionSingleton sharedInstance] mainArticleTitle] dataStore:nil]; + [[MWKArticle alloc] initWithTitle:[MWKTitle titleWithString:@"Main Page" site:[MWKSite siteWithCurrentLocale]] + dataStore:nil]; NSDictionary* mainPageMobileView = [[[self wmf_bundle] wmf_jsonFromContentsOfFile:@"MainPageMobileView"] objectForKey:@"mobileview"]; - NSAssert(self.article.isMain, @"supposed to be testing main pages!"); + NSAssert([self.article isMain], @"supposed to be testing main pages!"); [self.article importMobileViewJSON:mainPageMobileView]; assertThat(self.article.shareSnippet, is(@"Gary Cooper was an American film actor known for his natural, authentic, and understated acting style. He was a movie star from the end of the silent film era through the end of the golden age of Classical Hollywood. Cooper began his career as a film extra and stunt rider and soon established himself as a Western hero in films such as The Virginian. He played the lead in adventure films and dramas such as A Farewell to Arms and The Lives of a Bengal Lancer, and extended his range of performances to include roles in most major film genres. He portrayed champions of the common man in films such as Mr. Deeds Goes to Town, Meet John Doe, Sergeant York, The Pride of the Yankees, and For Whom the Bell Tolls. In his later years, he delivered award-winning performances in High Noon and Friendly Persuasion. Cooper received three Academy Awards and appeared on the Motion Picture Herald exhibitors poll of top ten film personalities every year from 1936 to 1958. His screen persona embodied the American folk hero. Ongoing: Nepal earthquake – Yemeni Civil WarRecent deaths: Ruth Rendell – Maya Plisetskaya")); -- To view, visit https://gerrit.wikimedia.org/r/215114 To unsubscribe, visit https://gerrit.wikimedia.org/r/settings Gerrit-MessageType: newchange Gerrit-Change-Id: If5906e1ad8f6f39899e3efbb703ce660bceeb6d9 Gerrit-PatchSet: 1 Gerrit-Project: apps/ios/wikipedia Gerrit-Branch: master Gerrit-Owner: Bgerstle <bgers...@wikimedia.org> _______________________________________________ MediaWiki-commits mailing list MediaWiki-commits@lists.wikimedia.org https://lists.wikimedia.org/mailman/listinfo/mediawiki-commits