Full Code of cloudkite/Classy for AI

master 0ae47bf304c0 cached
161 files
492.9 KB
138.0k tokens
5 symbols
1 requests
Download .txt
Showing preview only (536K chars total). Download the full file or copy to clipboard to get everything.
Repository: cloudkite/Classy
Branch: master
Commit: 0ae47bf304c0
Files: 161
Total size: 492.9 KB

Directory structure:
gitextract_4fisv63u/

├── .gitignore
├── .travis.yml
├── CHANGELOG.md
├── CONTRIBUTING.md
├── Classy/
│   ├── Additions/
│   │   ├── CASStyleClassUtilities.h
│   │   ├── CASStyleClassUtilities.m
│   │   ├── CASStyleableItem.h
│   │   ├── CASStyleableItem.m
│   │   ├── NSObject+CASSwizzle.h
│   │   ├── NSObject+CASSwizzle.m
│   │   ├── NSRegularExpression+CASAdditions.h
│   │   ├── NSRegularExpression+CASAdditions.m
│   │   ├── NSString+CASAdditions.h
│   │   ├── NSString+CASAdditions.m
│   │   ├── UIBarItem+CASAdditions.h
│   │   ├── UIBarItem+CASAdditions.m
│   │   ├── UIColor+CASAdditions.h
│   │   ├── UIColor+CASAdditions.m
│   │   ├── UINavigationBar+CASAdditions.h
│   │   ├── UINavigationBar+CASAdditions.m
│   │   ├── UINavigationItem+CASAdditions.h
│   │   ├── UINavigationItem+CASAdditions.m
│   │   ├── UISlider+CASAdditions.h
│   │   ├── UISlider+CASAdditions.m
│   │   ├── UITabBar+CASAdditions.h
│   │   ├── UITabBar+CASAdditions.m
│   │   ├── UITextField+CASAdditions.h
│   │   ├── UITextField+CASAdditions.m
│   │   ├── UIToolbar+CASAdditions.h
│   │   ├── UIToolbar+CASAdditions.m
│   │   ├── UIView+CASAdditions.h
│   │   ├── UIView+CASAdditions.m
│   │   ├── UIViewController+CASAdditions.h
│   │   └── UIViewController+CASAdditions.m
│   ├── Classy.h
│   ├── Parser/
│   │   ├── CASDeviceOSVersionItem.h
│   │   ├── CASDeviceOSVersionItem.m
│   │   ├── CASDeviceScreenSizeItem.h
│   │   ├── CASDeviceScreenSizeItem.m
│   │   ├── CASDeviceSelector.h
│   │   ├── CASDeviceSelector.m
│   │   ├── CASDeviceSelectorItem.h
│   │   ├── CASDeviceTypeItem.h
│   │   ├── CASDeviceTypeItem.m
│   │   ├── CASExpressionSolver.h
│   │   ├── CASExpressionSolver.m
│   │   ├── CASInvocation.h
│   │   ├── CASInvocation.m
│   │   ├── CASLexer.h
│   │   ├── CASLexer.m
│   │   ├── CASParser.h
│   │   ├── CASParser.m
│   │   ├── CASStyleNode.h
│   │   ├── CASStyleNode.m
│   │   ├── CASStyleProperty.h
│   │   ├── CASStyleProperty.m
│   │   ├── CASStyleSelector.h
│   │   ├── CASStyleSelector.m
│   │   ├── CASStyler.h
│   │   ├── CASStyler.m
│   │   ├── CASTextAttributes.h
│   │   ├── CASTextAttributes.m
│   │   ├── CASToken.h
│   │   ├── CASToken.m
│   │   ├── CASUnitToken.h
│   │   ├── CASUnitToken.m
│   │   ├── CASUtilities.h
│   │   └── CASUtilities.m
│   ├── Reflection/
│   │   ├── CASArgumentDescriptor.h
│   │   ├── CASArgumentDescriptor.m
│   │   ├── CASAssociatedObjectsWeakWrapper.h
│   │   ├── CASAssociatedObjectsWeakWrapper.m
│   │   ├── CASObjectClassDescriptor.h
│   │   ├── CASObjectClassDescriptor.m
│   │   ├── CASPropertyDescriptor.h
│   │   ├── CASPropertyDescriptor.m
│   │   ├── CASRuntimeExtensions.h
│   │   └── CASRuntimeExtensions.m
│   └── Supporting Files/
│       └── Info.plist
├── Classy.podspec
├── Classy.xcodeproj/
│   ├── project.pbxproj
│   └── xcshareddata/
│       └── xcschemes/
│           └── Classy.xcscheme
├── Example/
│   ├── ClassyExample/
│   │   ├── CASAppDelegate.h
│   │   ├── CASAppDelegate.m
│   │   ├── CASCatalogViewController.h
│   │   ├── CASCatalogViewController.m
│   │   ├── CASRootViewController.h
│   │   ├── CASRootViewController.m
│   │   ├── CASSimpleFormViewController.h
│   │   ├── CASSimpleFormViewController.m
│   │   ├── ClassyExample-Info.plist
│   │   ├── ClassyExample-Prefix.pch
│   │   ├── Images.xcassets/
│   │   │   ├── AppIcon.appiconset/
│   │   │   │   └── Contents.json
│   │   │   └── LaunchImage.launchimage/
│   │   │       └── Contents.json
│   │   ├── Stylesheets/
│   │   │   ├── stylesheet.cas
│   │   │   └── variables.cas
│   │   ├── en.lproj/
│   │   │   └── InfoPlist.strings
│   │   └── main.m
│   └── ClassyExample.xcodeproj/
│       ├── project.pbxproj
│       └── xcshareddata/
│           └── xcschemes/
│               └── ClassyExample.xcscheme
├── LICENSE
├── Podfile
├── README.md
├── Tests/
│   ├── ClassyTests-Info.plist
│   ├── ClassyTests-Prefix.pch
│   ├── ClassyTests.xcodeproj/
│   │   ├── project.pbxproj
│   │   ├── project.xcworkspace/
│   │   │   ├── contents.xcworkspacedata
│   │   │   └── xcshareddata/
│   │   │       └── ClassyTests.xccheckout
│   │   └── xcshareddata/
│   │       └── xcschemes/
│   │           └── ClassyTests.xcscheme
│   ├── ClassyTestsLoader/
│   │   ├── CASTestsAppDelegate.h
│   │   ├── CASTestsAppDelegate.m
│   │   ├── Classy-Prefix.pch
│   │   ├── ClassyTestsLoader-Info.plist
│   │   ├── Images.xcassets/
│   │   │   ├── AppIcon.appiconset/
│   │   │   │   └── Contents.json
│   │   │   └── LaunchImage.launchimage/
│   │   │       └── Contents.json
│   │   └── main.m
│   ├── Specs/
│   │   ├── CASExampleView.h
│   │   ├── CASExampleView.m
│   │   ├── CASExampleViewController.h
│   │   ├── CASExampleViewController.m
│   │   ├── CASSwizzler.h
│   │   ├── GcovTestObserver.m
│   │   ├── Integration Tests/
│   │   │   ├── CASCustomViewSpec.m
│   │   │   ├── CASUIAppearanceSpec.m
│   │   │   └── CASUIKitSpec.m
│   │   ├── UIDevice+CASMockDevice.h
│   │   ├── UIDevice+CASMockDevice.m
│   │   ├── Unit Tests/
│   │   │   ├── CASArgumentDescriptorSpec.m
│   │   │   ├── CASLexerSpec.m
│   │   │   ├── CASParserSpec.m
│   │   │   ├── CASRuntimeExtensionsSpec.m
│   │   │   ├── CASStylePropertySpec.m
│   │   │   ├── CASStyleSelectorSpec.m
│   │   │   ├── CASStylerSpec.m
│   │   │   ├── CASTokenSpec.m
│   │   │   └── UIView_CASAdditionsSpec.m
│   │   └── XCTest+Spec.h
│   ├── Stylesheets/
│   │   ├── CustomView-Basic.cas
│   │   ├── Import-1.cas
│   │   ├── Import-2.cas
│   │   ├── Import-Base.cas
│   │   ├── Injected-File.cas
│   │   ├── Precedence-1.cas
│   │   ├── Precedence-2.cas
│   │   ├── Properties-Args.cas
│   │   ├── Properties-Basic.cas
│   │   ├── Properties-Nested.cas
│   │   ├── Selectors-Complex.cas
│   │   ├── Selectors-Hierarchy.cas
│   │   ├── Selectors-Indentation.cas
│   │   ├── Selectors-Media-Queries-styler.cas
│   │   ├── Selectors-Media-Queries.cas
│   │   ├── Selectors-Messy.cas
│   │   ├── Selectors-Nested.cas
│   │   ├── UIAppearance-Basic.cas
│   │   ├── UIKit-Basic.cas
│   │   ├── UIKit-MultipleClasses.cas
│   │   ├── Variables-Basic.cas
│   │   └── Variables-Injection.cas
│   └── UIAppearance-setters.md
└── script/
    └── coveralls.sh

================================================
FILE CONTENTS
================================================

================================================
FILE: .gitignore
================================================
# Xcode
build/*
*.pbxuser
!default.pbxuser
*.mode1v3
!default.mode1v3
*.mode2v3
!default.mode2v3
*.perspectivev3
!default.perspectivev3
xcuserdata
profile
*.moved-aside
# Desktop Servies
.DS_Store
.idea/
/script/env.sh
/Pods/
/Podfile.lock

/Classy.xcworkspace/xcshareddata/Classy.xccheckout
Classy.xcodeproj/project.xcworkspace
Classy.xcworkspace/contents.xcworkspacedata
Artifacts


================================================
FILE: .travis.yml
================================================
language: objective-c

before_install:
  - sudo easy_install cpp-coveralls

script:
  - pod install
  - xctool -workspace 'Classy.xcworkspace' -scheme 'ClassyTests' -configuration Debug -sdk iphonesimulator7.0 clean build test ARCHS=i386 VALID_ARCHS=i386 ONLY_ACTIVE_ARCH=NO GCC_INSTRUMENT_PROGRAM_FLOW_ARCS=YES GCC_GENERATE_TEST_COVERAGE_FILES=YES

after_success:
  - ./script/coveralls.sh


================================================
FILE: CHANGELOG.md
================================================
v1.0.0
======
* Promoted v0.2.6 to v1.0.0 as basic functionaltiy is complete and functional.

v0.2.6
======

* Updated list of UIAppearance-setters in tests
* Fixed misnamed @param name in code docs
* Support for iOS 10 font weights
* Reduced warnings caused by missing void in method input declaration

v0.2.5
======

* Styling per window
* Caching to binary plist
* Load on demand
* System font support

v0.2.4
======

* Fixed memory leak
* Added ability to inject variables into stylesheets
* Added various UIKit properties. Thanks awesome contributors

v0.2.3
======

* Live reload, ensure imported stylesheet files are not watched more than once.
* Subtract less precedence points for subclass matching [issue #30](https://github.com/cloudkite/Classy/issues/30)

v0.2.2
======

* Call `cas_updateStyling` on live reload
* Added support for `[UIButton setImage:forState:]` ([skeeet](https://github.com/skeeet))
* Only wrap variable values in brackets if variable has more than one value

v0.2.1
======

* Fixed crash when using media queries [issue #24](https://github.com/cloudkite/Classy/issues/24)
* Added UILabel textAlignment
* Fixed error with style selector nesting
* Removed delay in style updates in didMoveToWindow

v0.2.0
======

* Added @media/@device queries

```Scala
UIView.widget {
  /* default background color */
  background-color red
  
  /* background color on iPads running iOS7 and above */
  @media ipad and (version:>=7) {
    background-color blue
  }
}
```

* Added support for images from documents/caches folder as well as from bundle. ([flippinjoe21](https://github.com/flippinjoe21))
* Fixed Scrolling delaying the update of styling. Fixes [issue #16](https://github.com/cloudkite/Classy/issues/16)
* Fixed style selector subclass matching with styleClass. Fixes [issue #18](https://github.com/cloudkite/Classy/issues/18)

v0.1.0
=======

* Added ability to seperate stylesheets into multiple files and `@import` them

```scala
@import "variables.cas"

UITableView UILabel {
  text-color $mainColor
}
```

* Added UIView tintColor ([glancashire](https://github.com/glancashire))


v0.0.3
=======

* Added ability to use rgb, rgba, hsl and hsla colors ([avalanched](https://github.com/avalanched))

```scala
  background-color  rgb(200, 100, 150)
  text-color        rgba(200, 100, 150, 0.5)
  layer @{
    border-color    hsl(200, 60%, 100%)
    shadow-color    hsla(200, 60%, 100%, 0.2)
  }
```

v0.0.2
=======

* Added ability to specify UIViewController class/subclass in style selector hierarchy. eg

```scala
MYHomeViewController > UIButton.main { 
  background-color black
}
```


================================================
FILE: CONTRIBUTING.md
================================================
# Contributing to Classy

Looking to contribute something to Classy? We would love to have you involved!

To make the process as painless as possible, we have just a couple of guidelines
that should make life easier for everyone involved.

## Pull Requests

If you know exactly how to implement the feature being suggested or fix the bug
being reported, please open a pull request instead of an issue. Pull requests are easier than
patches or inline code blocks for discussing and merging the changes.

If you can't make the change yourself, please open an issue after making sure
that one isn't already logged.

## Suggest Enhancements

Please [check existing enhancements](https://github.com/cloudkite/classy/issues?labels=enhancement&page=1&state=open)
Before submitting your feature request. If it already exists you can vote for it by adding a +1

## Reporting issues

Bugs must be isolated and reproducible problems. Please read the following guidelines before opening any issue.

1. Please [check existing bugs](https://github.com/cloudkite/classy/issues?&page=1&state=open) before submitting a new issue. Moreover, the issue may have already been resolved but has not been released yet.
2. **Create an isolated and reproducible test case.** Try to include the minimum amount of code and stylesheet needed to reproduce the issue.

## Coding standards

### Whitespace

 * Spaces, not tabs.
 * Make liberal use of vertical whitespace to divide code into logical chunks.


================================================
FILE: Classy/Additions/CASStyleClassUtilities.h
================================================
//
//  CASStyleClassUtilities.h
//  Pods
//
//  Created by Jonas Budelmann on 27/03/14.
//
//

#import <Foundation/Foundation.h>
#import "CASStyleableItem.h"

@interface CASStyleClassUtilities : NSObject

+ (NSString *)styleClassForItem:(id<CASStyleableItem>)item;

+ (void)setStyleClass:(NSString *)styleClass forItem:(id<CASStyleableItem>)item;

+ (NSMutableArray *)styleClassesForItem:(id<CASStyleableItem>)item;

+ (void)setStyleClasses:(NSMutableArray *)styleClasses forItem:(id<CASStyleableItem>)item;

+ (void)addStyleClass:(NSString *)styleClass forItem:(id<CASStyleableItem>)item;

+ (void)removeStyleClass:(NSString *)styleClass forItem:(id<CASStyleableItem>)item;

+ (BOOL)item:(id<CASStyleableItem>)item hasStyleClass:(NSString *)styleClass;

@end


================================================
FILE: Classy/Additions/CASStyleClassUtilities.m
================================================
//
//  CASStyleClassUtilities.m
//  Pods
//
//  Created by Jonas Budelmann on 27/03/14.
//
//

#import "CASStyleClassUtilities.h"
#import <objc/runtime.h>
#import "CASStyleableItem.h"


static void *CASStyleClassesKey = &CASStyleClassesKey;

@implementation CASStyleClassUtilities

+ (NSString *)styleClassForItem:(id<CASStyleableItem>)item {
    NSMutableArray *styleClasses = [self styleClassesForItem:item];
    return [styleClasses componentsJoinedByString:CASStyleClassSeparator];
}

+ (void)setStyleClass:(NSString *)styleClass forItem:(id<CASStyleableItem>)item {
    NSArray *classCandidates = [styleClass componentsSeparatedByCharactersInSet:[NSCharacterSet whitespaceCharacterSet]];
    NSMutableArray *styleClasses = [self styleClassesForItem:item];
    [styleClasses removeAllObjects];

    if (!classCandidates.count) {
        [self setStyleClasses:nil forItem:item];
        return;
    }
    if (!styleClasses) {
        styleClasses = [NSMutableArray array];
        [self setStyleClasses:styleClasses forItem:item];
    }
    for (NSString *styleClass in classCandidates) {
        if ([styleClass isKindOfClass:NSString.class] && styleClass.length) {
            [styleClasses addObject:styleClass];
        }
    }
}

+ (NSMutableArray *)styleClassesForItem:(id<CASStyleableItem>)item {
    return objc_getAssociatedObject(item, CASStyleClassesKey);
}

+ (void)setStyleClasses:(NSMutableArray *)styleClasses forItem:(id<CASStyleableItem>)item {
    objc_setAssociatedObject(item, CASStyleClassesKey, styleClasses, OBJC_ASSOCIATION_RETAIN_NONATOMIC);
}

+ (void)addStyleClass:(NSString *)styleClass forItem:(id<CASStyleableItem>)item {
    if (![styleClass isKindOfClass:NSString.class] || !styleClass.length) return;

    NSMutableArray *styleClasses = [self styleClassesForItem:item];
    if (!styleClasses) {
        styleClasses = [NSMutableArray array];
        [self setStyleClasses:styleClasses forItem:item];
    }
    [styleClasses addObject:styleClass];
}

+ (void)removeStyleClass:(NSString *)styleClass forItem:(id<CASStyleableItem>)item {
    if (![styleClass isKindOfClass:NSString.class] || !styleClass.length) return;

    NSMutableArray *styleClasses = [self styleClassesForItem:item];
    [styleClasses removeObject:styleClass];
}

+ (BOOL)item:(id<CASStyleableItem>)item hasStyleClass:(NSString *)styleClass {
    return [[self styleClassesForItem:item] containsObject:styleClass];
}

@end


================================================
FILE: Classy/Additions/CASStyleableItem.h
================================================
//
//  CASStyleableItem.h
//  
//
//  Created by Jonas Budelmann on 31/10/13.
//
//

#import <Foundation/Foundation.h>

/**
 *  separator string used to seperate multiple classes in the cas_styleClass property
 */
extern NSString *const CASStyleClassSeparator;

@protocol CASStyleableItem <NSObject>

/**
 *  NSString which should relate to a styleClass in your stylesheet
 *  multiple style classes can be set by separating them with the string defined as CASStyleClassSeparator
 *  this property will change cas_styleClasses and is a alternative accessor for the set of style classes (e.g. interface builder etc.)
 */
@property (nonatomic, copy) NSString *cas_styleClass;

/**
 *  Direct parent of receiver
 *  ie in case of UIView will be self.superview
 */
@property (nonatomic, weak, readonly) id<CASStyleableItem> cas_parent;

/**
 *  In some cases it is appropriate to specify an alternative parent relationship
 *  ie in case of UIView it's UIViewController is an alternative parent to it's superview
 */
@property (nonatomic, weak, readonly) id<CASStyleableItem> cas_alternativeParent;

/**
 *  adds a style class if it was not set previosly
 */
- (void)cas_addStyleClass:(NSString *)styleClass;

/**
 *  removes a style class if it was set previosly
 */
- (void)cas_removeStyleClass:(NSString *)styleClass;

/**
 * Returns wether or not the receiver has a specific style class assigned
 */
- (BOOL)cas_hasStyleClass:(NSString *)styleClass;

/**
 *  Peforms styling now if receiver needs styling
 */
- (void)cas_updateStylingIfNeeded;

/**
 *  Override this to adjust your styling during a style update pass.
 *  If overriding should call super
 */
- (void)cas_updateStyling;

/**
 *  Returns whether or not receiver has been marked as needing style update
 */
- (BOOL)cas_needsUpdateStyling;

/**
 *  Schedules the receiver to for a style update
 */
- (void)cas_setNeedsUpdateStyling;

@end


================================================
FILE: Classy/Additions/CASStyleableItem.m
================================================
//
//  CASStyleableItem.h
//
//
//  Created by Alexander Ney on 11/03/14.
//
//

#import "CASStyleableItem.h"

NSString *const CASStyleClassSeparator = @" ";

================================================
FILE: Classy/Additions/NSObject+CASSwizzle.h
================================================
//
//  NSObject+CASSwizzle.h
//  Classy
//
//  Created by Jonas Budelmann on 15/10/13.
//  Copyright (c) 2013 cloudling. All rights reserved.
//

#import <Foundation/Foundation.h>

@interface NSObject (CASSwizzle)

+ (void)cas_swizzleInstanceSelector:(SEL)originalSelector
                    withNewSelector:(SEL)newSelector;

@end


================================================
FILE: Classy/Additions/NSObject+CASSwizzle.m
================================================
//
//  NSObject+CASSwizzle.m
//  Classy
//
//  Created by Jonas Budelmann on 15/10/13.
//  Copyright (c) 2013 cloudling. All rights reserved.
//

#import "NSObject+CASSwizzle.h"
#import <objc/runtime.h>

@implementation NSObject (CASSwizzle)

+ (void)cas_swizzleInstanceSelector:(SEL)originalSelector
                    withNewSelector:(SEL)newSelector {
    Method originalMethod = class_getInstanceMethod(self, originalSelector);
    Method newMethod = class_getInstanceMethod(self, newSelector);

    BOOL methodAdded = class_addMethod([self class],
                                        originalSelector,
                                        method_getImplementation(newMethod),
                                        method_getTypeEncoding(newMethod));

    if (methodAdded) {
        class_replaceMethod([self class],
                            newSelector,
                            method_getImplementation(originalMethod),
                            method_getTypeEncoding(originalMethod));
    } else {
        method_exchangeImplementations(originalMethod, newMethod);
    }
}

@end


================================================
FILE: Classy/Additions/NSRegularExpression+CASAdditions.h
================================================
//
//  NSRegularExpression+CASAdditions.h
//  Classy
//
//  Created by Jonas Budelmann on 17/09/13.
//  Copyright (c) 2013 cloudling. All rights reserved.
//

#import <Foundation/Foundation.h>

extern NSRegularExpression *CASRegex(NSString *patternFormat, ...) NS_FORMAT_FUNCTION(1,2);

@interface NSRegularExpression (CASAdditions)

- (NSUInteger)cas_replaceMatchesInString:(NSMutableString *)string withTemplate:(NSString *)templ;

@end


================================================
FILE: Classy/Additions/NSRegularExpression+CASAdditions.m
================================================
//
//  NSRegularExpression+CASAdditions.m
//  Classy
//
//  Created by Jonas Budelmann on 17/09/13.
//  Copyright (c) 2013 cloudling. All rights reserved.
//

#import "NSRegularExpression+CASAdditions.h"
#import "CASUtilities.h"

extern NSRegularExpression *CASRegex(NSString *patternFormat, ...) {
    va_list args;
    va_start(args, patternFormat);
    NSString *pattern = [[NSString alloc] initWithFormat:patternFormat arguments:args];
    va_end(args);

    NSError *error = nil;
    NSRegularExpression *regex = [NSRegularExpression regularExpressionWithPattern:pattern options:0 error:&error];
    NSCAssert(error == nil, @"Could not create regex from pattern %@", pattern);
    if (error) {
        CASLog(@"error %@", error);
    }
    return regex;
}

@implementation NSRegularExpression (CASAdditions)

- (NSUInteger)cas_replaceMatchesInString:(NSMutableString *)string withTemplate:(NSString *)templ {
    return [self replaceMatchesInString:string options:0 range:NSMakeRange(0, string.length) withTemplate:templ];
}

@end


================================================
FILE: Classy/Additions/NSString+CASAdditions.h
================================================
//
//  NSString+CASAdditions.h
//  Classy
//
//  Created by Jonas Budelmann on 14/10/13.
//  Copyright (c) 2013 cloudling. All rights reserved.
//

#import <Foundation/Foundation.h>

@interface NSString (CASAdditions)

- (NSString *)cas_stringByCapitalizingFirstLetter;
- (NSString *)cas_stringByTrimmingWhitespace;
- (NSString *)cas_stringByCamelCasing;
- (NSString *)cas_stringByTrimmingLeadingCharactersInSet:(NSCharacterSet *)characterSet;

@end


================================================
FILE: Classy/Additions/NSString+CASAdditions.m
================================================
//
//  NSString+CASAdditions.m
//  Classy
//
//  Created by Jonas Budelmann on 14/10/13.
//  Copyright (c) 2013 cloudling. All rights reserved.
//

#import "NSString+CASAdditions.h"

@implementation NSString (CASAdditions)

- (NSString *)cas_stringByCapitalizingFirstLetter {
    if (!self.length) return self;
    return [self stringByReplacingCharactersInRange:NSMakeRange(0,1) withString:[[self substringToIndex:1] capitalizedString]];
}

- (NSString *)cas_stringByTrimmingWhitespace {
    return [self stringByTrimmingCharactersInSet:NSCharacterSet.whitespaceCharacterSet];
}

- (NSString *)cas_stringByCamelCasing {
    NSArray *components = [self componentsSeparatedByString:@"-"];
    if (components.count <= 1) return self;

    NSMutableString *camelCasedString = [NSMutableString string];
    for (NSUInteger i = 0; i < components.count; i++) {
        if (i == 0) {
            [camelCasedString appendString:components[i]];
        } else {
            [camelCasedString appendString:[components[i] cas_stringByCapitalizingFirstLetter]];
        }
    }
    return camelCasedString;
}

- (NSString *)cas_stringByTrimmingLeadingCharactersInSet:(NSCharacterSet *)characterSet {
    NSInteger i = 0;
    while ((i < self.length) && [characterSet characterIsMember:[self characterAtIndex:i]]) {
        i++;
    }
    return [self substringFromIndex:i];
}

@end


================================================
FILE: Classy/Additions/UIBarItem+CASAdditions.h
================================================
//
//  UIBarItem+CASAdditions.h
//  
//
//  Created by Jonas Budelmann on 5/11/13.
//
//

#import <UIKit/UIKit.h>
#import "CASStyleableItem.h"

@interface UIBarItem (CASAdditions) <CASStyleableItem>
+ (void)bootstrapClassy;
@property (nonatomic, weak, readwrite) id<CASStyleableItem> cas_parent;

@end


================================================
FILE: Classy/Additions/UIBarItem+CASAdditions.m
================================================
//
//  UIBarItem+CASAdditions.m
//  
//
//  Created by Jonas Budelmann on 5/11/13.
//
//

#import "UIBarItem+CASAdditions.h"
#import <objc/runtime.h>
#import "CASStyler.h"
#import "NSString+CASAdditions.h"
#import "CASStyleClassUtilities.h"
#import "NSObject+CASSwizzle.h"
#import "CASAssociatedObjectsWeakWrapper.h"

@implementation UIBarItem (CASAdditions)

CASSynthesize(weak, id<CASStyleableItem>, cas_parent, setCas_parent);


+ (void)bootstrapClassy {
    [self cas_swizzleInstanceSelector:@selector(init)
                      withNewSelector:@selector(cas_init)];
}

- (id)cas_init {
    [self cas_init];
    [self cas_updateStyling];
    return self;
}

#pragma mark - CASStyleableItem

- (NSString *)cas_styleClass {
    return [CASStyleClassUtilities styleClassForItem:self];
}

- (void)setCas_styleClass:(NSString *)styleClass {
    [CASStyleClassUtilities setStyleClass:styleClass forItem:self];
    [self cas_setNeedsUpdateStyling];
}

- (void)cas_addStyleClass:(NSString *)styleClass {
    [CASStyleClassUtilities addStyleClass:styleClass forItem:self];
    [self cas_setNeedsUpdateStyling];
}

- (void)cas_removeStyleClass:(NSString *)styleClass {
    [CASStyleClassUtilities removeStyleClass:styleClass forItem:self];
    [self cas_setNeedsUpdateStyling];
}

- (BOOL)cas_hasStyleClass:(NSString *)styleClass {
    return [CASStyleClassUtilities item:self hasStyleClass:styleClass];
}

- (id<CASStyleableItem>)cas_alternativeParent {
    return nil;
}

- (void)cas_updateStylingIfNeeded {
    if ([self cas_needsUpdateStyling] && self.cas_parent) {
        [self cas_updateStyling];
    }
}

- (void)cas_updateStyling {
    [CASStyler.defaultStyler styleItem:self];
}

- (BOOL)cas_needsUpdateStyling {
    return [self.cas_parent cas_needsUpdateStyling];
}

- (void)cas_setNeedsUpdateStyling {
    [self.cas_parent cas_setNeedsUpdateStyling];
}

@end


================================================
FILE: Classy/Additions/UIColor+CASAdditions.h
================================================
//
//  UIColor+CASAdditions.h
//  Classy
//
//  Created by Jonas Budelmann on 16/09/13.
//  Copyright (c) 2013 cloudling. All rights reserved.
//
//  Slightly modified from https://github.com/soffes/sstoolkit/blob/master/SSToolkit/UIColor%2BSSToolkitAdditions.h
//  Main change namespaced to avoid collisions with other UIColor categories

#import <UIKit/UIKit.h>

@interface UIColor (CASAdditions)

/**
 *  Creates and returns an UIColor object containing a given value.
 *
 *  @param hex The value for the new color. The `#` sign is not required.
 *
 *  @return An UIColor object containing a value.
 *  You can specify hex values in the following formats: `rgb`, `rrggbb`, or `rrggbbaa`.
 *  The default alpha value is `1.0`.
 */
+ (UIColor *)cas_colorWithHex:(NSString *)hex;

/**
 *  Returns the receiver's value as a hex string.
 *
 *  @return The receiver's value as a hex string.
 *  The value will be `nil` if the color is in a color space other than Grayscale or RGB.
 *  The `#` sign is omitted. Alpha will be omitted.
 */
- (NSString *)cas_hexValue;

/**
 *  Returns the receiver's value as a hex string.
 *
 *  @param includeAlpha `YES` if alpha should be included. `NO` if it should not.
 *
 *  @return The receiver's value as a hex string.
 *  The value will be `nil` if the color is in a color space other than Grayscale or RGB.
 *  The `#` sign is omitted. Alpha is included if `includeAlpha` is `YES`.
 */
- (NSString *)cas_hexValueWithAlpha:(BOOL)includeAlpha;

@end


================================================
FILE: Classy/Additions/UIColor+CASAdditions.m
================================================
//
//  UIColor+CASAdditions.m
//  Classy
//
//  Created by Jonas Budelmann on 16/09/13.
//  Copyright (c) 2013 cloudling. All rights reserved.
//

#import "UIColor+CASAdditions.h"

@interface NSString (CASPrivateAdditions)
- (NSUInteger)cas_hexValue;
@end

@implementation NSString (CASPrivateAdditions)
- (NSUInteger)cas_hexValue {
    NSUInteger result = 0;
    sscanf([self UTF8String], "%lx", &result);
    return result;
}
@end

@implementation UIColor (CASAdditions)

+ (UIColor *)cas_colorWithHex:(NSString *)hex {
    // Remove `#` and `0x`
    if ([hex hasPrefix:@"#"]) {
        hex = [hex substringFromIndex:1];
    } else if ([hex hasPrefix:@"0x"]) {
        hex = [hex substringFromIndex:2];
    }

    // Invalid if not 3, 6, or 8 characters
    NSUInteger length = [hex length];
    if (length != 3 && length != 6 && length != 8) {
        return nil;
    }

    // Make the string 8 characters long for easier parsing
    if (length == 3) {
        NSString *r = [hex substringWithRange:NSMakeRange(0, 1)];
        NSString *g = [hex substringWithRange:NSMakeRange(1, 1)];
        NSString *b = [hex substringWithRange:NSMakeRange(2, 1)];
        hex = [NSString stringWithFormat:@"%@%@%@%@%@%@ff",
               r, r, g, g, b, b];
    } else if (length == 6) {
        hex = [hex stringByAppendingString:@"ff"];
    }

    CGFloat red = [[hex substringWithRange:NSMakeRange(0, 2)] cas_hexValue] / 255.0f;
    CGFloat green = [[hex substringWithRange:NSMakeRange(2, 2)] cas_hexValue] / 255.0f;
    CGFloat blue = [[hex substringWithRange:NSMakeRange(4, 2)] cas_hexValue] / 255.0f;
    CGFloat alpha = [[hex substringWithRange:NSMakeRange(6, 2)] cas_hexValue] / 255.0f;

    return [UIColor colorWithRed:red green:green blue:blue alpha:alpha];
}

- (NSString *)cas_hexValue {
    return [self cas_hexValueWithAlpha:NO];
}

- (NSString *)cas_hexValueWithAlpha:(BOOL)includeAlpha {
    CGColorRef color = self.CGColor;
    size_t count = CGColorGetNumberOfComponents(color);
    const CGFloat *components = CGColorGetComponents(color);

    static NSString *stringFormat = @"%02x%02x%02x";

    NSString *hex = nil;

    // Grayscale
    if (count == 2) {
        NSUInteger white = (NSUInteger)(components[0] * 255.0f);
        hex = [NSString stringWithFormat:stringFormat, white, white, white];
    }

    // RGB
    else if (count == 4) {
        hex = [NSString stringWithFormat:stringFormat, (NSUInteger)(components[0] * 255.0f),
               (NSUInteger)(components[1] * 255.0f), (NSUInteger)(components[2] * 255.0f)];
    }
    
    // Add alpha
    if (hex && includeAlpha) {
        hex = [hex stringByAppendingFormat:@"%02lx", (unsigned long)(CGColorGetAlpha(self.CGColor) * 255.0f)];
    }
    
    // Unsupported color space
    return hex;
}

@end


================================================
FILE: Classy/Additions/UINavigationBar+CASAdditions.h
================================================
//
//  UINavigationBar+CASAdditions.h
//  
//
//  Created by Jonas Budelmann on 31/10/13.
//
//

#import <UIKit/UIKit.h>

@interface UINavigationBar (CASAdditions)

@end


================================================
FILE: Classy/Additions/UINavigationBar+CASAdditions.m
================================================
//
//  UINavigationBar+CASAdditions.m
//  
//
//  Created by Jonas Budelmann on 31/10/13.
//
//

#import "UINavigationBar+CASAdditions.h"
#import "CASStyler.h"
#import "UIView+CASAdditions.h"
#import "UIBarItem+CASAdditions.h"

@implementation UINavigationBar (CASAdditions)

- (void)cas_updateStyling {
    [super cas_updateStyling];
    
    for (UINavigationItem *navigationItem in self.items) {
        for (UIBarButtonItem *barButtonItem in navigationItem.leftBarButtonItems) {
            barButtonItem.cas_parent = self;
            [barButtonItem cas_updateStyling];
        }
        for (UIBarButtonItem *barButtonItem in navigationItem.rightBarButtonItems) {
            barButtonItem.cas_parent = self;
            [barButtonItem cas_updateStyling];
        }
    }
}

@end


================================================
FILE: Classy/Additions/UINavigationItem+CASAdditions.h
================================================
//
//  UINavigationItem+CASAdditions.h
//  
//
//  Created by Joseph Ridenour on 1/21/15.
//
//

#import <UIKit/UIKit.h>

@interface UINavigationItem (CASAdditions)
+ (void)bootstrapClassy;
@end


================================================
FILE: Classy/Additions/UINavigationItem+CASAdditions.m
================================================
//
//  UINavigationItem+CASAdditions.m
//  
//
//  Created by Joseph Ridenour on 1/21/15.
//
//

#import "UINavigationItem+CASAdditions.h"
#import "CASStyler.h"
#import "UIBarItem+CASAdditions.h"
#import "NSObject+CASSwizzle.h"

@implementation UINavigationItem (CASAdditions)

+ (void)bootstrapClassy {
    [self cas_swizzleInstanceSelector:@selector(setRightBarButtonItem:animated:) withNewSelector:@selector(cas_setRightBarButtonItem:animated:)];
    [self cas_swizzleInstanceSelector:@selector(setLeftBarButtonItem:animated:) withNewSelector:@selector(cas_setLeftBarButtonItem:animated:)];
    
    [self cas_swizzleInstanceSelector:@selector(setLeftBarButtonItems:animated:) withNewSelector:@selector(cas_setRightBarButtonItems:animated:)];
    [self cas_swizzleInstanceSelector:@selector(setLeftBarButtonItems:animated:) withNewSelector:@selector(cas_setLeftBarButtonItems:animated:)];
}

- (void)cas_setRightBarButtonItem:(UIBarButtonItem *)item animated:(BOOL)animated {
    [CASStyler.defaultStyler styleItem:item];
    [self cas_setRightBarButtonItem:item animated:animated];
}

- (void)cas_setLeftBarButtonItem:(UIBarButtonItem *)item animated:(BOOL)animated {
    [CASStyler.defaultStyler styleItem:item];
    [self cas_setLeftBarButtonItem:item animated:animated];
}

- (void)cas_setRightBarButtonItems:(NSArray *)items animated:(BOOL)animated {
    [items enumerateObjectsUsingBlock:^(id obj, NSUInteger idx, BOOL *stop) {
        [CASStyler.defaultStyler styleItem:obj];
    }];
    [self cas_setRightBarButtonItems:items animated:animated];
}

- (void)cas_setLeftBarButtonItems:(NSArray *)items animated:(BOOL)animated {
    [items enumerateObjectsUsingBlock:^(id obj, NSUInteger idx, BOOL *stop) {
        [CASStyler.defaultStyler styleItem:obj];
    }];
    [self cas_setLeftBarButtonItems:items animated:animated];
}

@end


================================================
FILE: Classy/Additions/UISlider+CASAdditions.h
================================================
//
//  UISlider+CASAdditions.h
//  
//
//  Created by Cail Borrell on 19/02/14.
//
//

#import <UIKit/UIKit.h>

@interface UISlider (CASAdditions)

@end


================================================
FILE: Classy/Additions/UISlider+CASAdditions.m
================================================
//
//  UISlider+CASAdditions.m
//  
//
//  Created by Cail Borrell on 19/02/14.
//
//

#import "UISlider+CASAdditions.h"

@implementation UISlider (CASAdditions)

- (void)didMoveToWindow {
    [super didMoveToWindow];
}

@end


================================================
FILE: Classy/Additions/UITabBar+CASAdditions.h
================================================
//
//  UITabBar+CASAdditions.h
//  
//
//  Created by Jonas Budelmann on 1/11/13.
//
//

#import <UIKit/UIKit.h>

@interface UITabBar (CASAdditions)

@end


================================================
FILE: Classy/Additions/UITabBar+CASAdditions.m
================================================
//
//  UITabBar+CASAdditions.m
//  
//
//  Created by Jonas Budelmann on 1/11/13.
//
//

#import "UITabBar+CASAdditions.h"

#import "CASStyler.h"
#import "UIView+CASAdditions.h"
#import "UIBarItem+CASAdditions.h"

@implementation UITabBar (CASAdditions)

- (void)cas_updateStyling {
    [super cas_updateStyling];

    for (UITabBarItem *item in self.items) {
        item.cas_parent = self;
        [item cas_updateStyling];
    }
}

@end

================================================
FILE: Classy/Additions/UITextField+CASAdditions.h
================================================
//
//  UITextField+CASAdditions.h
//  Classy
//
//  Created by Jonas Budelmann on 14/10/13.
//  Copyright (c) 2013 cloudling. All rights reserved.
//

#import <UIKit/UIKit.h>

@interface UITextField (CASAdditions)
+ (void)bootstrapClassy;
@property (nonatomic, assign) UIEdgeInsets cas_textEdgeInsets;

@end


================================================
FILE: Classy/Additions/UITextField+CASAdditions.m
================================================
//
//  UITextField+CASAdditions.m
//  Classy
//
//  Created by Jonas Budelmann on 14/10/13.
//  Copyright (c) 2013 cloudling. All rights reserved.
//

#import "UITextField+CASAdditions.h"
#import <objc/runtime.h>
#import "NSObject+CASSwizzle.h"

@implementation UITextField (CASAdditions)

+ (void)bootstrapClassy {
    [self cas_swizzleInstanceSelector:@selector(textRectForBounds:)
                      withNewSelector:@selector(cas_textRectForBounds:)];

    [self cas_swizzleInstanceSelector:@selector(editingRectForBounds:)
                      withNewSelector:@selector(cas_editingRectForBounds:)];
}

#pragma mark - text insets

- (void)setCas_textEdgeInsets:(UIEdgeInsets)cas_textEdgeInsets {
    NSValue *value = [NSValue valueWithUIEdgeInsets:cas_textEdgeInsets];
    objc_setAssociatedObject(self, @selector(cas_textEdgeInsets), value, OBJC_ASSOCIATION_RETAIN_NONATOMIC);
}

- (UIEdgeInsets)cas_textEdgeInsets {
    return [objc_getAssociatedObject(self, @selector(cas_textEdgeInsets)) UIEdgeInsetsValue];
}

- (CGRect)cas_textRectForBounds:(CGRect)bounds {
    if (UIEdgeInsetsEqualToEdgeInsets(self.cas_textEdgeInsets, UIEdgeInsetsZero)) {
        return [self cas_textRectForBounds:bounds];
    }
    return UIEdgeInsetsInsetRect(bounds, self.cas_textEdgeInsets);
}

- (CGRect)cas_editingRectForBounds:(CGRect)bounds {
    return [self textRectForBounds:bounds];
}

@end


================================================
FILE: Classy/Additions/UIToolbar+CASAdditions.h
================================================
//
//  UIToolbar+CASAdditions.h
//  
//
//  Created by Jonas Budelmann on 1/11/13.
//
//

#import <UIKit/UIKit.h>

@interface UIToolbar (CASAdditions)

@end


================================================
FILE: Classy/Additions/UIToolbar+CASAdditions.m
================================================
//
//  UIToolbar+CASAdditions.m
//  
//
//  Created by Jonas Budelmann on 1/11/13.
//
//

#import "UIToolbar+CASAdditions.h"

#import "CASStyler.h"
#import "UIView+CASAdditions.h"
#import "UIBarItem+CASAdditions.h"

@implementation UIToolbar (CASAdditions)

- (void)cas_updateStyling {
    [super cas_updateStyling];

    for (UIBarButtonItem *item in self.items) {
        item.cas_parent = self;
        [item cas_updateStyling];
    }
}

@end

================================================
FILE: Classy/Additions/UIView+CASAdditions.h
================================================
//
//  UIView+CASAdditions.h
//  Classy
//
//  Created by Jonas Budelmann on 30/09/13.
//  Copyright (c) 2013 cloudling. All rights reserved.
//

#import <UIKit/UIKit.h>
#import "CASStyleableItem.h"

@interface UIView (CASAdditions) <CASStyleableItem>
+ (void)bootstrapClassy;

@property (nonatomic, weak, readwrite) id<CASStyleableItem> cas_alternativeParent;
@property (nonatomic, copy) IBInspectable NSString *cas_styleClass;

- (void)cas_setNeedsUpdateStylingForSubviews;

@end



================================================
FILE: Classy/Additions/UIView+CASAdditions.m
================================================
//
//  UIView+CASAdditions.m
//  Classy
//
//  Created by Jonas Budelmann on 30/09/13.
//  Copyright (c) 2013 cloudling. All rights reserved.
//

#import "UIView+CASAdditions.h"
#import <objc/runtime.h>
#import <QuartzCore/QuartzCore.h>
#import "NSObject+CASSwizzle.h"
#import "CASStyler.h"
#import "NSString+CASAdditions.h"
#import "CASStyleClassUtilities.h"
#import "CASAssociatedObjectsWeakWrapper.h"

static void *CASStyleHasBeenUpdatedKey = &CASStyleHasBeenUpdatedKey;

@implementation UIView (CASAdditions)

CASSynthesize(weak, id<CASStyleableItem>, cas_alternativeParent, setCas_alternativeParent);

+ (void)bootstrapClassy {
    [self cas_swizzleInstanceSelector:@selector(didMoveToWindow)
                      withNewSelector:@selector(cas_didMoveToWindow)];
}

- (void)cas_didMoveToWindow {
    [self cas_updateStyling];
    [self cas_didMoveToWindow];
}

- (void)cas_setNeedsUpdateStylingForSubviews {
    [self cas_setNeedsUpdateStyling];
    for (UIView *view in self.subviews) {
        [view cas_setNeedsUpdateStylingForSubviews];
    }
}

#pragma mark - CASStyleableItem

- (NSString *)cas_styleClass {
    return [CASStyleClassUtilities styleClassForItem:self];
}

- (void)setCas_styleClass:(NSString *)styleClass {
    [CASStyleClassUtilities setStyleClass:styleClass forItem:self];
    [self cas_setNeedsUpdateStyling];
}

- (void)cas_addStyleClass:(NSString *)styleClass {
    [CASStyleClassUtilities addStyleClass:styleClass forItem:self];
    [self cas_setNeedsUpdateStyling];
}

- (void)cas_removeStyleClass:(NSString *)styleClass {
    [CASStyleClassUtilities removeStyleClass:styleClass forItem:self];
    [self cas_setNeedsUpdateStyling];
}

- (BOOL)cas_hasStyleClass:(NSString *)styleClass {
    return [CASStyleClassUtilities item:self hasStyleClass:styleClass];
}

- (id<CASStyleableItem>)cas_parent {
    return self.superview;
}

- (void)cas_updateStylingIfNeeded {
    if ([self cas_needsUpdateStyling]) {
        [self cas_updateStyling];
    }
}

- (void)cas_updateStyling {
    if (self.window) {
        [CASStyler.defaultStyler styleItem:self];
    }
    objc_setAssociatedObject(self, CASStyleHasBeenUpdatedKey, @(YES), OBJC_ASSOCIATION_RETAIN_NONATOMIC);
    [CASStyler.defaultStyler unscheduleUpdateForItem:self];
}

- (BOOL)cas_needsUpdateStyling {
    return ![objc_getAssociatedObject(self, CASStyleHasBeenUpdatedKey) boolValue];
}

- (void)cas_setNeedsUpdateStyling {
    objc_setAssociatedObject(self, CASStyleHasBeenUpdatedKey, @(NO), OBJC_ASSOCIATION_RETAIN_NONATOMIC);
    [CASStyler.defaultStyler scheduleUpdateForItem:self];
}

@end


================================================
FILE: Classy/Additions/UIViewController+CASAdditions.h
================================================
//
//  UIViewController+CASAdditions.h
//  
//
//  Created by Jonas Budelmann on 17/11/13.
//
//

#import <UIKit/UIKit.h>
#import "CASStyleableItem.h"

@interface UIViewController (CASAdditions) <CASStyleableItem>
+ (void)bootstrapClassy;
@end


================================================
FILE: Classy/Additions/UIViewController+CASAdditions.m
================================================
//
//  UIViewController+CASAdditions.m
//  
//
//  Created by Jonas Budelmann on 17/11/13.
//
//

#import "UIViewController+CASAdditions.h"
#import "NSObject+CASSwizzle.h"
#import "UIView+CASAdditions.h"
#import <objc/runtime.h>
#import "CASStyler.h"
#import "NSString+CASAdditions.h"
#import "CASStyleClassUtilities.h"


static void *CASStyleHasBeenUpdatedKey = &CASStyleHasBeenUpdatedKey;

@implementation UIViewController (CASAdditions)

+ (void)bootstrapClassy {
    [self cas_swizzleInstanceSelector:@selector(setView:)
                      withNewSelector:@selector(cas_setView:)];
}

- (void)cas_setView:(UIView *)view {
    view.cas_alternativeParent = self;

    [self cas_setView:view];
    [self cas_setNeedsUpdateStyling];
}

#pragma mark - CASStyleableItem

- (NSString *)cas_styleClass {
    return [CASStyleClassUtilities styleClassForItem:self];
}

- (void)setCas_styleClass:(NSString *)styleClass {
    [CASStyleClassUtilities setStyleClass:styleClass forItem:self];
    [self cas_setNeedsUpdateStyling];
    [self.view cas_setNeedsUpdateStylingForSubviews];
}

- (void)cas_addStyleClass:(NSString *)styleClass {
    [CASStyleClassUtilities addStyleClass:styleClass forItem:self];
    [self cas_setNeedsUpdateStyling];
    [self.view cas_setNeedsUpdateStylingForSubviews];
}

- (void)cas_removeStyleClass:(NSString *)styleClass {
    [CASStyleClassUtilities removeStyleClass:styleClass forItem:self];
    [self cas_setNeedsUpdateStyling];
    [self.view cas_setNeedsUpdateStylingForSubviews];
}

- (BOOL)cas_hasStyleClass:(NSString *)styleClass {
    return [CASStyleClassUtilities item:self hasStyleClass:styleClass];
}

- (id<CASStyleableItem>)cas_parent {
    return self.view.superview;
}

- (id<CASStyleableItem>)cas_alternativeParent {
    return self.parentViewController;
}

- (void)cas_updateStylingIfNeeded {
    if ([self cas_needsUpdateStyling]) {
        [self cas_updateStyling];
    }
}

- (void)cas_updateStyling {
    [CASStyler.defaultStyler styleItem:self];
    objc_setAssociatedObject(self, CASStyleHasBeenUpdatedKey, @(YES), OBJC_ASSOCIATION_RETAIN_NONATOMIC);
    [CASStyler.defaultStyler unscheduleUpdateForItem:self];
}

- (BOOL)cas_needsUpdateStyling {
    return ![objc_getAssociatedObject(self, CASStyleHasBeenUpdatedKey) boolValue];
}

- (void)cas_setNeedsUpdateStyling {
    objc_setAssociatedObject(self, CASStyleHasBeenUpdatedKey, @(NO), OBJC_ASSOCIATION_RETAIN_NONATOMIC);
    [CASStyler.defaultStyler scheduleUpdateForItem:self];
}

@end


================================================
FILE: Classy/Classy.h
================================================
//
//  Classy.h
//  Classy
//
//  Created by Jonas Budelmann on 16/09/13.
//  Copyright (c) 2013 cloudling. All rights reserved.
//

#import <Foundation/Foundation.h>

#import "CASStyler.h"
#import "CASStyleableItem.h"
#import "CASUtilities.h"
#import "CASObjectClassDescriptor.h"
#import "CASArgumentDescriptor.h"
#import "CASPropertyDescriptor.h"

#import "UIColor+CASAdditions.h"
#import "UITextField+CASAdditions.h"
#import "UIView+CASAdditions.h"
#import "UIBarItem+CASAdditions.h"
#import "UINavigationBar+CASAdditions.h"
#import "UITabBar+CASAdditions.h"
#import "UIToolbar+CASAdditions.h"
#import "UIViewController+CASAdditions.h"
#import "UINavigationItem+CASAdditions.h"


================================================
FILE: Classy/Parser/CASDeviceOSVersionItem.h
================================================
//
//  CASDeviceOSVersionItem.h
//  Classy
//
//  Created by Jonas Budelmann on 25/11/13.
//  Copyright (c) 2013 cloudling. All rights reserved.
//

#import <UIKit/UIKit.h>
#import "CASDeviceSelectorItem.h"

@interface CASDeviceOSVersionItem : NSObject <CASDeviceSelectorItem>

@property (nonatomic, copy) NSString *version;
@property (nonatomic, assign) CASRelation relation;

@end


================================================
FILE: Classy/Parser/CASDeviceOSVersionItem.m
================================================
//
//  CASDeviceOSVersionItem.m
//  Classy
//
//  Created by Jonas Budelmann on 25/11/13.
//  Copyright (c) 2013 cloudling. All rights reserved.
//

#import "CASDeviceOSVersionItem.h"
#import "CASUtilities.h"
#import "CASDeviceSelector.h"

@implementation CASDeviceOSVersionItem

- (BOOL)isValid {
    switch (self.relation) {
        case CASRelationLessThan:
            return CASDeviceSystemVersionIsLessThan(self.version);
        case CASRelationLessThanOrEqual:
            return CASDeviceSystemVersionIsLessThanOrEqualTo(self.version);
        case CASRelationEqual:
            return CASDeviceSystemVersionIsEqualTo(self.version);
        case CASRelationGreaterThanOrEqual:
            return CASDeviceSystemVersionIsGreaterThanOrEqualTo(self.version);
        case CASRelationGreaterThan:
            return CASDeviceSystemVersionIsGreaterThan(self.version);
        default:
            NSAssert(NO, @"Invalid ralation");
            return NO;
    }
}

- (NSString *)stringValue {
    return [NSString stringWithFormat:@"(version:%@%@)", [CASDeviceSelector stringFromRelation:self.relation], self.version];
}

#pragma mark - NSCoding

- (id)initWithCoder:(NSCoder *)aDecoder {
    self = [self init];
    if (nil != self) {
        self.relation = [aDecoder decodeIntegerForKey:NSStringFromSelector(@selector(relation))];
        self.version = [aDecoder decodeObjectForKey:NSStringFromSelector(@selector(version))];
    }
    return self;
}

- (void)encodeWithCoder:(NSCoder *)aCoder {
    [aCoder encodeInteger:self.relation forKey:NSStringFromSelector(@selector(relation))];
    [aCoder encodeObject:self.version forKey:NSStringFromSelector(@selector(version))];
}

@end


================================================
FILE: Classy/Parser/CASDeviceScreenSizeItem.h
================================================
//
//  Created by Ole Gammelgaard Poulsen on 05/04/14.
//  Copyright (c) 2014 SHAPE A/S. All rights reserved.
//

#import <Foundation/Foundation.h>
#import "CASDeviceSelectorItem.h"
#import "CASDeviceSelector.h"


@interface CASDeviceScreenSizeItem : NSObject <CASDeviceSelectorItem>

@property (nonatomic, assign) float value;
@property (nonatomic, assign) CASRelation relation;
@property(nonatomic, assign) CASDeviceSelectorScreenDimension dimension;

@end


================================================
FILE: Classy/Parser/CASDeviceScreenSizeItem.m
================================================
//
//  Created by Ole Gammelgaard Poulsen on 05/04/14.
//  Copyright (c) 2014 SHAPE A/S. All rights reserved.
//

#import "CASDeviceScreenSizeItem.h"

@implementation CASDeviceScreenSizeItem

- (BOOL)isValid {
    CGSize screenSize = [[UIScreen mainScreen] bounds].size;
    BOOL isPad = UIDevice.currentDevice.userInterfaceIdiom == UIUserInterfaceIdiomPad;
    CGFloat minDimension = MIN(screenSize.width, screenSize.height);
    CGFloat maxDimension = MAX(screenSize.width, screenSize.height);
    CGFloat width = isPad ?  maxDimension : minDimension;
    CGFloat height = isPad ? minDimension : maxDimension;

    CGFloat sizeInDimension = self.dimension == CASDeviceSelectorScreenDimensionWidth ? width : height;

    switch (self.relation) {
        case CASRelationLessThan:
            return  sizeInDimension < self.value;
        case CASRelationLessThanOrEqual:
            return sizeInDimension <= self.value;
        case CASRelationEqual:
            return fabs(self.value - sizeInDimension) < 0.001;
        case CASRelationGreaterThanOrEqual:
            return sizeInDimension >= self.value;
        case CASRelationGreaterThan:
            return sizeInDimension > self.value;
        default:
            NSAssert(NO, @"There should not be undefined relations");
            return NO;
    }
}

- (NSString *)stringValue {
    NSString *dimensionString = self.dimension == CASDeviceSelectorScreenDimensionWidth ? @"width" : @"height";
    return [NSString stringWithFormat:@"(screen-%@:%@%.0f)", dimensionString, [CASDeviceSelector stringFromRelation:self.relation], self.value];
}

#pragma mark - NSCoding

- (id)initWithCoder:(NSCoder *)aDecoder {
    self = [self init];
    if (nil != self) {
        self.relation = [aDecoder decodeIntegerForKey:NSStringFromSelector(@selector(relation))];
        self.value = [aDecoder decodeFloatForKey:NSStringFromSelector(@selector(value))];
        self.dimension = [aDecoder decodeIntegerForKey:NSStringFromSelector(@selector(dimension))];
    }
    return self;
}

- (void)encodeWithCoder:(NSCoder *)aCoder {
    [aCoder encodeInteger:self.relation forKey:NSStringFromSelector(@selector(relation))];
    [aCoder encodeFloat:self.value forKey:NSStringFromSelector(@selector(value))];
    [aCoder encodeInteger:self.dimension forKey:NSStringFromSelector(@selector(dimension))];
}

@end


================================================
FILE: Classy/Parser/CASDeviceSelector.h
================================================
//
//  CASStyleMediaSelector.h
//  Classy
//
//  Created by Jonas Budelmann on 24/11/13.
//  Copyright (c) 2013 cloudling. All rights reserved.
//

#import <UIKit/UIKit.h>
#import "CASDeviceTypeItem.h"
#import "CASDeviceOSVersionItem.h"

typedef NS_ENUM(NSUInteger, CASDeviceSelectorScreenDimension) {
    CASDeviceSelectorScreenDimensionWidth = 0,
    CASDeviceSelectorScreenDimensionHeight,
};

@interface CASDeviceSelector : NSObject <NSCoding>

@property (nonatomic, strong, readonly) NSArray *items;

+ (NSString *)stringFromRelation:(CASRelation)relation;

- (void)addItems:(NSArray *)items;
- (void)addDeviceType:(CASDeviceType)deviceType;
- (BOOL)addOSVersion:(NSString *)versionExpression;
- (BOOL)addScreenSize:(NSString *)sizeExpression dimension:(CASDeviceSelectorScreenDimension)dimension;

- (BOOL)isValid;
- (NSString *)stringValue;

@end


================================================
FILE: Classy/Parser/CASDeviceSelector.m
================================================
//
//  CASStyleMediaSelector.m
//  Classy
//
//  Created by Jonas Budelmann on 24/11/13.
//  Copyright (c) 2013 cloudling. All rights reserved.
//

#import "CASDeviceSelector.h"
#import "NSString+CASAdditions.h"
#import "CASDeviceScreenSizeItem.h"

@implementation CASDeviceSelector {
    NSMutableArray *_items;
}

- (id)init {
    self = [super init];
    if (!self) return nil;

    _items = NSMutableArray.new;

    return self;
}

- (NSArray *)items {
    return _items;
}

- (void)addItems:(NSArray *)items {
    [_items addObjectsFromArray:items];
}

- (void)addDeviceType:(CASDeviceType)deviceType {
    CASDeviceTypeItem *item = CASDeviceTypeItem.new;
    item.deviceType = deviceType;
    [_items addObject:item];
}

- (BOOL)addOSVersion:(NSString *)versionExpression {
    NSString *valueString = [self valueFromExpression:versionExpression];
    NSString *relationString = [versionExpression substringToIndex:versionExpression.length - valueString.length];

    CASRelation relation = [self relationFromExpression:relationString];
    if (relation == CASRelationUndefined) return NO;

    CASDeviceOSVersionItem *item = CASDeviceOSVersionItem.new;
    item.version = valueString;
    item.relation = relation;
    [_items addObject:item];

    return YES;
}

- (BOOL)addScreenSize:(NSString *)sizeExpression dimension:(CASDeviceSelectorScreenDimension)dimension {
    NSString *valueString = [self valueFromExpression:sizeExpression];
    NSString *relationString = [sizeExpression substringToIndex:sizeExpression.length - valueString.length];

    CASRelation relation = [self relationFromExpression:relationString];
    if (relation == CASRelationUndefined) return NO;

    CASDeviceScreenSizeItem *item = CASDeviceScreenSizeItem.new;
    item.value = valueString.floatValue;
    item.relation = relation;
    item.dimension = dimension;
    [_items addObject:item];

    return YES;
}

- (NSString *)valueFromExpression:(NSString *)relation {
    NSCharacterSet *equalityCharacterSet = [NSCharacterSet characterSetWithCharactersInString:@">=<"];
    NSString *valueString = [relation cas_stringByTrimmingLeadingCharactersInSet:equalityCharacterSet];
    return valueString;
}

- (CASRelation)relationFromExpression:(NSString *)expression {
    CASRelation relation = CASRelationEqual;
    if ([expression isEqualToString:@"<"]) {
        relation = CASRelationLessThan;
    } else if ([expression isEqualToString:@"<="]) {
        relation = CASRelationLessThanOrEqual;
    } else if ([expression isEqualToString:@"=="]) {
        relation = CASRelationEqual;
    } else if ([expression isEqualToString:@">="]) {
        relation = CASRelationGreaterThanOrEqual;
    } else if ([expression isEqualToString:@">"]) {
        relation = CASRelationGreaterThan;
    } else if (relation == CASRelationEqual && expression.length) {
       relation = CASRelationUndefined;
    }
    return relation;
}

- (BOOL)isValid {
    for (id<CASDeviceSelectorItem> item in self.items) {
        if (!item.isValid) {
            return NO;
        }
    }
    return YES;
}

- (NSString *)stringValue {
    NSMutableString *string = NSMutableString.new;

    [self.items enumerateObjectsUsingBlock:^(id<CASDeviceSelectorItem> item, NSUInteger idx, BOOL *stop) {
        [string appendString:item.stringValue];
        if (idx != self.items.count - 1) {
            [string appendString:@" and "];
        }
    }];

    return string;
}

+ (NSString *)stringFromRelation:(CASRelation)relation {
    switch (relation) {
        case CASRelationLessThan:
            return @"<";
        case CASRelationLessThanOrEqual:
            return @"<=";
        case CASRelationEqual:
            return @"";
        case CASRelationGreaterThanOrEqual:
            return @">=";
        case CASRelationGreaterThan:
            return @">";
        case CASRelationUndefined:
            NSAssert(NO, @"Relation should not be undefined");
            return nil;
        default:
            NSAssert(NO, @"Relation should not be an undefined enum value");
            return nil;
    }
}

#pragma mark - NSCoding

- (id)initWithCoder:(NSCoder *)aDecoder {
    self = [self init];
    if (nil != self) {
        _items = [aDecoder decodeObjectForKey:NSStringFromSelector(@selector(items))];
    }
    return self;
}

- (void)encodeWithCoder:(NSCoder *)aCoder {
    [aCoder encodeObject:self.items forKey:NSStringFromSelector(@selector(items))];
}

@end


================================================
FILE: Classy/Parser/CASDeviceSelectorItem.h
================================================
//
//  CASDeviceSelectorItem.h
//  Classy
//
//  Created by Jonas Budelmann on 25/11/13.
//  Copyright (c) 2013 cloudling. All rights reserved.
//

#import <Foundation/Foundation.h>

typedef NS_ENUM(NSInteger, CASRelation) {
    CASRelationLessThan = -2,
    CASRelationLessThanOrEqual = -1,
    CASRelationEqual = 0,
    CASRelationGreaterThanOrEqual = 1,
    CASRelationGreaterThan = 2,
    CASRelationUndefined = NSNotFound,
};

@protocol CASDeviceSelectorItem <NSObject, NSCoding>

- (BOOL)isValid;
- (NSString *)stringValue;

@end


================================================
FILE: Classy/Parser/CASDeviceTypeItem.h
================================================
//
//  CASDeviceTypeItem.h
//  Classy
//
//  Created by Jonas Budelmann on 25/11/13.
//  Copyright (c) 2013 cloudling. All rights reserved.
//

#import <UIKit/UIKit.h>
#import "CASDeviceSelectorItem.h"


typedef NS_ENUM(NSInteger, CASDeviceType) {
    CASDeviceTypePhone = 0,
    CASDeviceTypePad = 1,
};

@interface CASDeviceTypeItem : NSObject <CASDeviceSelectorItem>

@property (nonatomic, assign) CASDeviceType deviceType;

@end


================================================
FILE: Classy/Parser/CASDeviceTypeItem.m
================================================
//
//  CASDeviceTypeItem.m
//  Classy
//
//  Created by Jonas Budelmann on 25/11/13.
//  Copyright (c) 2013 cloudling. All rights reserved.
//

#import "CASDeviceTypeItem.h"

@implementation CASDeviceTypeItem

- (BOOL)isValid {
    if (self.deviceType == CASDeviceTypePhone) {
        return UIDevice.currentDevice.userInterfaceIdiom == UIUserInterfaceIdiomPhone;
    } else {
        return UIDevice.currentDevice.userInterfaceIdiom == UIUserInterfaceIdiomPad;
    }
}

- (NSString *)stringValue {
    if (self.deviceType == CASDeviceTypePhone) {
        return @"phone";
    }
    return @"pad";
}

#pragma mark - NSCoding

- (id)initWithCoder:(NSCoder *)aDecoder {
    self = [self init];
    if (nil != self) {
        self.deviceType = [aDecoder decodeIntegerForKey:NSStringFromSelector(@selector(deviceType))];
    }
    return self;
}

- (void)encodeWithCoder:(NSCoder *)aCoder {
    [aCoder encodeInteger:self.deviceType forKey:NSStringFromSelector(@selector(deviceType))];
}

@end


================================================
FILE: Classy/Parser/CASExpressionSolver.h
================================================
//
//  CASExpressionSolver.h
//  Classy
//
//  Created by Jonas Budelmann on 18/10/13.
//  Copyright (c) 2013 cloudling. All rights reserved.
//

#import <Foundation/Foundation.h>

@interface CASExpressionSolver : NSObject

/**
 *  Takes an array of tokens and reduces any expressions contained within to a numerical value.
 *  Skips tokens which cannot be evaluated in a expression
 *
 *  @return a NSArray of tokens
 */
- (NSArray *)tokensByReducingTokens:(NSArray *)tokens;

@end


================================================
FILE: Classy/Parser/CASExpressionSolver.m
================================================
//
//  CASExpressionSolver.m
//  Classy
//
//  Created by Jonas Budelmann on 18/10/13.
//  Copyright (c) 2013 cloudling. All rights reserved.
//

#import "CASExpressionSolver.h"
#import "CASToken.h"

@interface CASExpressionSolver ()

@property (nonatomic, strong) NSMutableArray *tokens;
@property (nonatomic, strong) NSMutableDictionary *tupleByIndex;

@end

@implementation CASExpressionSolver

- (NSArray *)tokensByReducingTokens:(NSArray *)tokens {
    self.tokens = tokens.mutableCopy;
    self.tupleByIndex = NSMutableDictionary.new;
    [self extractTuples];

    NSMutableArray *tokenStack = NSMutableArray.new;
    NSMutableArray *expressionStack = NSMutableArray.new;
    NSMutableDictionary *expressionMap = NSMutableDictionary.new;
    NSMutableDictionary *tupleMap = NSMutableDictionary.new;

    for (CASToken *token in self.tokens) {
        BOOL isPlaceholder = token.value == (id)NSNull.null;
        BOOL isFunctionKeyword = !isPlaceholder && [self.class.acceptableExpressionKeywords containsObject:token.value];
        if (isPlaceholder || isFunctionKeyword || token.isPossiblyExpression ) {
            if (token.isWhitespace && !expressionStack.count) {
                [tokenStack addObject:token];
                continue;
            }
            
            BOOL split = [token valueIsEqualTo:@","] || [self doesToken:token requireSplitForTokens:self.tokens];
            if (split) {
                [tokenStack addObject:NSNull.null];
                expressionMap[@(tokenStack.count-1)] = expressionStack;
                expressionStack = NSMutableArray.new;
            }

            if (isPlaceholder) {
                tupleMap[@(tokenStack.count)] = self.tupleByIndex[@([self.tokens indexOfObject:token])];
            }

            [expressionStack addObject:token];
        } else {
            [tokenStack addObject:token];
        }
    }

    if (expressionStack.count) {
        [tokenStack addObject:NSNull.null];
        expressionMap[@(tokenStack.count-1)] = expressionStack;
    }

    [expressionMap enumerateKeysAndObjectsUsingBlock:^(NSNumber *key, NSMutableArray *expressionStack, BOOL *stop) {
        NSArray *tuple = tupleMap[key];
        NSInteger index = [key integerValue];
        if (tuple.count) {
            [tokenStack replaceObjectAtIndex:index withObject:[CASToken tokenOfType:CASTokenTypeLeftRoundBrace value:@"("]];
            for (CASToken *tupleToken in tuple) {
                CASToken *token = [self evaluateTokens:expressionStack withPlaceholderToken:tupleToken];
                [tokenStack insertObject:token atIndex:++index];
                if ([tuple indexOfObject:tupleToken] != tuple.count - 1) {
                    [tokenStack insertObject:[CASToken tokenOfType:CASTokenTypeOperator value:@","] atIndex:++index];
                }
            }
            [tokenStack insertObject:[CASToken tokenOfType:CASTokenTypeRightRoundBrace value:@")"] atIndex:++index];
        } else {
            CASToken *token = [self evaluateTokens:expressionStack withPlaceholderToken:nil];
            [tokenStack replaceObjectAtIndex:index withObject:token];
        }
    }];

    return tokenStack;
}

#pragma mark - private

- (void)extractTuples {
    NSArray *tokens = [self.tokens copy];
    NSInteger braceCount = 0, previousBraceCount = 0;
    NSRange tupleRange = NSMakeRange(0, 0);
    NSMutableArray *tuple;

    // TODO this is bit convuluted and fragile.
    // look into extending something like DDMathParser to support tuples

    for (CASToken *token in tokens) {
        if (token.type == CASTokenTypeLeftRoundBrace) {
            braceCount++;
        } else if (token.type == CASTokenTypeRightRoundBrace) {
            braceCount--;
        }

        if (previousBraceCount == 0 && braceCount == 1) {
            // opening tuple brace
            tuple = [NSMutableArray arrayWithObject:NSMutableArray.new];
            tupleRange.location = [self.tokens indexOfObject:token];
        } else if (previousBraceCount > 0 && braceCount == 0) {
            // closing tuple brace
            if (tuple.count > 1) {
                for (NSMutableArray *expressionStack in tuple.copy) {
                    CASToken *token = [self evaluateTokens:expressionStack withPlaceholderToken:nil];
                    [tuple replaceObjectAtIndex:[tuple indexOfObject:expressionStack] withObject:token];
                }
                NSInteger endIndex = [self.tokens indexOfObject:token] + 1;
                tupleRange.length = endIndex - tupleRange.location;
                [self.tokens removeObjectsInRange:tupleRange];
                CASToken *placeholderToken = [CASToken tokenOfType:CASTokenTypeUnit value:NSNull.null];

                [self.tokens insertObject:placeholderToken atIndex:tupleRange.location];
                self.tupleByIndex[@(tupleRange.location)] = tuple;
                tuple = nil;
            }
        } else if (braceCount >= 1) {
            // within tuple

            BOOL split = [token valueIsEqualTo:@","] || [self doesToken:token requireSplitForTokens:tokens];
            if (split) {
                [tuple addObject:NSMutableArray.new];
            }
            if (![token valueIsEqualTo:@","] && !token.isWhitespace) {
                [tuple.lastObject addObject:token];
            }
        }

        previousBraceCount = braceCount;
    }
}

- (BOOL)doesToken:(CASToken *)token requireSplitForTokens:(NSArray *)tokens {
    CASToken *prevNonWhitespaceToken;
    for (NSInteger i = [tokens indexOfObject:token] - 1; i >= 0; i--) {
        if (![tokens[i] isWhitespace]) {
            prevNonWhitespaceToken = tokens[i];
            break;
        }
    }

    return prevNonWhitespaceToken != nil && (
        (prevNonWhitespaceToken.type == CASTokenTypeUnit
         && token.type == CASTokenTypeLeftRoundBrace)
     || (prevNonWhitespaceToken.type == CASTokenTypeRightRoundBrace
         && token.type == CASTokenTypeUnit)
     || (prevNonWhitespaceToken.type == CASTokenTypeUnit
         && token.type == CASTokenTypeUnit));
}

+ (NSSet *)acceptableExpressionKeywords {
    //not a complete list but added ones that seemed useful
    static NSSet * _acceptableExpressionKeywords = nil;
    static dispatch_once_t onceToken;
    dispatch_once(&onceToken, ^{
        _acceptableExpressionKeywords = [[NSSet alloc] initWithArray:@[
            @"floor", @"abs", @"random", @"ceiling", @"abs",
            @"uppercase", @"lowercase", @"log"
        ]];
    });

    return _acceptableExpressionKeywords;
}

- (CASToken *)evaluateTokens:(NSArray *)tokens withPlaceholderToken:(CASToken *)placeholderToken {
    if (tokens.count == 1) {
        return tokens.lastObject;
    }

    NSMutableArray *values = NSMutableArray.new;
    for (CASToken *token in tokens) {
        if (token.value == (id)NSNull.null) {
            [values addObject:placeholderToken.stringValue];
        } else {
            [values addObject:token.stringValue];
        }
    }

    NSExpression *expression = [NSExpression expressionWithFormat:[values componentsJoinedByString:@""]];
    id value = [expression expressionValueWithObject:nil context:nil];
    return [CASToken tokenOfType:CASTokenTypeUnit value:value];
}

@end


================================================
FILE: Classy/Parser/CASInvocation.h
================================================
//
//  CASInvocation.h
//  
//
//  Created by Jonas Budelmann on 5/11/13.
//
//

#import <Foundation/Foundation.h>

@interface CASInvocation : NSObject

/**
 *  Wraps an NSInvocation with an optional keypath.
 *  If keypath is non-nil will alter the target when invokeWithTarget: is called
 *
 *  @param invocation NSInvocation to wrap
 *  @param keyPath to store for later use
 *
 *  @return CASInvocation
 */
- (id)initWithInvocation:(NSInvocation *)invocation forKeyPath:(NSString *)keyPath;

/**
 *  Invoke the wrapped NSInvocation.
 *  If keypath is non-nil target will be evaluated keypath
 *
 *  @param target root object
 */
- (void)invokeWithTarget:(id)target;

@end


================================================
FILE: Classy/Parser/CASInvocation.m
================================================
//
//  CASInvocation.m
//  
//
//  Created by Jonas Budelmann on 5/11/13.
//
//

#import "CASInvocation.h"

@interface CASInvocation ()

@property (nonatomic, copy) NSString *keyPath;
@property (nonatomic, strong) NSInvocation *invocation;

@end

@implementation CASInvocation

- (id)initWithInvocation:(NSInvocation *)invocation forKeyPath:(NSString *)keyPath {
    self = [super init];
    if (!self) return nil;

    self.invocation = invocation;
    self.keyPath = keyPath;

    return self;
}

- (void)invokeWithTarget:(id)target {
    id resolvedTarget = self.keyPath ? [target valueForKeyPath:self.keyPath] : target;
    [self.invocation invokeWithTarget:resolvedTarget];
}

@end


================================================
FILE: Classy/Parser/CASLexer.h
================================================
//
//  CASLexer.h
//  Classy
//
//  Created by Jonas Budelmann on 15/09/13.
//  Copyright (c) 2013 cloudling. All rights reserved.
//

#import <Foundation/Foundation.h>
#import "CASToken.h"

extern NSString * const CASParseErrorDomain;
extern NSInteger const CASParseErrorInvalidToken;
extern NSInteger const CASParseErrorInvalidIndentation;
extern NSString * const CASParseFailingLineNumberErrorKey;
extern NSString * const CASParseFailingStringErrorKey;

@interface CASLexer : NSObject

/**
 *  The error, if any, that occurred during tokenisation
 */
@property (nonatomic, strong, readonly) NSError *error;

/**
 *  The remaining length of string to tokenise
 */
@property (nonatomic, assign, readonly) NSInteger length;

/**
 *  Create new `CASLexer` with a `NSString` to Tokenize
 */
- (id)initWithString:(NSString *)str;

/**
 *  Create a error and attach current string and line number to userInfo
 */
- (NSError *)errorWithDescription:(NSString *)description reason:(NSString *)reason code:(NSUInteger)code;

/**
 *  Token from stash or by advancing.
 *
 *  @return a `CASToken` which will remain or be added to the stash
 */
- (CASToken *)peekToken;

/**
 *  Token from stash or by advancing.
 *
 *  @return a `CASToken` which if coming from stash will be removed
 */
- (CASToken *)nextToken;

/**
 *  lookahead advances stash if needed
 *  then returns the token `count` from top of stash
 *
 *  @param count number of tokens to lookahead, minimum=1
 *
 *  @return The token corresponding to the number of lookaheads
 */
- (CASToken *)lookaheadByCount:(NSUInteger)count;

@end


================================================
FILE: Classy/Parser/CASLexer.m
================================================
//
//  CASLexer.m
//  Classy
//
//  Created by Jonas Budelmann on 15/09/13.
//  Copyright (c) 2013 cloudling. All rights reserved.
//

#import "CASLexer.h"
#import "NSRegularExpression+CASAdditions.h"
#import "NSString+CASAdditions.h"
#import "UIColor+CASAdditions.h"
#import "CASUnitToken.h"

NSString * const CASParseErrorDomain = @"CASParseErrorDomain";
NSInteger const CASParseErrorInvalidToken = 1;
NSInteger const CASParseErrorInvalidIndentation = 2;
NSString * const CASParseFailingLineNumberErrorKey = @"CASParseFailingLineNumberErrorKey";
NSString * const CASParseFailingStringErrorKey = @"CASParseFailingStringErrorKey";

@interface CASLexer ()

@property (nonatomic, strong, readwrite) NSError *error;
@property (nonatomic, strong) NSMutableString *str;
@property (nonatomic, strong) NSMutableArray *stash;
@property (nonatomic, strong) NSMutableArray *indentStack;
@property (nonatomic, strong) CASToken *previousToken;
@property (nonatomic, strong) NSDictionary *regexCache;
@property (nonatomic, strong) NSRegularExpression *indentRegex;
@property (nonatomic, assign) NSInteger lineNumber;

@end

@implementation CASLexer

- (id)initWithString:(NSString *)str {
    self = [super init];
    if (!self) return nil;

    self.str = [str mutableCopy];
    self.stash = NSMutableArray.new;
    self.indentStack = NSMutableArray.new;
    self.lineNumber = 1;

    // replace carriage returns (\r\n | \r) with newlines
    [CASRegex(@"\\r\\n?") cas_replaceMatchesInString:self.str withTemplate:@"\n"];
    
    // trim whitespace & newlines from end of string
    [CASRegex(@"\\s+$") cas_replaceMatchesInString:self.str withTemplate:@"\n"];

    NSString *units = [@[@"pt", @"px", @"%"] componentsJoinedByString:@"|"];

    // cache regex's
    self.regexCache = @{

        // 1 of `;` followed by 0-* of whitespace
        @(CASTokenTypeSemiColon) : @[ CASRegex(@"^;[ \\t]*") ],

        // 1 of `^` followed by 0-* of whitespace
        @(CASTokenTypeCarat)     : @[ CASRegex(@"^\\^[ \\t]*") ],

        // new line followed by tabs or spaces
        @(CASTokenTypeIndent)    : @[ CASRegex(@"^\\n([\\t]*)"),
                                      CASRegex(@"^\\n([ ]*)") ],

        // #rrggbbaa | #rrggbb | #rgb
        @(CASTokenTypeColor)     : @[ CASRegex(@"^#([a-fA-F0-9]{8})[ \\t]*"),
                                      CASRegex(@"^#([a-fA-F0-9]{6})[ \\t]*"),
                                      CASRegex(@"^#([a-fA-F0-9]{3})[ \\t]*") ],

        // string enclosed in single or double quotes
        @(CASTokenTypeString)    : @[ CASRegex(@"^(\"[^\"]*\"|'[^']*')[ \\t]*") ],

        // decimal/integer number with optional (px, pt) suffix
        @(CASTokenTypeUnit)      : @[ CASRegex(@"^(-)?(\\d+\\.\\d+|\\d+|\\.\\d+)(%@)?[ \\t]*", units) ],

        // true | false | YES | NO
        @(CASTokenTypeBoolean)   : @[ CASRegex(@"^(true|false|YES|NO)\\b([ \\t]*)") ],

        // optional `@` | `-` then at least one `_a-zA-Z$` following by any alphanumeric char or `-` or `$`
        @(CASTokenTypeRef)       : @[ CASRegex(@"^(@)?(-*[_a-zA-Z$][-\\w\\d$]*)") ],

        // tests if string looks like math operation
        @(CASTokenTypeOperator)  : @[ CASRegex(@"^([.]{2,3}|&&|\\|\\||[!<>=?:]=|\\*\\*|[-+*\\/%%]=?|[,=?:!~<>&\\[\\]])([ \\t]*)") ],

        // 1-* of whitespace
        @(CASTokenTypeSpace)     : @[ CASRegex(@"^([ \\t]+)") ],

        // any character except `\n` | `{` | `,` | whitespace
        @(CASTokenTypeSelector)  : @[ CASRegex(@"^.*?(?=\\/\\/|[ \\t,\\n{])") ]
    };

    return self;
}

- (NSInteger)length {
    return self.str.length;
}

- (CASToken *)peekToken {
    return [self lookaheadByCount:1];
}

- (CASToken *)nextToken {
    CASToken *token = self.popToken;
    if (!token) {
        token = self.advanceToken;
        [self attachDebugInfoForToken:token];
    }
    self.previousToken = token;
    return token;
}

- (CASToken *)lookaheadByCount:(NSUInteger)count {
    NSAssert(count > 0, @"Invalid lookahead. Count `%lu` must be >= 1", (unsigned long)count);
    NSInteger fetch = count - self.stash.count;
    while (fetch > 0) {
        CASToken *token = self.advanceToken;
        [self attachDebugInfoForToken:token];
        [self.stash addObject:token];
        fetch = count - self.stash.count;
    }
    return self.stash[count-1];
}

- (void)attachDebugInfoForToken:(CASToken *)token {
    switch (token.type) {
        case CASTokenTypeNewline:
        case CASTokenTypeIndent:
            ++self.lineNumber;
            break;
        case CASTokenTypeOutdent:
            if (CASTokenTypeOutdent != self.previousToken.type) ++self.lineNumber;
            break;
        default:
            break;
    }
    token.lineNumber = self.lineNumber;
}

- (NSError *)errorWithDescription:(NSString *)description reason:(NSString *)reason code:(NSUInteger)code {
    NSInteger length = MIN(self.str.length, 25);
    NSString *format = length != self.str.length ? @"\"%@ ...\"" : @"\"%@\"";
    NSString *string = [NSString stringWithFormat:format, [self.str substringToIndex:length]];
    NSDictionary *userInfo = @{
        NSLocalizedDescriptionKey: description,
        NSLocalizedFailureReasonErrorKey: reason,
        CASParseFailingLineNumberErrorKey: @(self.lineNumber),
        CASParseFailingStringErrorKey: string
    };
    return [NSError errorWithDomain:CASParseErrorDomain code:code userInfo:userInfo];
}

#pragma mark - private

- (void)skip:(NSUInteger)n {
    [self.str deleteCharactersInRange:NSMakeRange(0, n)];
}

- (CASToken *)advanceToken {
    // TODO optimise
    // this could possibly be faster using simple string scanning (NSScanner), instead of regex
    // however all these regexs are anchored to start of string so should be fairly quick
    CASToken *token = self.eos
        ?: self.seperator
        ?: self.carat
        ?: self.comment
        ?: self.newline
        ?: self.squareBrace
        ?: self.curlyBrace
        ?: self.roundBrace
        ?: self.color
        ?: self.string
        ?: self.unit
        ?: self.boolean
        ?: self.ref
        ?: self.operation
        ?: self.space
        ?: self.selector;

    if (!token) {
        self.error = [self errorWithDescription:@"Invalid style string"
                                         reason:@"Could not determine token"
                                           code:CASParseErrorInvalidToken];
        return nil;
    }

    return token;
}

- (CASToken *)popToken {
    // Return the next stashed token and remove it from stash.
    if (self.stash.count) {
        CASToken *token = self.stash[0];
        [self.stash removeObjectAtIndex:0];
        return token;
    }
    return nil;
}

#pragma mark - tokens

- (CASToken *)eos {
    // EOS | trailing outdents.
    if (self.str.length) return nil;
    if (self.indentStack.count) {
        [self.indentStack removeObjectAtIndex:0];
        return [CASToken tokenOfType:CASTokenTypeOutdent];
    } else {
        return [CASToken tokenOfType:CASTokenTypeEOS];
    }
}

- (CASToken *)seperator {
    return [self testForTokenType:CASTokenTypeSemiColon transformValueBlock:nil];
}

- (CASToken *)carat {
    return [self testForTokenType:CASTokenTypeCarat transformValueBlock:^id(NSString *value, NSTextCheckingResult *match) {
        return value;
    }];
}

- (CASToken *)comment {
    // Single line
    if ([self.str hasPrefix:@"//"]) {
        NSInteger nextLine = [self.str rangeOfString:@"\n"].location;
        if (nextLine == NSNotFound) {
            nextLine = self.str.length;
        }
        [self skip:nextLine];
        return self.advanceToken;
    }

    // Multi-line
    if ([self.str hasPrefix:@"/*"]) {
        NSInteger closeComment = [self.str rangeOfString:@"*/"].location;
        if (closeComment == NSNotFound) {
            closeComment = self.str.length;
        } else {
            closeComment += 2;
        }
        NSInteger lines = [[self.str substringWithRange:NSMakeRange(0, closeComment)] componentsSeparatedByCharactersInSet:[NSCharacterSet newlineCharacterSet]].count;

        self.lineNumber += lines - 1;
        [self skip:closeComment];
        return self.advanceToken;
    }
    
    return nil;
}

- (CASToken *)newline {
    // we have established the indentation regexp
    NSTextCheckingResult *match;
    if (self.indentRegex){
        match = [self.indentRegex firstMatchInString:self.str options:0 range:NSMakeRange(0, self.str.length)];
    } else {
        // figure out if we are using tabs or spaces
        for (NSRegularExpression *regex in self.regexCache[@(CASTokenTypeIndent)]) {
            match = [regex firstMatchInString:self.str options:0 range:NSMakeRange(0, self.str.length)];
            if (match && [match rangeAtIndex:1].length) {
                self.indentRegex = regex;
                break;
            }
        }
    }

    if (!match) return nil;

    [self skip:match.range.length];

    if ([self.str hasPrefix:@" "] || [self.str hasPrefix:@"\t"]) {
        ++self.lineNumber;
        self.error = [self errorWithDescription:@"Invalid indentation"
                                         reason:@"You can use tabs or spaces to indent, but not both."
                                           code:CASParseErrorInvalidIndentation];
        return nil;
    }

    // Blank line
    if ([self.str hasPrefix:@"\n"]) {
        ++self.lineNumber;
        return self.advanceToken;
    }

    NSInteger indents = [match rangeAtIndex:1].length;
    if (self.indentStack.count && indents < [self.indentStack[0] integerValue]) {
        while (self.indentStack.count && [self.indentStack[0] integerValue] > indents) {
            [self.stash addObject:[CASToken tokenOfType:CASTokenTypeOutdent]];
            [self.indentStack removeObjectAtIndex:0];
        }
        return [self advanceToken];
    } else if (indents && indents != (self.indentStack.count ? [self.indentStack[0] integerValue] : 0)) {
        [self.indentStack insertObject:@(indents) atIndex:0];
        return [CASToken tokenOfType:CASTokenTypeIndent];
    } else {
        return [CASToken tokenOfType:CASTokenTypeNewline];
    }

    return self.advanceToken;
}

- (CASToken *)squareBrace {
    if ([self.str hasPrefix:@"["]) {
        [self skip:1];
        return [CASToken tokenOfType:CASTokenTypeLeftSquareBrace value:@"["];
    }
    if ([self.str hasPrefix:@"]"]) {
        [self skip:1];
        return [CASToken tokenOfType:CASTokenTypeRightSquareBrace value:@"]"];
    }
    return nil;
}

- (CASToken *)curlyBrace {
    if ([self.str hasPrefix:@"{"]) {
        [self skip:1];
        return [CASToken tokenOfType:CASTokenTypeLeftCurlyBrace value:@"{"];
    }
    if ([self.str hasPrefix:@"}"]) {
        [self skip:1];
        return [CASToken tokenOfType:CASTokenTypeRightCurlyBrace value:@"}"];
    }
    return nil;
}

- (CASToken *)roundBrace {
    if ([self.str hasPrefix:@"("]) {
        [self skip:1];
        return [CASToken tokenOfType:CASTokenTypeLeftRoundBrace value:@"("];
    }
    if ([self.str hasPrefix:@")"]) {
        [self skip:1];
        return [CASToken tokenOfType:CASTokenTypeRightRoundBrace value:@")"];
    }
    return nil;
}

- (CASToken *)color {
    return [self testForTokenType:CASTokenTypeColor transformValueBlock:^id(NSString *value, NSTextCheckingResult *match) {
        return [UIColor cas_colorWithHex:[value cas_stringByTrimmingWhitespace]];
    }];
}

- (CASToken *)string {
    return [self testForTokenType:CASTokenTypeString transformValueBlock:^id(NSString *value, NSTextCheckingResult *match) {
        NSString *string = [value cas_stringByTrimmingWhitespace];
        return [string substringWithRange:NSMakeRange(1, string.length-2)];
    }];
}

- (CASToken *)unit {
    __block NSString *suffix;
    __block NSString *rawValue;
    CASUnitToken *unitToken = (id)[self testForTokenType:CASTokenTypeUnit tokenClass:CASUnitToken.class transformValueBlock:^id(NSString *value, NSTextCheckingResult *match){
        NSRange suffixRange = [match rangeAtIndex:match.numberOfRanges-1];
        if (suffixRange.location != NSNotFound) {
            suffix = [value substringWithRange:suffixRange];
        }

        NSRange valueRange = [match rangeAtIndex:0];
        if (valueRange.location != NSNotFound) {
            rawValue = [value substringWithRange:valueRange];
        }
        return @([rawValue doubleValue]);
    }];

    if (unitToken) {
        unitToken.suffix = suffix;
        unitToken.rawValue = rawValue;
    }

    return unitToken;
}

- (CASToken *)boolean {
    return [self testForTokenType:CASTokenTypeBoolean transformValueBlock:^id(NSString *value, NSTextCheckingResult *match) {
        return @([value hasPrefix:@"true"] || [value hasPrefix:@"YES"]);
    }];
}

- (CASToken *)ref {
    return [self testForTokenType:CASTokenTypeRef transformValueBlock:^id(NSString *value, NSTextCheckingResult *match) {
        return value;
    }];
}

- (CASToken *)operation {
    return [self testForTokenType:CASTokenTypeOperator transformValueBlock:^id(NSString *value, NSTextCheckingResult *match) {
        return [self.str substringWithRange:[match rangeAtIndex:1]];
    }];
}

- (CASToken *)space {
    return [self testForTokenType:CASTokenTypeSpace transformValueBlock:^id(NSString *value, NSTextCheckingResult *match) {
        return value;
    }];
}

- (CASToken *)selector {
    return [self testForTokenType:CASTokenTypeSelector transformValueBlock:^id(NSString *value, NSTextCheckingResult *match) {
        return value;
    }];
}

#pragma mark - helpers

- (CASToken *)testForTokenType:(CASTokenType)tokenType transformValueBlock:(id(^)(NSString *value, NSTextCheckingResult *match))transformValueBlock {
    return [self testForTokenType:tokenType tokenClass:CASToken.class transformValueBlock:transformValueBlock];
}

- (CASToken *)testForTokenType:(CASTokenType)tokenType tokenClass:(Class)tokenClass transformValueBlock:(id(^)(NSString *value, NSTextCheckingResult *match))transformValueBlock {
    NSArray *regexes = self.regexCache[@(tokenType)];
    NSAssert(regexes.count, @"Invalid cache. No cached regex for CASTokenType `%@`", [CASToken stringForType:tokenType]);
    for (NSRegularExpression *regex in regexes) {
        NSTextCheckingResult *match = [regex firstMatchInString:self.str options:0 range:NSMakeRange(0, self.str.length)];
        if (match) {
            CASToken *token = [tokenClass tokenOfType:tokenType];
            if (transformValueBlock) {
                token.value = transformValueBlock([self.str substringWithRange:match.range], match);
            }
            [self skip:match.range.length];
            return token;
        }
    }
    return nil;
}

@end


================================================
FILE: Classy/Parser/CASParser.h
================================================
//
//  CASParser.h
//  Classy
//
//  Created by Jonas Budelmann on 15/09/13.
//  Copyright (c) 2013 cloudling. All rights reserved.
//

#import <Foundation/Foundation.h>
#import "CASLexer.h"

extern NSString * const CASParseFailingFilePathErrorKey;
extern NSInteger const CASParseErrorFileContents;

@interface CASParser : NSObject

@property (nonatomic, strong, readonly) NSArray *styleNodes;
@property (nonatomic, strong, readonly) NSSet *importedFileNames;

/**
 *  Create a parser with the given file path
 */
+ (CASParser *)parserFromFilePath:(NSString *)filePath variables:(NSDictionary *)variables error:(NSError **)error;

@end


================================================
FILE: Classy/Parser/CASParser.m
================================================
//
//  CASParser.m
//  Classy
//
//  Created by Jonas Budelmann on 15/09/13.
//  Copyright (c) 2013 cloudling. All rights reserved.
//

#import "CASParser.h"
#import "CASLexer.h"
#import "CASStyleNode.h"
#import "CASToken.h"
#import "CASUtilities.h"
#import "CASStyleProperty.h"
#import "CASStyleSelector.h"
#import "CASDeviceSelector.h"
#import "NSString+CASAdditions.h"

NSString * const CASParseFailingFilePathErrorKey = @"CASParseFailingFilePathErrorKey";
NSInteger const CASParseErrorFileContents = 2;

@interface CASParser ()

@property (nonatomic, strong) CASLexer *lexer;
@property (nonatomic, strong, readwrite) NSArray *styleNodes;
@property (nonatomic, strong) NSMutableDictionary *styleVars;
@property (nonatomic, strong) NSError *error;
@property (nonatomic, copy) NSString *filePath;

@end

@implementation CASParser {
    NSMutableSet *_importedFileNames;
}

+ (CASParser *)parserFromFilePath:(NSString *)filePath variables:(NSDictionary *)variables error:(NSError **)error {
    NSError *fileError = nil;
    NSString *contents = [NSString stringWithContentsOfFile:filePath
                                                   encoding:NSUTF8StringEncoding
                                                      error:&fileError];
    if (!contents || fileError) {
        NSMutableDictionary *userInfo = @{
            NSLocalizedDescriptionKey: @"Could not parse file",
            NSLocalizedFailureReasonErrorKey: @"File does not exist or is empty",
            CASParseFailingFilePathErrorKey : filePath ?: @""
        }.mutableCopy;

        if (fileError) {
            [userInfo setObject:fileError forKey:NSUnderlyingErrorKey];
        }

        if (error) {
            *error = [NSError errorWithDomain:CASParseErrorDomain code:CASParseErrorFileContents userInfo:userInfo];
        }

        return nil;
    }

    CASLog(@"Start parsing file \n%@", filePath);
    NSError *parseError = nil;
    CASParser *parser = CASParser.new;
    parser.filePath = filePath;
    parser.styleVars = NSMutableDictionary.new;

    //transform variables into tokens
    [variables enumerateKeysAndObjectsUsingBlock:^(NSString *key, id obj, BOOL *stop) {
        if ([obj isKindOfClass:CASStyleProperty.class]) {
            parser.styleVars[key] = obj;
            return;
        }
        NSString *stringValue = [obj isKindOfClass:NSString.class] ? obj : [obj stringValue];
        CASLexer *lexer = [[CASLexer alloc] initWithString:stringValue];
        CASToken *nameToken = [CASToken tokenOfType:CASTokenTypeRef value:key];

        NSMutableArray *tokens = NSMutableArray.new;
        while (lexer.peekToken && lexer.peekToken.type != CASTokenTypeEOS) {
            [tokens addObject:lexer.nextToken];
        }
        parser.styleVars[key] = [[CASStyleProperty alloc] initWithNameToken:nameToken valueTokens:tokens];
    }];
    parser.styleNodes = [parser parseString:contents error:&parseError];

    if (parseError) {
        if (error) {
            *error = parseError;
        }
        return nil;
    }

    if (parseError) {
        NSMutableDictionary *userInfo = parseError.userInfo.mutableCopy;
        [userInfo addEntriesFromDictionary:@{ CASParseFailingFilePathErrorKey : filePath }];
        if (error) {
            *error = [NSError errorWithDomain:parseError.domain code:parseError.code userInfo:userInfo];
        }
        return nil;
    }

    return parser;
}

- (NSError *)error {
    return _error ?: self.lexer.error;
}

- (NSSet *)importedFileNames {
    return _importedFileNames;
}

- (NSArray *)parseString:(NSString *)string error:(NSError **)error {
    self.lexer = [[CASLexer alloc] initWithString:string];
    _importedFileNames = NSMutableSet.new;

    NSMutableArray *allStyleNodes = NSMutableArray.new;
    NSMutableArray *styleNodesStack = NSMutableArray.new;
    NSMutableArray *stylePropertiesStack = NSMutableArray.new;

    while (self.peekToken.type != CASTokenTypeEOS) {
        if (self.error) {
            if (error) *error = self.error;
            return nil;
        }

        //check for import
        if (self.peekToken.type == CASTokenTypeRef && [self.peekToken valueIsEqualTo:@"@import"]) {
            if (styleNodesStack.count) {
                // can't have vars inside styleNodes
                if (error) {
                    *error = [self.lexer errorWithDescription:@"@import cannot be used inside style selectors"
                                                       reason:nil
                                                         code:CASParseErrorFileContents];
                }
                return nil;
            }

            //skip import token
            [self nextToken];

            //skip whitespace
            [self consumeTokensMatching:^BOOL(CASToken *token) {
                return token.type == CASTokenTypeSpace;
            }];

            //combine all following tokens until newline | ;
            NSMutableArray *fileNameComponents = NSMutableArray.new;
            while (self.peekToken.type != CASTokenTypeNewline &&
                   self.peekToken.type != CASTokenTypeSemiColon &&
                   self.peekToken.type != CASTokenTypeEOS) {
                [fileNameComponents addObject:self.nextToken.stringValue];
            }


            NSString *fileName = [[fileNameComponents componentsJoinedByString:@""] cas_stringByTrimmingWhitespace];
            fileName = [fileName stringByTrimmingCharactersInSet:[NSCharacterSet characterSetWithCharactersInString:@";"]];

            if ([fileName hasPrefix:@"$"]) {
                CASStyleProperty *property = self.styleVars[fileName];
                if (property) {
                    fileName = [property.values componentsJoinedByString:@""];
                }
            }

            if (!fileName.length) {
                if (error) {
                    *error = [self.lexer errorWithDescription:@"@import does not specify file to import"
                                                       reason:nil
                                                         code:CASParseErrorFileContents];
                }
                return nil;
            }

            [_importedFileNames addObject:fileName];
            NSString *filePath = [[self.filePath stringByDeletingLastPathComponent] stringByAppendingPathComponent:fileName];
            NSError *importError = nil;
            CASParser *parser = [CASParser parserFromFilePath:filePath variables:self.styleVars error:&importError];
            if (importError) {
                if (error) {
                    *error = importError;
                }
                return nil;
            }

            [allStyleNodes addObjectsFromArray:parser.styleNodes];
            [self.styleVars addEntriesFromDictionary:parser.styleVars];
            [_importedFileNames addObjectsFromArray:parser.importedFileNames.allObjects];

            [self consumeTokensMatching:^BOOL(CASToken *token) {
                return self.peekToken.type == CASTokenTypeNewline || self.peekToken.type == CASTokenTypeSemiColon;
            }];
            continue;
        }

        NSArray *styleNodes = [self nextStyleNodes];
        if (self.error) {
            if (error) *error = self.error;
            return nil;
        }

        if (styleNodes.count) {
            NSMutableArray *flattenStyleNodes = styleNodesStack.count ? NSMutableArray.new : nil;
            for (CASStyleNode *parentNode in styleNodesStack.lastObject) {
                for (CASStyleNode *styleNode in styleNodes) {
                    CASStyleNode *flattenStyleNode = CASStyleNode.new;
                    flattenStyleNode.deviceSelector = styleNode.deviceSelector;
                    if (!flattenStyleNode.deviceSelector && parentNode.deviceSelector) {
                        flattenStyleNode.deviceSelector = CASDeviceSelector.new;
                    }
                    [flattenStyleNode.deviceSelector addItems:parentNode.deviceSelector.items];
                    CASStyleSelector *parentSelector = [parentNode.styleSelector copy];
                    parentSelector.parent = YES;
                    if (!styleNode.styleSelector) {
                        flattenStyleNode.styleSelector = parentSelector;
                        parentSelector.parent = NO;
                    } else if (styleNode.styleSelector.lastSelector.shouldConcatToParent) {
                        CASStyleSelector *styleSelector = styleNode.styleSelector;
                        if (styleSelector.styleClass) {
                            parentSelector.styleClass = styleSelector.styleClass;
                        }
                        if (styleSelector.arguments) {
                            parentSelector.arguments = styleSelector.arguments;
                        }
                        flattenStyleNode.styleSelector = parentSelector;
                    } else {
                        flattenStyleNode.styleSelector = [styleNode.styleSelector copy];
                        flattenStyleNode.styleSelector.lastSelector.parentSelector = parentSelector;
                    }
                    [flattenStyleNodes addObject:flattenStyleNode];
                }
            }
            if (flattenStyleNodes.count) {
                styleNodes = flattenStyleNodes;
            }

            [allStyleNodes addObjectsFromArray:styleNodes];
            [styleNodesStack addObject:styleNodes];
            [self consumeTokenOfType:CASTokenTypeLeftCurlyBrace];
            [self consumeTokenOfType:CASTokenTypeIndent];
            continue;
        }

        CASStyleProperty *styleVar = [self nextStyleVar];
        if (self.error) {
            if (error) *error = self.error;
            return nil;
        }

        if (styleVar) {
            if (styleNodesStack.count) {
                // can't have vars inside styleNodes
                if (error) {
                    *error = [self.lexer errorWithDescription:@"Variables cannot be declared inside style selectors"
                                                       reason:[NSString stringWithFormat:@"Variable: %@", styleVar]
                                                         code:CASParseErrorFileContents];
                }
                return nil;
            }
            [styleVar resolveExpressions];
            self.styleVars[styleVar.nameToken.value] = styleVar;
            [self consumeTokensMatching:^BOOL(CASToken *token) {
                return token.type == CASTokenTypeSpace || token.type == CASTokenTypeSemiColon;
            }];
            continue;
        }

        // not a style group therefore must be a property
        BOOL isStylePropertyParent = NO;
        CASStyleProperty *styleProperty = [self nextStylePropertyIsParent:&isStylePropertyParent];
        if (self.error) {
            if (error) *error = self.error;
            return nil;
        }

        if (styleProperty) {
            if (!styleNodesStack.count) {
                if (error) {
                    *error = [self.lexer errorWithDescription:@"Invalid style property"
                                                       reason:@"Needs to be within a style node"
                                                         code:CASParseErrorFileContents];
                }
                return nil;
            }
            [styleProperty resolveExpressions];

            if (stylePropertiesStack.count) {
                CASStyleProperty *parent = stylePropertiesStack.lastObject;
                [parent addChildStyleProperty:styleProperty];
            } else {
                for (CASStyleNode *node in styleNodesStack.lastObject) {
                    [node addStyleProperty:styleProperty];
                }
            }

            if (isStylePropertyParent) {
                [stylePropertiesStack addObject:styleProperty];
            }
            continue;
        }

        __block NSInteger previousLength = NSNotFound;
        __block CASToken *previousToken = nil;
        BOOL acceptableToken = [self consumeTokensMatching:^BOOL(CASToken *token) {
            BOOL popStack = token.type == CASTokenTypeOutdent || token.type == CASTokenTypeRightCurlyBrace;

            // make sure we don't double pop
            BOOL alreadyPopped = previousLength == self.lexer.length
                && previousToken.type == CASTokenTypeOutdent
                && token.type == CASTokenTypeRightCurlyBrace;

            if (!alreadyPopped && popStack) {
                NSMutableArray *stack = stylePropertiesStack.count ? stylePropertiesStack : styleNodesStack;
                if (stack.count) {
                    [stack removeObjectAtIndex:stack.count-1];
                }
            }

            previousLength = self.lexer.length;
            previousToken = token;
            return popStack || token.isWhitespace || token.type == CASTokenTypeSemiColon;
        }];

        if (!acceptableToken) {
            NSString *description = [NSString stringWithFormat:@"Unexpected token `%@`", self.nextToken];
            if (error) {
                *error = [self.lexer errorWithDescription:description
                                                   reason:@"Token does not belong in current context"
                                                     code:CASParseErrorFileContents];
            }
            return nil;
        }
    }

    return allStyleNodes;
}

#pragma mark - token helpers

- (CASToken *)peekToken {
    return self.lexer.peekToken;
}

- (CASToken *)nextToken {
    CASToken *token = self.lexer.nextToken;
    return token;
}

- (CASToken *)lookaheadByCount:(NSUInteger)count {
    return [self.lexer lookaheadByCount:count];
}

- (CASToken *)consumeTokenOfType:(CASTokenType)type {
    if (type == self.peekToken.type) {
        // return token and remove from stack
        return self.nextToken;
    }
    return nil;
}

- (BOOL)consumeTokensMatching:(BOOL(^)(CASToken *token))matchBlock {
    BOOL anyMatches = NO;
    while (matchBlock(self.peekToken)) {
        anyMatches = YES;
        [self nextToken];
    }
    return anyMatches;
}

- (BOOL)isNestedPropertyAtIndex:(NSInteger)index {
    NSInteger tail = index;
    while (--tail > 0) {
        CASToken *tailToken = [self lookaheadByCount:tail];
        if ([tailToken valueIsEqualTo:@"@"]) {
            return YES;
        }
        if (!tailToken.isWhitespace && tailToken.type != CASTokenTypeLeftCurlyBrace) {
            return NO;
        }
    }
    return NO;
}

#pragma mark - nodes

- (CASStyleProperty *)nextStyleVar {
    // variable if following seq: CASTokenTypeRef, `=`, any token until newline
    NSInteger i = 1;
    CASToken *token = [self lookaheadByCount:i];
    BOOL hasEqualsSign = NO;
    CASToken *refToken;

    while (token && token.isPossiblyVar && !(hasEqualsSign && refToken)) {
        if (token.type == CASTokenTypeRef) {
            refToken = token;
        }
        if (refToken && [token valueIsEqualTo:@"="]) {
            hasEqualsSign = YES;
        }
        token = [self lookaheadByCount:++i];
    }

    if ([refToken.value hasPrefix:@"@"]) {
        self.error = [self.lexer errorWithDescription:@"Variables cannot begin with `@` character"
                                               reason:@"`@` is reserved for @media, @version and property lookup"
                                                 code:CASParseErrorFileContents];
    }

    if (hasEqualsSign && refToken) {
        // consume LHS of var
        while (--i >= 0) {
            [self nextToken];
        }

        // collect value tokens, enclose in ()
        NSMutableArray *valueTokens = NSMutableArray.new;
        NSInteger nonWhitespaceTokenCount = 0;
        while (token.type != CASTokenTypeNewline && token.type != CASTokenTypeSemiColon && token.type != CASTokenTypeEOS) {
            [valueTokens addObject:token];
            if (!token.isWhitespace) {
                nonWhitespaceTokenCount++;
            }
            token = [self nextToken];
        }

        // only wrap in braces if more than one non-whitespace token present
        if (nonWhitespaceTokenCount > 1) {
            [valueTokens insertObject:[CASToken tokenOfType:CASTokenTypeLeftRoundBrace value:@"("] atIndex:0];
            [valueTokens addObject:[CASToken tokenOfType:CASTokenTypeRightRoundBrace value:@")"]];
        }

        return [[CASStyleProperty alloc] initWithNameToken:refToken valueTokens:valueTokens];
    }
    return nil;
}

- (NSArray *)nextDeviceQueries {
    NSInteger i = 1;
    CASToken *token = [self lookaheadByCount:i];
    BOOL isDeviceQuery = NO;
    while (token && token.type != CASTokenTypeEOS
           && token.type != CASTokenTypeNewline
           && token.type != CASTokenTypeIndent
           && token.type != CASTokenTypeOutdent
           && token.type != CASTokenTypeLeftCurlyBrace) {

        if (!isDeviceQuery && token.type != CASTokenTypeSpace) {
            if ([token valueIsEqualTo:@"@media"] || [token valueIsEqualTo:@"@device"]) {
                isDeviceQuery = YES;
            } else {
                break;
            }
        }
        token = [self lookaheadByCount:++i];
    }

    if (!isDeviceQuery) return nil;

    CASStyleNode *currentNode = CASStyleNode.new;
    currentNode.deviceSelector = CASDeviceSelector.new;
    NSMutableArray *nodes = NSMutableArray.new;
    [nodes addObject:currentNode];

    BOOL waitingForRightRoundBrace = NO;

    NSString *itemName;
    NSMutableString *itemValue = NSMutableString.new;
    NSMutableString *itemRelation = NSMutableString.new;

    while (--i > 0) {
        token = [self nextToken];

        if (token.type == CASTokenTypeSpace) {
            continue;
        }

        if (waitingForRightRoundBrace) {
            if (!itemName) {
                if (token.type == CASTokenTypeRef) {
                    itemName = token.value;
                } else {
                    //TODO unexpected token error
                }
                continue;
            }

            if (token.type == CASTokenTypeRightRoundBrace) {
                waitingForRightRoundBrace = NO;
                if ([itemName isEqualToString:@"version"]) {
                    NSString *versionString = [(itemRelation ?: @"") stringByAppendingString:(itemValue ?: @"")];

                    [currentNode.deviceSelector addOSVersion:versionString];
                } else if ([itemName isEqualToString:@"screen-width"]) {
                    NSString *widthConstraintString = [(itemRelation ?: @"") stringByAppendingString:(itemValue ?: @"")];
                    [currentNode.deviceSelector addScreenSize:widthConstraintString dimension:CASDeviceSelectorScreenDimensionWidth];
                } else if ([itemName isEqualToString:@"screen-height"]) {
                    NSString *heightConstraintString = [(itemRelation ?: @"") stringByAppendingString:(itemValue ?: @"")];
                    [currentNode.deviceSelector addScreenSize:heightConstraintString dimension:CASDeviceSelectorScreenDimensionHeight];
                }
                itemRelation = NSMutableString.new;
                itemValue = NSMutableString.new;

            } else if (token.type == CASTokenTypeLeftRoundBrace) {
                //TODO unexpected token error
            } else if (token.type == CASTokenTypeRef) {
                itemName = token.value;
            } else if (token.type == CASTokenTypeOperator) {
                if ([token valueIsEqualTo:@":"]) {
                    continue;
                } else {
                    [itemRelation appendString:token.value];
                }
            } else if (token.type == CASTokenTypeUnit) {
                [itemRelation appendString:token.stringValue];
            }

            continue;
        }

        if (token.type == CASTokenTypeLeftRoundBrace) {
            waitingForRightRoundBrace = YES;
        } else if (token.type == CASTokenTypeRightRoundBrace) {
            //TODO unexpected token error
        } else if (token.type == CASTokenTypeOperator) {
            if ([token valueIsEqualTo:@","]) {
                currentNode = CASStyleNode.new;
                currentNode.deviceSelector = CASDeviceSelector.new;
                [nodes addObject:currentNode];
            }
        } else if (token.type == CASTokenTypeRef) {
            NSString *lowercaseValue = [token.value lowercaseString];
            if ([lowercaseValue isEqualToString:@"ipad"] || [lowercaseValue isEqualToString:@"pad"]) {
                [currentNode.deviceSelector addDeviceType:CASDeviceTypePad];
            } else if ([lowercaseValue isEqualToString:@"iphone"] || [lowercaseValue isEqualToString:@"phone"]) {
                [currentNode.deviceSelector addDeviceType:CASDeviceTypePhone];
            } else if ([lowercaseValue isEqualToString:@"and"]) {
                continue;
            } else {
                //TODO unexpected token error
            }
        } else {
            //TODO unexpected token error
        }
    }

    return nodes;
}

- (NSArray *)nextStyleNodes {
    NSArray *deviceQueryNodes = [self nextDeviceQueries];
    if (deviceQueryNodes) return deviceQueryNodes;

    NSInteger i = 1;
    CASToken *token = [self lookaheadByCount:i];
    CASToken *previousNonWhitespaceToken;
    while (token && token.isPossiblySelector) {
        CASToken *nextToken = [self lookaheadByCount:++i];
        //newline must be preceded by comma unless next token is selector delimiter
        if (token.type == CASTokenTypeNewline && previousNonWhitespaceToken && ![previousNonWhitespaceToken valueIsEqualTo:@","] && !nextToken.isPossiblySelectorDelimiter) {
            return nil;
        }
        if (![token isWhitespace]) {
            previousNonWhitespaceToken = token;
        }
        token = nextToken;
    }

    if ([self isNestedPropertyAtIndex:i]) {
        return nil;
    }

    if (!token.isPossiblySelectorDelimiter) {
        return nil;
    }

    NSMutableArray *styleNodes = NSMutableArray.new;
    NSMutableDictionary *arguments;
    CASStyleSelector *styleSelector;
    CASToken *previousToken, *argNameToken, *argValueToken;
    token = nil;
    BOOL shouldSelectSubclasses = NO;
    BOOL shouldSelectIndirectSuperview = YES;
    BOOL argumentListMode = NO;
    BOOL shouldConcatToParent = NO;

    while (--i > 0) {
        previousToken = token;
        token = [self nextToken];

        if (argumentListMode) {
            if (token.type == CASTokenTypeRightSquareBrace) {
                styleSelector.arguments = [arguments copy];
                argumentListMode = NO;
                arguments = nil;
            } else if (token.type == CASTokenTypeSelector || token.type == CASTokenTypeRef) {
                if (!argNameToken) {
                    argNameToken = token;
                } else if (!argValueToken) {
                    argValueToken = token;
                }

                if (argNameToken && argValueToken) {
                    if (!arguments) {
                        arguments = NSMutableDictionary.new;
                    }
                    NSString *argValue = [argValueToken.value cas_stringByTrimmingWhitespace];
                    NSString *argName = [argNameToken.value cas_stringByTrimmingWhitespace];
                    [arguments setObject:argValue forKey:argName];

                    argValueToken = nil;
                    argNameToken = nil;
                }
            }
            continue;
        }

        if (token.type == CASTokenTypeCarat) {
            shouldSelectSubclasses = YES;
        } else if (token.type == CASTokenTypeRef || token.type == CASTokenTypeSelector) {
            NSString *tokenValue = [token.value cas_stringByTrimmingWhitespace];

            BOOL shouldSpawn = ![tokenValue hasPrefix:@"."]
                                || styleSelector == nil
                                || previousToken.isWhitespace
                                || [previousToken valueIsEqualTo:@">"];

            if (shouldSpawn) {
                if (styleSelector) {
                    CASStyleSelector *childSelector = CASStyleSelector.new;
                    childSelector.shouldSelectIndirectSuperview = shouldSelectIndirectSuperview;
                    childSelector.shouldConcatToParent = shouldConcatToParent;
                    childSelector.parentSelector = styleSelector;
                    styleSelector.parent = YES;

                    styleSelector = childSelector;
                } else {
                    styleSelector = CASStyleSelector.new;
                    styleSelector.shouldSelectIndirectSuperview = shouldSelectIndirectSuperview;
                    styleSelector.shouldConcatToParent = shouldConcatToParent;
                }
                styleSelector.shouldSelectSubclasses = shouldSelectSubclasses;
            }


            if ([tokenValue hasPrefix:@"."]) {
                styleSelector.styleClass = [tokenValue substringFromIndex:1];
            } else {
                styleSelector.objectClass = NSClassFromString(tokenValue);
                if (!styleSelector.objectClass) {
                     // Maybe it's a custom Swift class
                    styleSelector.objectClass = [self swiftClassFromString:tokenValue];
                }
            }

            if (!styleSelector.objectClass && !shouldConcatToParent) {
                self.error = [self.lexer errorWithDescription:[NSString stringWithFormat:@"Invalid class name `%@`", tokenValue]
                                                       reason:@"Every selector must have a objectClass"
                                                         code:CASParseErrorFileContents];
                return nil;
            }

            // reset state
            shouldSelectSubclasses = NO;
            shouldSelectIndirectSuperview = YES;
            shouldConcatToParent = NO;
        } else if (token.type == CASTokenTypeLeftSquareBrace) {
            argumentListMode = YES;
        } else if ([token valueIsEqualTo:@"&"]) {
            shouldConcatToParent = YES;
        } else if([token valueIsEqualTo:@">"]) {
            shouldSelectIndirectSuperview = NO;
        } else if ([token valueIsEqualTo:@","]) {
            if (styleSelector) {
                CASStyleNode *node = CASStyleNode.new;
                node.styleSelector = styleSelector;
                [styleNodes addObject:node];
            }
            styleSelector = nil;
        }
    }
    if (styleSelector) {
        CASStyleNode *node = CASStyleNode.new;
        node.styleSelector = styleSelector;
        [styleNodes addObject:node];
    }

    return styleNodes;
}

- (CASStyleProperty *)nextStylePropertyIsParent:(BOOL *)isParent {
    NSInteger i = 1;

    BOOL hasName = NO, hasValues = NO;
    CASToken *token = [self lookaheadByCount:i];
    while (token && token.type != CASTokenTypeNewline
           && token.type != CASTokenTypeRightCurlyBrace
           && token.type != CASTokenTypeIndent
           && token.type != CASTokenTypeOutdent
           && token.type != CASTokenTypeSemiColon
           && token.type != CASTokenTypeEOS) {

        if (token.type == CASTokenTypeSpace
            || token.type == CASTokenTypeIndent
            || [token valueIsEqualTo:@":"]) {
            token = [self lookaheadByCount:++i];
            continue;
        }

        if (!hasName && token.value) {
            hasName = YES;
        } else {
            hasValues = YES;
        }
        token = [self lookaheadByCount:++i];
    }

    if (hasName && hasValues) {
        CASToken *nameToken;
        NSMutableArray *valueTokens = NSMutableArray.new;
        NSMutableDictionary *arguments;
        CASToken *argNameToken, *argValueToken;
        BOOL argumentListMode = NO;
        BOOL hasChildren = [self isNestedPropertyAtIndex:i];

        // consume tokens
        while (--i > 0) {
            token = [self nextToken];

            if (!nameToken && token.value) {
                nameToken = token;
                continue;
            }

            if (argumentListMode) {
                if (token.type == CASTokenTypeRightSquareBrace) {
                    argumentListMode = NO;
                } else if (token.type == CASTokenTypeSelector || token.type == CASTokenTypeRef) {
                    if (!argNameToken) {
                        argNameToken = token;
                    } else if (!argValueToken) {
                        argValueToken = token;
                    }

                    if (argNameToken && argValueToken) {
                        if (!arguments) {
                            arguments = NSMutableDictionary.new;
                        }
                        NSString *argValue = [argValueToken.value cas_stringByTrimmingWhitespace];
                        NSString *argName = [argNameToken.value cas_stringByTrimmingWhitespace];
                        [arguments setObject:argValue forKey:argName];

                        argValueToken = nil;
                        argNameToken = nil;
                    }
                }
                continue;
            }

            if (token.isWhitespace || [token valueIsEqualTo:@":"]) {
                continue;
            }
            if (token.type == CASTokenTypeLeftSquareBrace) {
                argumentListMode = YES;
            } else if (token.type == CASTokenTypeRef) {
                CASStyleProperty *styleVar = self.styleVars[token.value];
                if (styleVar) {
                    [valueTokens addObjectsFromArray:styleVar.valueTokens];
                } else {
                    [valueTokens addObject:token];
                }
            } else {
                [valueTokens addObject:token];
            }
        }
        CASStyleProperty *styleProperty = [[CASStyleProperty alloc] initWithNameToken:nameToken valueTokens:valueTokens];
        styleProperty.arguments = [arguments copy];
        *isParent = hasChildren;
        return styleProperty;
    }

    return nil;
}

- (Class)swiftClassFromString:(NSString *)className {
    NSString *appName = [[NSBundle mainBundle] objectForInfoDictionaryKey:@"CFBundleName"];
    NSString *sanitizedAppName = [appName stringByReplacingOccurrencesOfString:@" " withString:@"_"];
    NSString *classStringName = [NSString stringWithFormat:@"_TtC%lu%@%lu%@", (unsigned long)sanitizedAppName.length, sanitizedAppName, (unsigned long)className.length, className];
    return NSClassFromString(classStringName);
}


@end


================================================
FILE: Classy/Parser/CASStyleNode.h
================================================
//
//  CASStyleGroup.h
//  Classy
//
//  Created by Jonas Budelmann on 25/09/13.
//  Copyright (c) 2013 cloudling. All rights reserved.
//

#import <Foundation/Foundation.h>
#import "CASStyleProperty.h"
#import "CASStyleSelector.h"
#import "CASDeviceSelector.h"

@interface CASStyleNode : NSObject <NSCoding>

/**
 *  NSInvocations to apply to appropriate view
 */
@property (nonatomic, strong) NSArray *invocations;

/**
 *  returns all style properties for the receiver
 */
@property (nonatomic, strong, readonly) NSArray *styleProperties;

/**
 *  style selector related to this node
 */
@property (nonatomic, strong) CASStyleSelector *styleSelector;

/**
 *  device selector related to this node
 */
@property (nonatomic, strong) CASDeviceSelector *deviceSelector;

/**
 *  Add a style property to the receiver
 */
- (void)addStyleProperty:(CASStyleProperty *)styleProperty;

@end


================================================
FILE: Classy/Parser/CASStyleNode.m
================================================
//
//  CASStyleGroup.m
//  Classy
//
//  Created by Jonas Budelmann on 25/09/13.
//  Copyright (c) 2013 cloudling. All rights reserved.
//

#import "CASStyleNode.h"

@implementation CASStyleNode {
    NSMutableArray *_styleProperties;
}

@synthesize styleProperties = _styleProperties;

- (id)init {
    self = [super init];
    if (!self) return nil;

    _styleProperties = NSMutableArray.new;

    return self;
}

- (void)addStyleProperty:(CASStyleProperty *)styleProperty {
    [_styleProperties addObject:styleProperty];
}

#pragma mark - NSCoding

- (id)initWithCoder:(NSCoder *)aDecoder {
    self = [self init];
    if (nil != self) {
        self.invocations = nil;
        _styleProperties = [aDecoder decodeObjectForKey:NSStringFromSelector(@selector(styleProperties))];
        self.styleSelector = [aDecoder decodeObjectForKey:NSStringFromSelector(@selector(styleSelector))];
        self.deviceSelector = [aDecoder decodeObjectForKey:NSStringFromSelector(@selector(deviceSelector))];
    }
    return self;
}

- (void)encodeWithCoder:(NSCoder *)aCoder {
    [aCoder encodeObject:self.styleProperties forKey:NSStringFromSelector(@selector(styleProperties))];
    [aCoder encodeObject:self.styleSelector forKey:NSStringFromSelector(@selector(styleSelector))];
    [aCoder encodeObject:self.deviceSelector forKey:NSStringFromSelector(@selector(deviceSelector))];
}

@end


================================================
FILE: Classy/Parser/CASStyleProperty.h
================================================
//
//  MODStyleProperty.h
//  Mod
//
//  Created by Jonas Budelmann on 25/09/13.
//  Copyright (c) 2013 cloudling. All rights reserved.
//

#import <Foundation/Foundation.h>
#import <UIKit/UIKit.h>
#import "CASToken.h"

@interface CASStyleProperty : NSObject <NSCoding>

/**
 *  Name of the receiver
 */
@property (nonatomic, strong, readonly) NSString *name;

/**
 *  Raw values of the receiver
 */
@property (nonatomic, strong, readonly) NSArray *values;

/**
 *  Name token of the receiver
 */
@property (nonatomic, strong, readonly) CASToken *nameToken;

/**
 *  value tokens of the receiver
 */
@property (nonatomic, strong, readonly) NSArray *valueTokens;

/**
 *  child style properties
 */
@property (nonatomic, strong, readonly) NSArray *childStyleProperties;

/**
 *  Provides support for properties that have extra arguments such as
 *  - setTitle:forState:
 */
@property (nonatomic, strong) NSDictionary *arguments;

/**
 *  Creates property with raw data in the form of CASTokens
 */
- (id)initWithNameToken:(CASToken *)nameToken valueTokens:(NSArray *)valueTokens;

/**
 *  Returns first valueToken of a specific token type
 *
 *  @param tokenType `CASTokenType` sought
 *
 *  @return a `CASToken`
 */
- (id)valueOfTokenType:(CASTokenType)tokenType;

/**
 *  Returns all consecutive valueTokens of a specific token type
 *  Will ignore whitespace and commas
 *
 *  @param tokenType `CASTokenType` sought
 *
 *  @return a `CASToken`
 */
- (NSArray *)consecutiveValuesOfTokenType:(CASTokenType)tokenType;

/**
 *  Attempts to extract a CGSize from the valueTokens
 *
 *  @param size CGSize pointer
 *
 *  @return whether the extraction succeeded
 */
- (BOOL)transformValuesToCGSize:(CGSize *)size;

/**
 *  Attempts to extract a CGPoint from the valueTokens
 *
 *  @param point CGPoint pointer
 *
 *  @return whether the extraction succeeded
 */
- (BOOL)transformValuesToCGPoint:(CGPoint *)point;

/**
 *  Attempts to extract a CGRect from the valueTokens
 *
 *  @param rect CGRect pointer
 *
 *  @return whether the extraction succeeded
 */
- (BOOL)transformValuesToCGRect:(CGRect *)rect;

/**
 *  Attempts to extract a UIOffset from the valueTokens
 *
 *  @param offset UIOffset pointer
 *
 *  @return whether the extraction succeeded
 */
- (BOOL)transformValuesToUIOffset:(UIOffset *)offset;

/**
 *  Attempts to extract a UIEdgeInsets from the valueTokens
 *
 *  @param insets UIEdgeInsets pointer
 *
 *  @return whether the extraction succeeded
 */
- (BOOL)transformValuesToUIEdgeInsets:(UIEdgeInsets *)insets;

/**
 *  Attempts to extract a UIColor from the valueTokens
 *
 *  @param color UIColor pointer
 *
 *  @return whether the extraction succeeded
 */
- (BOOL)transformValuesToUIColor:(UIColor **)color;

/**
 *  Attempts to extract a UIImage from the valueTokens
 *
 *  @param image UIImage pointer
 *
 *  @return whether the extraction succeeded
 */
- (BOOL)transformValuesToUIImage:(UIImage **)image;

/**
 *  Attempts to extract a NSString from the valueTokens
 *
 *  @param string NSString pointer
 *
 *  @return whether the extraction succeeded
 */
- (BOOL)transformValuesToNSString:(NSString **)string;

/**
 *  Attempts to extract a UIFont from the valueTokens
 *
 *  @param font UIFont pointer
 *
 *  @return whether the extraction succeeded
 */
- (BOOL)transformValuesToUIFont:(UIFont **)font;

/**
 *  Replace any detected expressions/equations with a numerical value
 */
- (void)resolveExpressions;

/**
 *  adds nested CASStyleProperty to the receiver
 */
- (void)addChildStyleProperty:(CASStyleProperty *)styleProperty;

@end


================================================
FILE: Classy/Parser/CASStyleProperty.m
================================================
//
//  CASStyleProperty.m
//  Classy
//
//  Created by Jonas Budelmann on 25/09/13.
//  Copyright (c) 2013 cloudling. All rights reserved.
//

#import "CASStyleProperty.h"
#import "NSString+CASAdditions.h"
#import "CASExpressionSolver.h"

@interface CASStyleProperty ()

@property (nonatomic, strong, readwrite) NSString *name;
@property (nonatomic, strong, readwrite) NSArray *values;

@property (nonatomic, strong, readwrite) CASToken *nameToken;
@property (nonatomic, strong, readwrite) NSArray *valueTokens;

@end

@implementation CASStyleProperty {
    NSMutableArray *_childStyleProperties;
}

@synthesize childStyleProperties = _childStyleProperties;

- (id)initWithNameToken:(CASToken *)nameToken valueTokens:(NSArray *)valueTokens {
    self = [super init];
    if (!self) return nil;

    self.nameToken = nameToken;
    self.valueTokens = valueTokens;

    return self;
}

#pragma mark - properties

- (NSString *)name {
    if (!_name) {
        _name = [self.nameToken.value cas_stringByCamelCasing];
    }
    return _name;
}

- (NSArray *)values {
    if (!_values) {
        NSMutableArray *values = NSMutableArray.new;
        for (CASToken *valueToken in self.valueTokens) {
            if (valueToken.value) {
                [values addObject:valueToken.value];
            }
        }
        _values = values;
    }
    return _values;
}

#pragma mark - helpers

- (id)valueOfTokenType:(CASTokenType)tokenType {
    for (CASToken *token in self.valueTokens) {
        if (token.type == tokenType) return token.value;
    }
    return nil;
}

- (NSArray *)consecutiveValuesOfTokenType:(CASTokenType)tokenType {
    NSMutableArray *tokens = NSMutableArray.new;
    for (CASToken *token in self.valueTokens) {
        if (token.type == tokenType) {
            [tokens addObject:token.value];
        } else if (tokens.count && !token.isWhitespace && ![token valueIsEqualTo:@","]) {
            return tokens;
        }
    }
    return tokens;
}

#pragma - value transformation

- (BOOL)transformValuesToCGSize:(CGSize *)size {
    NSArray *unitTokens = [self consecutiveValuesOfTokenType:CASTokenTypeUnit];
    if (unitTokens.count == 1) {
        CGFloat value = [unitTokens[0] doubleValue];
        *size = CGSizeMake(value, value);
        return YES;
    }
    if (unitTokens.count == 2) {
        CGFloat value1 = [unitTokens[0] doubleValue];
        CGFloat value2 = [unitTokens[1] doubleValue];
        *size = CGSizeMake(value1, value2);
        return YES;
    }
    return NO;
}

- (BOOL)transformValuesToCGPoint:(CGPoint *)point {
    NSArray *unitTokens = [self consecutiveValuesOfTokenType:CASTokenTypeUnit];
    if (unitTokens.count == 1) {
        CGFloat value = [unitTokens[0] doubleValue];
        *point = CGPointMake(value, value);
        return YES;
    }
    if (unitTokens.count == 2) {
        CGFloat value1 = [unitTokens[0] doubleValue];
        CGFloat value2 = [unitTokens[1] doubleValue];
        *point = CGPointMake(value1, value2);
        return YES;
    }
    return NO;
}

- (BOOL)transformValuesToCGRect:(CGRect *)rect {
    NSArray *unitTokens = [self consecutiveValuesOfTokenType:CASTokenTypeUnit];
    if (unitTokens.count == 4) {
        *rect = CGRectMake([unitTokens[0] doubleValue], [unitTokens[1] doubleValue], [unitTokens[2] doubleValue], [unitTokens[3] doubleValue]);
        return YES;
    }
    return NO;
}

- (BOOL)transformValuesToUIEdgeInsets:(UIEdgeInsets *)insets {
    NSArray *unitTokens = [self consecutiveValuesOfTokenType:CASTokenTypeUnit];
    if (unitTokens.count == 1) {
        CGFloat value = [unitTokens[0] doubleValue];
        *insets = UIEdgeInsetsMake(value, value, value, value);
        return YES;
    }
    if (unitTokens.count == 2) {
        CGFloat value1 = [unitTokens[0] doubleValue];
        CGFloat value2 = [unitTokens[1] doubleValue];
        *insets = UIEdgeInsetsMake(value1, value2, value1, value2);
        return YES;
    }
    if (unitTokens.count == 4) {
        *insets = UIEdgeInsetsMake([unitTokens[0] doubleValue], [unitTokens[1] doubleValue], [unitTokens[2] doubleValue], [unitTokens[3] doubleValue]);
        return YES;
    }
    return NO;
}

- (BOOL)transformValuesToUIOffset:(UIOffset *)offset {
    NSArray *unitTokens = [self consecutiveValuesOfTokenType:CASTokenTypeUnit];
    if (unitTokens.count == 1) {
        CGFloat value = [unitTokens[0] doubleValue];
        *offset = UIOffsetMake(value, value);
        return YES;
    }
    if (unitTokens.count == 2) {
        CGFloat value1 = [unitTokens[0] doubleValue];
        CGFloat value2 = [unitTokens[1] doubleValue];
        *offset = UIOffsetMake(value1, value2);
        return YES;
    }
    return NO;
}

- (BOOL)transformValuesToUIColor:(UIColor **)color {
    UIColor *colorValue = [self valueOfTokenType:CASTokenTypeColor];
    if (colorValue) {
        *color = colorValue;
        return YES;
    }

    NSString *value = [self valueOfTokenType:CASTokenTypeRef]
        ?: [self valueOfTokenType:CASTokenTypeSelector]
        ?: [self valueOfTokenType:CASTokenTypeString];

    if ([value isEqualToString:@"rgb"] || [value isEqualToString:@"rgba"] || [value isEqualToString:@"hsl"] || [value isEqualToString:@"hsla"]) {
        NSArray *unitTokens = [self consecutiveValuesOfTokenType:CASTokenTypeUnit];
        CGFloat alpha = 1.0;

        // invalid if you don't have 3 colors
        if(unitTokens.count < 3) {
            return NO;
        } else if(unitTokens.count == 4){
            alpha = [unitTokens[3] doubleValue];
        }
        if( [value isEqualToString:@"rgb"] || [value isEqualToString:@"rgba"] ) {
            *color = [UIColor colorWithRed:[unitTokens[0] doubleValue]/255.0 green:[unitTokens[1] doubleValue]/255.0 blue:[unitTokens[2] doubleValue]/255.0 alpha:alpha];
        } else if ( [value isEqualToString:@"hsl"] || [value isEqualToString:@"hsla"] ) {
            *color = [UIColor colorWithHue:[unitTokens[0] doubleValue]/360.0 saturation:[unitTokens[1] doubleValue]/100.0 brightness:[unitTokens[2] doubleValue]/100.0 alpha:alpha];
        }
        return YES;
    }

    value = [value cas_stringByCamelCasing];
    SEL selector = NSSelectorFromString([NSString stringWithFormat:@"%@Color", value]);
    if (selector && [UIColor.class respondsToSelector:selector]) {
#pragma clang diagnostic push
#pragma clang diagnostic ignored "-Warc-performSelector-leaks"
        *color = [UIColor.class performSelector:selector];
#pragma clang diagnostic pop
        return YES;
    }

    return NO;
}

- (BOOL)transformValuesToNSString:(NSString **)string {
    NSString *value = [self valueOfTokenType:CASTokenTypeString]
        ?: [self valueOfTokenType:CASTokenTypeRef]
        ?: [self valueOfTokenType:CASTokenTypeSelector];
    if (value) {
        *string = value;
        return YES;
    }

    return NO;
}

- (BOOL)transformValuesToUIImage:(UIImage **)image {
    UIEdgeInsets insets;
    BOOL hasInsets = [self transformValuesToUIEdgeInsets:&insets];

    NSString *imageName = [self valueOfTokenType:CASTokenTypeString] ?: [self valueOfTokenType:CASTokenTypeRef];
    
    UIImage *imageValue = nil;
    NSRange schemeRange = [imageName rangeOfString:@"://"];
    if(schemeRange.location != NSNotFound) {
        
        // We are a file path instead
        NSString *scheme = [imageName substringToIndex:schemeRange.location];
        NSString *path = [imageName substringFromIndex:NSMaxRange(schemeRange)];
        
        // Checking if we're fetching from one of our built in
        // document uris
        NSSearchPathDirectory searchMask = 0;
        if([scheme isEqualToString:@"caches"]) {
            searchMask = NSCachesDirectory;
        } else if([scheme isEqualToString:@"documents"]) {
            searchMask = NSDocumentDirectory;
        } else if([scheme isEqualToString:@"appsupport"]) {
            searchMask = NSApplicationSupportDirectory;
        }
        
        if(searchMask != 0) {
            // If we found a search mask, then use that
            NSArray *paths = NSSearchPathForDirectoriesInDomains(searchMask, NSUserDomainMask, YES);
            NSString *imagePath = [paths firstObject];
            imageValue = [UIImage imageWithContentsOfFile:[imagePath stringByAppendingPathComponent:path]];
        } else {
            // Otherwise load from imageNamed as per norm
            imageValue = [UIImage imageNamed:path];
        }
        
    } else {
        // We're just an old boring image name
        imageValue = [UIImage imageNamed:imageName];
    }
    
    
    if (hasInsets) {
        imageValue = [imageValue resizableImageWithCapInsets:insets];
    }
    if (imageValue) {
        *image = imageValue;
        return YES;
    }
    return NO;
}

- (BOOL)transformValuesToUIFont:(UIFont **)font {
    NSNumber *fontSize = [self valueOfTokenType:CASTokenTypeUnit];
    NSString *fontName = [self valueOfTokenType:CASTokenTypeString]
        ?: [self valueOfTokenType:CASTokenTypeRef]
        ?: [self valueOfTokenType:CASTokenTypeSelector];

    if (!fontSize && !fontName.length) {
        return NO;
    }

    static NSDictionary *textStyleLookupMap = nil;
    if (!textStyleLookupMap) {
        // Classy is available also on iOS6, so instead of using UIKit consts for text styles that are available
        // only on iOS7+ let the strings be hardcoded. This avoids the need for weak-linking UIKit.
        textStyleLookupMap = @{
                @"body" : @"UICTFontTextStyleBody",
                @"caption1" : @"UICTFontTextStyleCaption1",
                @"caption2" : @"UICTFontTextStyleCaption2",
                @"footnote" : @"UICTFontTextStyleFootnote",
                @"headline" : @"UICTFontTextStyleHeadline",
                @"subheadline" : @"UICTFontTextStyleSubhead",
        };
    }

    NSString *textStyle = textStyleLookupMap[fontName];
    if (textStyle && !fontSize) {
#pragma clang diagnostic push
#pragma ide diagnostic ignored "UnavailableInDeploymentTarget"
        if ([UIFont respondsToSelector:@selector(preferredFontForTextStyle:)]) {
            *font = [UIFont preferredFontForTextStyle:textStyle];
        } else {
            return NO;
        }
#pragma clang diagnostic pop
    } else {
        CGFloat fontSizeValue = [fontSize floatValue] ?: [UIFont systemFontSize];
        if (fontName) {
            if ([fontName hasPrefix:@"System"]) {
                
                NSString *weightString = @"Regular";
                NSArray *nameComponents = [fontName componentsSeparatedByString:@"-"];
                if (nameComponents.count == 2) {
                    weightString = nameComponents[1];
                }
                
                if ([weightString isEqualToString:@"Regular"]) {
                    *font = [UIFont systemFontOfSize:fontSizeValue];
                }
                else {
                    static NSDictionary *weightNameToFloatMapping = nil;
                    static dispatch_once_t onceToken;
                    dispatch_once(&onceToken, ^{
                        weightNameToFloatMapping = @{@"black":       @(UIFontWeightBlack),
                                                     @"heavy":       @(UIFontWeightHeavy),
                                                     @"bold":        @(UIFontWeightBold),
                                                     @"semibold":    @(UIFontWeightSemibold),
                                                     @"medium":      @(UIFontWeightMedium),
                                                     @"regular":     @(UIFontWeightRegular),
                                                     @"thin":        @(UIFontWeightThin),
                                                     @"light":       @(UIFontWeightLight),
                                                     @"ultralight":  @(UIFontWeightUltraLight)};
                    });
                    
                    
                    UIFont *systemFont = nil;
                    UIFontDescriptor *descriptor = nil;
                    
                    if ([[UIFont class] respondsToSelector:@selector(systemFontOfSize:weight:)]) {
                        CGFloat weight = UIFontWeightRegular;
                        
                        NSNumber *weightNumber = weightNameToFloatMapping[[weightString lowercaseString]];
                        if (weightNumber != nil) {
                            weight = [weightNumber floatValue];
                        }
                        
                        systemFont = [UIFont systemFontOfSize:fontSizeValue weight:weight];
                        descriptor = [UIFontDescriptor fontDescriptorWithFontAttributes:@{UIFontDescriptorNameAttribute: systemFont.fontName}];
                    }
                    else {
                        systemFont = [UIFont systemFontOfSize:fontSizeValue];
                        descriptor = [UIFontDescriptor fontDescriptorWithFontAttributes:@{UIFontDescriptorFaceAttribute: weightString,
                                                                                          UIFontDescriptorFamilyAttribute: systemFont.familyName}];
                    }
                    
                    *font = [UIFont fontWithDescriptor:descriptor size:fontSizeValue];
                }
            }
            else {
                *font = [UIFont fontWithName:fontName size:fontSizeValue];
            }
        } else {
            *font = [UIFont systemFontOfSize:fontSizeValue];
        }
    }
    return YES;
}

- (void)resolveExpressions {
    BOOL hasOperator = NO;
    for (CASToken *token in self.valueTokens) {
        if (token.type == CASTokenTypeOperator && ![token valueIsEqualTo:@","]) {
            hasOperator = YES;
            break;
        }
    }

    if (!hasOperator) return;

    CASExpressionSolver *solver = CASExpressionSolver.new;
    self.valueTokens = [solver tokensByReducingTokens:self.valueTokens];
    self.values = nil;
}

- (void)addChildStyleProperty:(CASStyleProperty *)styleProperty {
    if (!_childStyleProperties) {
        _childStyleProperties = NSMutableArray.new;
    }
    [_childStyleProperties addObject:styleProperty];
}

#pragma mark - NSCoding

- (id)initWithCoder:(NSCoder *)aDecoder {
    self = [self init];
    if (nil != self) {
        self.name = [aDecoder decodeObjectForKey:NSStringFromSelector(@selector(name))];
        self.values = [aDecoder decodeObjectForKey:NSStringFromSelector(@selector(values))];
        self.nameToken = [aDecoder decodeObjectForKey:NSStringFromSelector(@selector(nameToken))];
        self.valueTokens = [aDecoder decodeObjectForKey:NSStringFromSelector(@selector(valueTokens))];
        _childStyleProperties = [aDecoder decodeObjectForKey:NSStringFromSelector(@selector(childStyleProperties))];
        self.arguments = [aDecoder decodeObjectForKey:NSStringFromSelector(@selector(arguments))];
    }
    return self;
}

- (void)encodeWithCoder:(NSCoder *)aCoder {
    [aCoder encodeObject:self.name forKey:NSStringFromSelector(@selector(name))];
    [aCoder encodeObject:self.values forKey:NSStringFromSelector(@selector(values))];
    [aCoder encodeObject:self.nameToken forKey:NSStringFromSelector(@selector(nameToken))];
    [aCoder encodeObject:self.valueTokens forKey:NSStringFromSelector(@selector(valueTokens))];
    [aCoder encodeObject:_childStyleProperties forKey:NSStringFromSelector(@selector(childStyleProperties))];
    [aCoder encodeObject:self.arguments forKey:NSStringFromSelector(@selector(arguments))];
}

@end


================================================
FILE: Classy/Parser/CASStyleSelector.h
================================================
//
//  CASStyleSelector.h
//  Classy
//
//  Created by Jonas Budelmann on 29/09/13.
//  Copyright (c) 2013 cloudling. All rights reserved.
//

#import <UIKit/UIKit.h>
#import "CASStyleableItem.h"

@interface CASStyleSelector : NSObject <NSCopying, NSCoding>

/**
 *  Class of View to match
 */
@property (nonatomic, strong) Class objectClass;

/**
 *  If not nil checks the view's cas_styleClass property
 */
@property (nonatomic, copy) NSString *styleClass;

/**
 *  Whether or not to do strict matching against objectClass
 */
@property (nonatomic, assign) BOOL shouldSelectSubclasses;

/**
 *  Whether or not parent selector can be an indirect superview
 */
@property (nonatomic, assign) BOOL shouldSelectIndirectSuperview;

/**
 *  Whether or not this selector is a parent
 */
@property (nonatomic, assign, getter = isParent) BOOL parent;


/**
 *  Whether or not this selector should be concatenated
 */
@property (nonatomic, assign) BOOL shouldConcatToParent;

/**
 *  Provides support for properties that have extra arguments such as
 *  - setTitle:forState:
 */
@property (nonatomic, strong) NSDictionary *arguments;

/**
 *  Parent selector
 */
@property (nonatomic, strong) CASStyleSelector *parentSelector;

/**
 *  Last selector in heirachy
 */
@property (nonatomic, readonly) CASStyleSelector *lastSelector;

/**
 *  Returns a integer representation of how specific this selector is.
 *  Provides a way to order selectors.
 *
 *  The Rules
 *
 *  ObjectClass matches
 *   +2 ancestor
 *   +3 superview
 *   +4 view
 *
 *   if loose match (shouldSelectSubclasses)
 *    -1
 *
 *  StyleClass matches
 *   +1000 ancestor
 *   +2000 superview
 *   +3000 view
 *
 *  @return Precendence score
 */
- (NSInteger)precedence;

/**
 *  Whether is selector matches the given item
 *
 *  @param item `CASStyleableItem` or a subclass
 *
 *  @return `YES` if all selectors including parent selectors match the item
 */
- (BOOL)shouldSelectItem:(id<CASStyleableItem>)item;

/**
 *  String representation of receiver
 *
 *  @return a `NSString` value
 */
- (NSString *)stringValue;

@end


================================================
FILE: Classy/Parser/CASStyleSelector.m
================================================
//
//  CASStyleSelector.m
//  Classy
//
//  Created by Jonas Budelmann on 29/09/13.
//  Copyright (c) 2013 cloudling. All rights reserved.
//

#import "CASStyleSelector.h"
#import "UIView+CASAdditions.h"
#import "NSString+CASAdditions.h"

@implementation CASStyleSelector

- (id)init {
    self = [super init];
    if (!self) return nil;

    self.shouldSelectSubclasses = NO;
    self.shouldSelectIndirectSuperview = YES;

    return self;
}

- (id)copyWithZone:(NSZone *)zone {
    CASStyleSelector *newSelector = [[self.class allocWithZone:zone] init];
    if (!newSelector) return nil;

    newSelector.objectClass = self.objectClass;
    newSelector.styleClass = self.styleClass;
    newSelector.shouldSelectSubclasses = self.shouldSelectSubclasses;
    newSelector.shouldSelectIndirectSuperview = self.shouldSelectIndirectSuperview;
    newSelector.shouldConcatToParent = self.shouldConcatToParent;
    newSelector.arguments = [self.arguments copy];
    newSelector.parentSelector = [self.parentSelector copy];

    return newSelector;
}

#pragma mark - properties

- (CASStyleSelector *)lastSelector {
    return self.parentSelector.lastSelector ?: self;
}

#pragma mark - public

- (NSInteger)precedence {
    NSInteger precedence = 0;
    if (self.objectClass) {
        if (self.isParent) {
            precedence += self.shouldSelectIndirectSuperview ? 2 : 3;
        } else {
            precedence += 4;
        }
        if (self.shouldSelectSubclasses) {
            precedence -= 1;
        }
    }

    if (self.styleClass) {
        if (self.isParent) {
            precedence += self.shouldSelectIndirectSuperview ? 1000 : 2000;
        } else {
            precedence += 3000;
        }
    }

    precedence += self.parentSelector.precedence;
    return precedence;
}

- (BOOL)shouldSelectItem:(id<CASStyleableItem>)item {
    if (![self matchesItem:item]) {
        return NO;
    }

    if (self.parentSelector) {
        return [self.parentSelector matchesAncestorsOfItem:item traverse:self.shouldSelectIndirectSuperview];
    }

    return YES;
}

- (NSString *)stringValue {
    NSMutableString *stringValue = NSMutableString.new;
    if (self.parentSelector) {
        [stringValue appendFormat:self.shouldSelectIndirectSuperview ? @"%@ " : @"%@ > ", [self.parentSelector stringValue]];
    }
    if (self.shouldSelectSubclasses) {
        [stringValue appendString:@"^"];
    }
    if (self.objectClass) {
        [stringValue appendString:NSStringFromClass(self.objectClass)];
    }
    if (self.arguments) {
        [stringValue appendString:@"["];
        NSArray *keys = [self.arguments.allKeys sortedArrayUsingSelector:@selector(caseInsensitiveCompare:)];
        [keys enumerateObjectsUsingBlock:^(NSString *key, NSUInteger idx, BOOL *stop) {
            [stringValue appendFormat:@"%@:%@", key, self.arguments[key]];
            if (idx != keys.count - 1) {
                [stringValue appendString:@", "];
            }
        }];
        [stringValue appendString:@"]"];
    }
    if (self.styleClass) {
        [stringValue appendFormat:@".%@", self.styleClass];
    }
    return stringValue;
}

#pragma mark - private

- (BOOL)matchesAncestorsOfItem:(id<CASStyleableItem>)item traverse:(BOOL)traverse {
    id<CASStyleableItem> currentItem = item;

    while (currentItem.cas_parent != nil || currentItem.cas_alternativeParent != nil) {
        id<CASStyleableItem> ancestor;
        if ([self matchesItem:currentItem.cas_parent]) {
            ancestor = currentItem.cas_parent;
        } else if ([self matchesItem:currentItem.cas_alternativeParent]) {
            ancestor = currentItem.cas_alternativeParent;
        }

        if (ancestor) {
            if (!self.parentSelector) return YES;
            BOOL traverse = self.shouldSelectIndirectSuperview;
            if ([self.parentSelector matchesAncestorsOfItem:ancestor traverse:traverse]) return YES;
        }
        if (!traverse) return NO;
        currentItem = currentItem.cas_parent;
    }

    return NO;
}

- (BOOL)matchesItem:(id<CASStyleableItem>)item {
    if (self.objectClass) {
        if (self.shouldSelectSubclasses) {
            if (![item isKindOfClass:self.objectClass]) return NO;
        } else {
            if (![item isMemberOfClass:self.objectClass]) return NO;
        }
    }
    
    if (self.styleClass.length && ![item cas_hasStyleClass:self.styleClass]) {
        return NO;
    }
    return YES;
}

#pragma mark - NSCoding

- (id)initWithCoder:(NSCoder *)aDecoder {
    self = [self init];
    
    if (nil != self) {
        self.objectClass = NSClassFromString([aDecoder decodeObjectForKey:NSStringFromSelector(@selector(objectClass))]);
        self.styleClass = [aDecoder decodeObjectForKey:NSStringFromSelector(@selector(styleClass))];
        self.shouldSelectSubclasses = [aDecoder decodeBoolForKey:NSStringFromSelector(@selector(shouldSelectSubclasses))];
        self.shouldSelectIndirectSuperview = [aDecoder decodeBoolForKey:NSStringFromSelector(@selector(shouldSelectIndirectSuperview))];
        self.parent = [aDecoder decodeBoolForKey:NSStringFromSelector(@selector(isParent))];
        self.shouldConcatToParent = [aDecoder decodeBoolForKey:NSStringFromSelector(@selector(shouldConcatToParent))];
        self.arguments = [aDecoder decodeObjectForKey:NSStringFromSelector(@selector(arguments))];
        self.parentSelector = [aDecoder decodeObjectForKey:NSStringFromSelector(@selector(parentSelector))];
    }
    
    return self;
}

- (void)encodeWithCoder:(NSCoder *)aCoder {
    [aCoder encodeObject:NSStringFromClass(self.objectClass) forKey:NSStringFromSelector(@selector(objectClass))];
    [aCoder encodeObject:self.styleClass forKey:NSStringFromSelector(@selector(styleClass))];
    [aCoder encodeBool:self.shouldSelectSubclasses forKey:NSStringFromSelector(@selector(shouldSelectSubclasses))];
    [aCoder encodeBool:self.shouldSelectIndirectSuperview forKey:NSStringFromSelector(@selector(shouldSelectIndirectSuperview))];
    [aCoder encodeBool:self.parent forKey:NSStringFromSelector(@selector(isParent))];
    [aCoder encodeBool:self.shouldConcatToParent forKey:NSStringFromSelector(@selector(shouldConcatToParent))];
    [aCoder encodeObject:self.arguments forKey:NSStringFromSelector(@selector(arguments))];
    [aCoder encodeObject:self.parentSelector forKey:NSStringFromSelector(@selector(parentSelector))];
}

@end


================================================
FILE: Classy/Parser/CASStyler.h
================================================
//
//  CASStyler.h
//  Classy
//
//  Created by Jonas Budelmann on 16/09/13.
//  Copyright (c) 2013 cloudling. All rights reserved.
//

#import <UIKit/UIKit.h>
#import "CASObjectClassDescriptor.h"
#import "CASStyleableItem.h"

@class CASStyleNode;

/**
 * Protocol caching the @c CASStyleNode's. Adds the possibility to cache the in-memory representation of Classy's CAS 
 * file, since parsing the file is slower than loading cached version.
 */
@protocol CASCacheProtocol <NSObject>
@optional
/**
 *  Loads cached version of stylesheet located at @c path with @c variables
 */
- (NSArray <CASStyleNode *> *)cachedStyleNodesFromCASPath:(NSString *)path withVariables:(NSDictionary *)variables;

/**
 * Stores the stylesheet in-memory representation loaded from @c path with @c variables
 */
- (void)cacheStyleNodes:(NSArray <CASStyleNode *> *)styleNodes fromPath:(NSString *)path variables:(NSDictionary *)variables;
@end


@interface CASStyler : NSObject

+ (void)bootstrapClassyWithTargetWindows:(NSArray *)targetWindows;

/**
 *  Singleton instance
 */
+ (instancetype)defaultStyler;

@property (nonatomic, weak) id<CASCacheProtocol> cache;

@property (nonatomic, copy) NSDictionary *variables;

/**
 *  File path which contains style data
 */
@property (nonatomic, copy) NSString *filePath;

/**
 *  File path to watch for changes.
 *  Only use for debugging on simulator
 */
@property (nonatomic, copy) NSString *watchFilePath;

/**
 *  Windows to update views. 
 *  Needed for live updates when UIApplication is not available (e.g. in Application Extensions)
 */
@property (nonatomic, strong) NSArray *targetWindows;

/**
 *  Set file path location of styling data and report any errors
 *
 *  @param filePath The location of the style data
 *  @param error    The error that occurred while parsing the filePath
 */
- (void)setFilePath:(NSString *)filePath error:(NSError **)error;

/**
 *  Apply any applicable styles to a CASStyleableItem instance, from low to high precendence
 *
 *  @param item `CASStyleableItem` to apply styles to
 */
- (void)styleItem:(id<CASStyleableItem>)item;

/**
 *  Returns a cached CASObjectClassDescriptor if it exists or creates one
 */
- (CASObjectClassDescriptor *)objectClassDescriptorForClass:(Class)aClass;

/**
 *  Schedule update for styleable item.
 *  This ensures we only update an item once per run loop
 *
 *  @param item CASStyleableItem to coalesce update calls
 */
- (void)scheduleUpdateForItem:(id<CASStyleableItem>)item;

/**
 *  Unschedule update for styleable item
 *
 *  @param item CASStyleableItem that no longer needs updating
 */
- (void)unscheduleUpdateForItem:(id<CASStyleableItem>)item;

@end


================================================
FILE: Classy/Parser/CASStyler.m
================================================

//
//  CASStyler.m
//  Classy
//
//  Created by Jonas Budelmann on 16/09/13.
//  Copyright (c) 2013 cloudling. All rights reserved.
//

#import "CASStyler.h"
#import "CASParser.h"
#import "CASPropertyDescriptor.h"
#import "UIView+CASAdditions.h"
#import "UITextField+CASAdditions.h"
#import "CASUtilities.h"
#import "CASStyleNode.h"
#import "NSString+CASAdditions.h"
#import "CASTextAttributes.h"
#import "CASInvocation.h"

#import "UIBarItem+CASAdditions.h"
#import "UINavigationItem+CASAdditions.h"
#import "UITextField+CASAdditions.h"
#import "UIView+CASAdditions.h"
#import "UIViewController+CASAdditions.h"
#import <objc/runtime.h>

// http://www.cocoawithlove.com/2010/01/getting-subclasses-of-objective-c-class.html
NSArray *ClassGetSubclasses(Class parentClass) {
    int numClasses = objc_getClassList(NULL, 0);
    Class *classes = NULL;
    classes = (__unsafe_unretained Class *)malloc(sizeof(Class) * numClasses);
    numClasses = objc_getClassList(classes, numClasses);
    
    NSMutableArray *result = [NSMutableArray array];
    for (NSInteger i = 0; i < numClasses; i++) {
        Class superClass = classes[i];
        do {
            superClass = class_getSuperclass(superClass);
        } while(superClass && superClass != parentClass);
        
        if (superClass == nil) {
            continue;
        }
        
        [result addObject:classes[i]];
    }
    free(classes);
    return result;
}


@interface CASStyler ()

@property (nonatomic, strong) NSMutableArray *styleNodes;
@property (nonatomic, strong) NSMapTable *objectClassDescriptorCache;
@property (nonatomic, strong) NSHashTable *scheduledItems;
@property (nonatomic, strong) NSTimer *updateTimer;
@property (nonatomic, strong) NSMutableArray *fileWatchers;
@property (nonatomic, strong) NSMutableArray *invocationObjectArguments;
@property (nonatomic, strong) NSMutableDictionary *styleClassIndex;
@property (nonatomic, strong) NSMutableDictionary *objectClassIndex;

@end

@implementation CASStyler

+ (instancetype)defaultStyler {
    static CASStyler * _defaultStyler = nil;
    static dispatch_once_t onceToken;
    dispatch_once(&onceToken, ^{
        _defaultStyler = CASStyler.new;
    });
    
    return _defaultStyler;
}

+ (void)bootstrapClassyWithTargetWindows:(NSArray *)targetWindows {
    [UIBarItem bootstrapClassy];
    [UINavigationItem bootstrapClassy];
    [UITextField bootstrapClassy];
    [UIView bootstrapClassy];
    [UIViewController bootstrapClassy];
    
    [[self defaultStyler] setTargetWindows:targetWindows];
}

- (id)init {
    self = [super init];
    if (!self) return nil;

    self.objectClassDescriptorCache = NSMapTable.strongToStrongObjectsMapTable;
    self.scheduledItems = [NSHashTable hashTableWithOptions:NSHashTableWeakMemory];
    self.fileWatchers = NSMutableArray.new;
    self.styleClassIndex = [NSMutableDictionary new];
    self.objectClassIndex = [NSMutableDictionary new];
    [self setupObjectClassDescriptors];

    return self;
}

- (void)styleItem:(id<CASStyleableItem>)item {
    if (!self.filePath) {
        // load default style file
        self.filePath = [[NSBundle mainBundle] pathForResource:@"stylesheet.cas" ofType:nil];
    }
    
    NSMutableArray *possibleStyleNodes = [NSMutableArray new];
    
    Class class = [item class];
    [possibleStyleNodes addObjectsFromArray:[self.objectClassIndex valueForKey:NSStringFromClass(class)]];
    
    NSArray *styleClasses = [item.cas_styleClass componentsSeparatedByString:CASStyleClassSeparator];
    
    for (NSString *styleClass in styleClasses) {
        [possibleStyleNodes addObjectsFromArray:[self.styleClassIndex valueForKey:styleClass]];
    }
    
    for (CASStyleNode *styleNode in possibleStyleNodes) {
        if ([styleNode.styleSelector shouldSelectItem:item]) {
            // apply style nodes
            for (CASInvocation *invocation in styleNode.invocations) {
                [invocation invokeWithTarget:item];
            }
        }
    }
}

- (void)setVariables:(NSDictionary *)variables {
    _variables = variables;

    if (!self.filePath) return;

    // if stylesheet has already been loaded. reload stylesheet
    NSString *filePath = _filePath;
    _filePath = nil;
    self.filePath = filePath;

    // reapply styles
    for (UIWindow *window in self.targetWindows) {
        [self styleSubviewsOfView:window];
    }
}

- (void)setFilePath:(NSString *)filePath {
    NSError *error = nil;
    [self setFilePath:filePath error:&error];
    if (error) {
        CASLog(@"Error: %@", error);
    }
}

- (void)setFilePath:(NSString *)filePath error:(NSError **)error {
    if ([_filePath isEqualToString:filePath]) return;
    _filePath = filePath;
    
    self.styleClassIndex = [NSMutableDictionary new];
    self.objectClassIndex = [NSMutableDictionary new];
    
    NSArray *styleNodes = nil;
    NSSet *importedFileNames = nil;
    
    if ([self.cache respondsToSelector:@selector(cachedStyleNodesFromCASPath:withVariables:)]) {
        styleNodes = [self.cache cachedStyleNodesFromCASPath:filePath withVariables:self.variables];
    }
    
    if (styleNodes != nil) {
        importedFileNames = [NSSet set];
        self.styleNodes = [NSMutableArray arrayWithArray:styleNodes];
    }
    else {
        CASParser *parser = [CASParser parserFromFilePath:filePath variables:self.variables error:error];
        styleNodes = parser.styleNodes;
        importedFileNames = parser.importedFileNames;
        
        self.styleNodes = [self validNodes:styleNodes];
        
        // order ascending by precedence
        [self.styleNodes sortWithOptions:NSSortStable usingComparator:^NSComparisonResult(CASStyleNode *n1, CASStyleNode *n2) {
            NSInteger precedence1 = [n1.styleSelector precedence];
            NSInteger precedence2 = [n2.styleSelector precedence];
            if (precedence2 > precedence1) {
                return NSOrderedAscending;
            } else if (precedence2 < precedence1) {
                return NSOrderedDescending;
            }
            return NSOrderedSame;
        }];
        
        if ([self.cache respondsToSelector:@selector(cacheStyleNodes:fromPath:variables:)]) {
            [self.cache cacheStyleNodes:styleNodes fromPath:filePath variables:self.variables];
        }
    }

    if (self.watchFilePath) {
        for (dispatch_source_t source in self.fileWatchers) {
            dispatch_source_cancel(source);
        }
        [self.fileWatchers removeAllObjects];
        [self reloadOnChangesToFilePath:self.watchFilePath];
        NSString *directoryPath = [self.watchFilePath stringByDeletingLastPathComponent];
        for (NSString *fileName in importedFileNames) {
            NSString *resolvedPath = [directoryPath stringByAppendingPathComponent:fileName];
            [self reloadOnChangesToFilePath:resolvedPath];
        }
    }
    
    if (!styleNodes.count) {
        return;
    }
    
    [self populateStyleLookupTables:self.styleNodes];
    
    self.invocationObjectArguments = NSMutableArray.new;
    // precompute values
    for (CASStyleNode *styleNode in self.styleNodes) {
        NSMutableArray *invocations = NSMutableArray.new;
        for (CASStyleProperty *styleProperty in styleNode.styleProperties) {
            // TODO type checking and throw errors
            NSArray *propertyInvocations = [self invocationsForClass:styleNode.styleSelector.objectClass styleProperty:styleProperty keyPath:nil];
            [invocations addObjectsFromArray:propertyInvocations];
        }
        styleNode.invocations = invocations;
    }
}

#pragma mark - private

- (NSMutableArray *)validNodes:(NSArray *)styleNodes {
    // filter redundant nodes
    NSMutableArray *filteredNodes = NSMutableArray.new;
    for (CASStyleNode *styleNode in styleNodes) {
        // invalid if does not have any properties
        BOOL invalid = !styleNode.styleProperties.count;
        
        // invalid if has deviceSelector and deviceSelector is not valid
        invalid = invalid || (styleNode.deviceSelector && !styleNode.deviceSelector.isValid);
        if (!invalid) {
            [filteredNodes addObject:styleNode];
        }
    }
    return filteredNodes;
}

- (void)populateStyleLookupTables:(NSArray *)styleNodes {
    for (CASStyleNode *styleNode in styleNodes) {
        if (styleNode.styleSelector.styleClass) {
            NSString *styleClassKey = styleNode.styleSelector.styleClass;
            if (![self.styleClassIndex valueForKey:styleClassKey]) {
                [self.styleClassIndex setValue:[@[] mutableCopy] forKey:styleClassKey];
            }
            [[self.styleClassIndex valueForKey:styleClassKey] addObject:styleNode];
        } else {
            
            Class class = styleNode.styleSelector.objectClass;
            if (![self.objectClassIndex valueForKey:NSStringFromClass(class)]) {
                [self.objectClassIndex setValue:[@[] mutableCopy] forKey:NSStringFromClass(class)];
            }
            [[self.objectClassIndex valueForKey:NSStringFromClass(class)] addObject:styleNode];
            
            if (styleNode.styleSelector.shouldSelectSubclasses) {
                // add an entry in the lookup table for each subclass
                NSArray *subclasses = ClassGetSubclasses(class);
                for (Class class in subclasses) {
                    if (![self.objectClassIndex valueForKey:NSStringFromClass(class)]) {
                        [self.objectClassIndex setValue:[@[] mutableCopy] forKey:NSStringFromClass(class)];
                    }
                    [[self.objectClassIndex valueForKey:NSStringFromClass(class)] addObject:styleNode];
                }
            }
        }
    }
}

- (NSArray *)invocationsForClass:(Class)aClass styleProperty:(CASStyleProperty *)styleProperty keyPath:(NSString *)keypath {
    CASObjectClassDescriptor *objectClassDescriptor = [self objectClassDescriptorForClass:aClass];
    CASPropertyDescriptor *propertyDescriptor = [objectClassDescriptor propertyDescriptorForKey:styleProperty.name];

    //Special case textAttributes
    BOOL isTextAttributesProperty = styleProperty.childStyleProperties.count && [[styleProperty.name lowercaseString] hasSuffix:@"textattributes"];

    NSInvocation *invocation;
    CASInvocation *invocationWrapper;;
    NSMutableArray *invocations = NSMutableArray.new;
    if (isTextAttributesProperty || !styleProperty.childStyleProperties.count) {
        invocation = [objectClassDescriptor invocationForPropertyDescriptor:propertyDescriptor];
        invocationWrapper = [[CASInvocation alloc] initWithInvocation:invocation forKeyPath:keypath];
        [invocations addObject:invocationWrapper];
    }

    [propertyDescriptor.argumentDescriptors enumerateObjectsUsingBlock:^(CASArgumentDescriptor *argDescriptor, NSUInteger idx, BOOL *stop) {
        NSInteger argIndex = 2 + idx;

        if (idx > 0) {
            //arguments after first only supports enums at moment
            NSString *valueName = [styleProperty.arguments[argDescriptor.name] cas_stringByCamelCasing];
            if (valueName.length) {
                NSInteger value = [argDescriptor.valuesByName[valueName] integerValue];
                [invocation setArgument:&value atIndex:argIndex];
            }
            return;
        }

        switch (argDescriptor.primitiveType) {
            case CASPrimitiveTypeBOOL: {
                id value = [styleProperty valueOfTokenType:CASTokenTypeBoolean] ?: [styleProperty valueOfTokenType:CASTokenTypeUnit];
                BOOL boolValue = [value boolValue];
                [invocation setArgument:&boolValue atIndex:argIndex];
                break;
            }
            case CASPrimitiveTypeInteger: {
                NSInteger value;
                if (argDescriptor.valuesByName) {
                    NSString *valueName = [[styleProperty valueOfTokenType:CASTokenTypeRef] cas_stringByCamelCasing];
                    value = [argDescriptor.valuesByName[valueName] integerValue];
                } else {
                    value = [[styleProperty valueOfTokenType:CASTokenTypeUnit] integerValue];
                }
                [invocation setArgument:&value atIndex:argIndex];
                break;
            }
            case CASPrimitiveTypeDouble: {
                CGFloat value = [[styleProperty valueOfTokenType:CASTokenTypeUnit] doubleValue];
                [invocation setArgument:&value atIndex:argIndex];
                break;
            }
            case CASPrimitiveTypeFloat: {
                float value = [[styleProperty valueOfTokenType:CASTokenTypeUnit] floatValue];
                [invocation setArgument:&value atIndex:argIndex];
                break;
            }
            case CASPrimitiveTypeCGSize: {
                CGSize size;
                [styleProperty transformValuesToCGSize:&size];
                [invocation setArgument:&size atIndex:argIndex];
                break;
            }
            case CASPrimitiveTypeCGRect: {
                CGRect rect;
                [styleProperty transformValuesToCGRect:&rect];
                [invocation setArgument:&rect atIndex:argIndex];
                break;
            }
            case CASPrimitiveTypeCGPoint: {
                CGPoint point;
                [styleProperty transformValuesToCGPoint:&point];
                [invocation setArgument:&point atIndex:argIndex];
                break;
            }
            case CASPrimitiveTypeUIEdgeInsets: {
                UIEdgeInsets insets;
                [styleProperty transformValuesToUIEdgeInsets:&insets];
                [invocation setArgument:&insets atIndex:argIndex];
                break;
            }
            case CASPrimitiveTypeUIOffset : {
                UIOffset offset;
                [styleProperty transformValuesToUIOffset:&offset];
                [invocation setArgument:&offset atIndex:argIndex];
                break;
            }
            case CASPrimitiveTypeCGColorRef : {
                UIColor *color = nil;
                [styleProperty transformValuesToUIColor:&color];
                if (color) {
                    [self.invocationObjectArguments addObject:color];
                }
                CGColorRef colorRef = color.CGColor;
                [invocation setArgument:&colorRef atIndex:argIndex];
            }
            default:
                break;
        }

        id objectArg = nil;
        if (argDescriptor.argumentClass == UIImage.class) {
            [styleProperty transformValuesToUIImage:&objectArg];
        } else if (argDescriptor.argumentClass == UIColor.class) {
            [styleProperty transformValuesToUIColor:&objectArg];
        } else if (argDescriptor.argumentClass == NSString.class) {
            [styleProperty transformValuesToNSString:&objectArg];
        } else if (argDescriptor.argumentClass == UIFont.class) {
            [styleProperty transformValuesToUIFont:&objectArg];
        }

        if (styleProperty.childStyleProperties.count) {
            id target = nil;
            Class targetClass = argDescriptor.argumentClass;

            NSString *childKeyPath = keypath.length ? [NSString stringWithFormat:@"%@.%@", keypath, styleProperty.name] : styleProperty.name;

            // handle textAttributes as special case
            BOOL isTextAttributesArg = targetClass == NSDictionary.class && isTextAttributesProperty;
            if (isTextAttributesArg) {
                target = CASTextAttributes.new;
                targetClass = CASTextAttributes.class;
                childKeyPath = nil;
            }

            for (CASStyleProperty *childStyleProperty in styleProperty.childStyleProperties) {
                NSArray *childInvocations = [self invocationsForClass:targetClass styleProperty:childStyleProperty keyPath:childKeyPath];
                
                if (target) {
                    [childInvocations makeObjectsPerformSelector:@selector(invokeWithTarget:) withObject:target];
                } else {
                    [invocations addObjectsFromArray:childInvocations];
                }
            }

            // if textAttributes set argument to dictionary value
            if (isTextAttributesArg) {
                objectArg = [target dictionary];
                [invocation setArgument:&objectArg atIndex:argIndex];
            }
        }

        if (objectArg != nil) {
            [invocation setArgument:&objectArg atIndex:argIndex];
            [self.invocationObjectArguments addObject:objectArg];
        }
    }];
    return invocations;
}

- (void)setupObjectClassDescriptors {

    // Common ENUM maps
    NSDictionary *controlStateMap = @{
        @"normal"               : @(UIControlStateNormal),
        @"highlighted"          : @(UIControlStateHighlighted),
        @"disabled"             : @(UIControlStateDisabled),
        @"selected"             : @(UIControlStateSelected),
        @"selectedHighlighted"  : @(UIControlStateSelected|UIControlStateHighlighted)
    };

    NSDictionary *textAlignmentMap = @{
        @"center"    : @(NSTextAlignmentCenter),
        @"left"      : @(NSTextAlignmentLeft),
        @"right"     : @(NSTextAlignmentRight),
        @"justified" : @(NSTextAlignmentJustified),
        @"natural"   : @(NSTextAlignmentNatural),
    };

    NSDictionary *lineBreakModeMap = @{
        @"wordWrapping"     : @(NSLineBreakByWordWrapping),
        @"charWrapping"     : @(NSLineBreakByCharWrapping),
        @"clipping"         : @(NSLineBreakByClipping),
        @"truncatingHead"   : @(NSLineBreakByTruncatingHead),
        @"truncatingTail"   : @(NSLineBreakByTruncatingTail),
        @"truncatingMiddle" : @(NSLineBreakByTruncatingMiddle)
    };

    NSDictionary *barMetricsMap = @{
        @"default"          : @(UIBarMetricsDefault),
        @"compact"          : @(UIBarMetricsCompact),
        @"defaultPrompt"    : @(UIBarMetricsDefaultPrompt),
        @"compactPrompt"    : @(UIBarMetricsCompactPrompt),
    };

    NSDictionary *searchBarIconMap = @{
        @"search"       : @(UISearchBarIconSearch),
        @"clear"        : @(UISearchBarIconClear),
        @"bookmark"     : @(UISearchBarIconBookmark),
        @"resultsList"  : @(UISearchBarIconResultsList),
    };

    NSDictionary *barPositionMap = @{
        @"any"          : @(UIBarPositionAny),
        @"bottom"       : @(UIBarPositionBottom),
        @"top"          : @(UIBarPositionTop),
        @"topAttached"  : @(UIBarPositionTopAttached),
    };

    // Common CASArgumentDescriptors
    CASArgumentDescriptor *colorArg = [CASArgumentDescriptor argWithClass:UIColor.class];
    CASArgumentDescriptor *dictionaryArg = [CASArgumentDescriptor argWithClass:NSDictionary.class];
    CASArgumentDescriptor *textAlignmentArg = [CASArgumentDescriptor argWithValuesByName:textAlignmentMap];
    CASArgumentDescriptor *lineBreakModeArg = [CASArgumentDescriptor argWithValuesByName:lineBreakModeMap];
    CASArgumentDescriptor *stateArg = [CASArgumentDescriptor argWithName:@"state" valuesByName:controlStateMap];
    CASArgumentDescriptor *imageArg = [CASArgumentDescriptor argWithClass:UIImage.class];
    CASArgumentDescriptor *barMetricsArg = [CASArgumentDescriptor argWithName:@"barMetrics" valuesByName:barMetricsMap];
    CASArgumentDescriptor *floatArg = [CASArgumentDescriptor argWithObjCType:@encode(CGFloat)];
    CASArgumentDescriptor *boolArg = [CASArgumentDescriptor argWithObjCType:@encode(BOOL)];
    CASArgumentDescriptor *offsetArg = [CASArgumentDescriptor argWithObjCType:@encode(UIOffset)];
    CASArgumentDescriptor *searchIconArg = [CASArgumentDescriptor argWithName:@"icon" valuesByName:searchBarIconMap];

    CASArgumentDescriptor *barPositionArg = [CASArgumentDescriptor argWithName:@"barPosition" valuesByName:barPositionMap];

    // UIView
    CASObjectClassDescriptor *objectClassDescriptor = [self objectClassDescriptorForClass:UIView.class];

    NSDictionary *contentModeMap = @{
        @"fill"        : @(UIViewContentModeScaleToFill),
        @"aspectFit"   : @(UIViewContentModeScaleAspectFit),
        @"aspectFill"  : @(UIViewContentModeScaleAspectFill),
        @"redraw"      : @(UIViewContentModeRedraw),
        @"center"      : @(UIViewContentModeCenter),
        @"top"         : @(UIViewContentModeTop),
        @"bottom"      : @(UIViewContentModeBottom),
        @"left"        : @(UIViewContentModeLeft),
        @"right"       : @(UIViewContentModeRight),
        @"topLeft"     : @(UIViewContentModeTopLeft),
        @"topRight"    : @(UIViewContentModeTopRight),
        @"bottomLeft"  : @(UIViewContentModeBottomLeft),
        @"bottomRight" : @(UIViewContentModeBottomRight),
    };
    [objectClassDescriptor setArgumentDescriptors:@[[CASArgumentDescriptor argWithValuesByName:contentModeMap]] forPropertyKey:@cas_propertykey(UIView, contentMode)];

    // some properties don't show up via reflection so we need to add them manually
    [objectClassDescriptor setArgumentDescriptors:@[boolArg] forPropertyKey:@cas_propertykey(UIView, clipsToBounds)];
    [objectClassDescriptor setArgumentDescriptors:@[colorArg] forPropertyKey:@cas_propertykey(UIView, backgroundColor)];
    [objectClassDescriptor setArgumentDescriptors:@[colorArg] forPropertyKey:@cas_propertykey(UIView, tintColor)];

    // UIBarItem
    objectClassDescriptor = [self objectClassDescriptorForClass:UIBarItem.class];
    [objectClassDescriptor setArgumentDescriptors:@[dictionaryArg, stateArg] setter:@selector(setTitleTextAttributes:forState:) forPropertyKey:@"titleTextAttributes"];

    // UILabel
    objectClassDescriptor = [self objectClassDescriptorForClass:UILabel.class];
    [objectClassDescriptor setArgumentDescriptors:@[lineBreakModeArg] forPropertyKey:@cas_propertykey(UILabel, lineBreakMode)];
    [objectClassDescriptor setArgumentDescriptors:@[textAlignmentArg] forPropertyKey:@cas_propertykey(UILabel, textAlignment)];

    // UITextField
    // TODO border insets
    objectClassDescriptor = [self objectClassDescriptorForClass:UITextField.class];
    objectClassDescriptor.propertyKeyAliases = @{
        @"backgroundImage"     : @cas_propertykey(UITextField, background),
        @"textInsets"          : @cas_propertykey(UITextField, cas_textEdgeInsets),
    };

    [objectClassDescriptor setArgumentDescriptors:@[textAlignmentArg] forPropertyKey:@cas_propertykey(UITextField, textAlignment)];

    NSDictionary *borderStyleMap = @{
        @"none"    : @(UITextBorderStyleNone),
        @"line"    : @(UITextBorderStyleLine),
        @"bezel"   : @(UITextBorderStyleBezel),
        @"rounded" : @(UITextBorderStyleRoundedRect),
    };
    [objectClassDescriptor setArgumentDescriptors:@[[CASArgumentDescriptor argWithValuesByName:borderStyleMap]] forPropertyKey:@cas_propertykey(UITextField, borderStyle)];

    NSDictionary *textFieldViewModeMap = @{
        @"never"           : @(UITextFieldViewModeNever),
        @"whileEditing"    : @(UITextFieldViewModeWhileEditing),
        @"unlessEditing"   : @(UITextFieldViewModeUnlessEditing),
        @"always"          : @(UITextFieldViewModeAlways),
    };
    [objectClassDescriptor setArgumentDescriptors:@[[CASArgumentDescriptor argWithValuesByName:textFieldViewModeMap]] forPropertyKey:@cas_propertykey(UITextField, leftViewMode)];
    [objectClassDescriptor setArgumentDescriptors:@[[CASArgumentDescriptor argWithValuesByName:textFieldViewModeMap]] forPropertyKey:@cas_propertykey(UITextField, rightViewMode)];
    [objectClassDescriptor setArgumentDescriptors:@[[CASArgumentDescriptor argWithValuesByName:textFieldViewModeMap]] forPropertyKey:@cas_propertykey(UITextField, clearButtonMode)];
    
    // UIControl
    objectClassDescriptor = [self objectClassDescriptorForClass:UIControl.class];

    NSDictionary *contentVerticalAlignmentMap = @{
        @"center" : @(UIControlContentVerticalAlignmentCenter),
        @"top"    : @(UIControlContentVerticalAlignmentTop),
        @"bottom" : @(UIControlContentVerticalAlignmentBottom),
        @"fill"   : @(UIControlContentVerticalAlignmentFill),
    };
    [objectClassDescriptor setArgumentDescriptors:@[[CASArgumentDescriptor argWithValuesByName:contentVerticalAlignmentMap]] forPropertyKey:@cas_propertykey(UIControl, contentVerticalAlignment)];

    NSDictionary *contentHorizontalAlignmentMap = @{
        @"center" : @(UIControlContentHorizontalAlignmentCenter),
        @"left"   : @(UIControlContentHorizontalAlignmentLeft),
        @"right"  : @(UIControlContentHorizontalAlignmentRight),
        @"fill"   : @(UIControlContentHorizontalAlignmentFill),
    };
    [objectClassDescriptor setArgumentDescriptors:@[[CASArgumentDescriptor argWithValuesByName:contentHorizontalAlignmentMap]] forPropertyKey:@cas_propertykey(UIControl, contentHorizontalAlignment)];

    // UIButton
    objectClassDescriptor = [self objectClassDescriptorForClass:UIButton.class];

    [objectClassDescriptor setArgumentDescriptors:@[colorArg, stateArg] setter:@selector(setTitleColor:forState:) forPropertyKey:@"titleColor"];

    [objectClassDescriptor setArgumentDescriptors:@[colorArg, stateArg] setter:@selector(setTitleShadowColor:forState:) forPropertyKey:@"titleShadowColor"];

    [objectClassDescriptor setArgumentDescriptors:@[imageArg, stateArg] setter:@selector(setBackgroundImage:forState:) forPropertyKey:@"backgroundImage"];
    
    [objectClassDescriptor setArgumentDescriptors:@[imageArg, stateArg] setter:@selector(setImage:forState:) forPropertyKey:@"image"];
    
    

    // UIBarButtonItem
    objectClassDescriptor = [self objectClassDescriptorForClass:UIBarButtonItem.class];

    [objectClassDescriptor setArgumentDescriptors:@[imageArg, stateArg, barMetricsArg] setter:@selector(setBackgroundImage:forState:barMetrics:) forPropertyKey:@"backgroundImage"];

    [objectClassDescriptor setArgumentDescriptors:@[floatArg, barMetricsArg] setter:@selector(setBackgroundVerticalPositionAdjustment:forBarMetrics:) forPropertyKey:@"backgroundVerticalPositionAdjustment"];

    [objectClassDescriptor setArgumentDescriptors:@[offsetArg, barMetricsArg] setter:@selector(setTitlePositionAdjustment:forBarMetrics:) forPropertyKey:@"titlePositionAdjustment"];

    //backButton
    [objectClassDescriptor setArgumentDescriptors:@[imageArg, stateArg, barMetricsArg] setter:@selector(setBackButtonBackgroundImage:forState:barMetrics:) forPropertyKey:@"backButtonBackgroundImage"];

    [objectClassDescriptor setArgumentDescriptors:@[floatArg, barMetricsArg] setter:@selector(setBackButtonBackgroundVerticalPositionAdjustment:forBarMetrics:) forPropertyKey:@"backButtonBackgroundVerticalPositionAdjustment"];

    [objectClassDescriptor setArgumentDescriptors:@[offsetArg, barMetricsArg] setter:@selector(setBackButtonTitlePositionAdjustment:forBarMetrics:) forPropertyKey:@"backButtonTitlePositionAdjustment"];

    // UINavigationBar
    objectClassDescriptor = [self objectClassDescriptorForClass:UINavigationBar.class];
    if (CASKeyDeviceSystemMajorVersion() >= 7) {
        [objectClassDescriptor setArgumentDescriptors:@[imageArg, barPositionArg, barMetricsArg] setter:@selector(setBackgroundImage:forBarPosition:barMetrics:) forPropertyKey:@"backgroundImage"];
    } else {
        [objectClassDescriptor setArgumentDescriptors:@[imageArg, barMetricsArg] setter:@selector(setBackgroundImage:forBarMetrics:) forPropertyKey:@"backgroundImage"];
    }

    [objectClassDescriptor setArgumentDescriptors:@[boolArg] forPropertyKey:@cas_propertykey(UINavigationBar, translucent)];
    [objectClassDescriptor setArgumentDescriptors:@[floatArg, barMetricsArg] setter:@selector(setTitleVerticalPositionAdjustment:forBarMetrics:) forPropertyKey:@"titleVerticalPositionAdjustment"];

    // UISearchBar
    objectClassDescriptor = [self objectClassDescriptorForClass:UISearchBar.class];
    if (CASKeyDeviceSystemMajorVersion() >= 7) {
        [objectClassDescriptor setArgumentDescriptors:@[imageArg, barPositionArg, barMetricsArg] setter:@selector(setBackgroundImage:forBarPosition:barMetrics:) forPropertyKey:@"backgroundImage"];
    }

    [objectClassDescriptor setArgumentDescriptors:@[imageArg, stateArg] setter:@selector(setSearchFieldBackgroundImage:forState:) forPropertyKey:@"searchFieldBackgroundImage"];

    [objectClassDescriptor setArgumentDescriptors:@[imageArg, searchIconArg, stateArg] setter:@selector(setImage:forSearchBarIcon:state:) forPropertyKey:@"iconImage"];

    [objectClassDescriptor setArgumentDescriptors:@[imageArg, stateArg] setter:@selector(setScopeBarButtonBackgroundImage:forState:) forPropertyKey:@"scopeBarButtonBackgroundImage"];

    [objectClassDescriptor setArgumentDescriptors:@[imageArg, [CASArgumentDescriptor argWithName:@"leftSegmentState" valuesByName:controlStateMap], [CASArgumentDescriptor argWithName:@"rightSegmentState" valuesByName:controlStateMap]] setter:@selector(setScopeBarButtonDividerImage:forLeftSegmentState:rightSegmentState:) forPropertyKey:@"scopeBarButtonDividerImage"];

    [objectClassDescriptor setArgumentDescriptors:@[offsetArg, searchIconArg] setter:@selector(setPositionAdjustment:forSearchBarIcon:) forPropertyKey:@"iconPositionAdjustment"];

    [objectClassDescriptor setArgumentDescriptors:@[dictionaryArg, stateArg] setter:@selector(setScopeBarButtonTitleTextAttributes:forState:) forPropertyKey:@"scopeBarButtonTitleTextAttributes"];

    // UISegmentedControl
    objectClassDescriptor = [self objectClassDescriptorForClass:UISegmentedControl.class];

    [objectClassDescriptor setArgumentDescriptors:@[imageArg, stateArg, barMetricsArg] setter:@selector(setBackgroundImage:forState:barMetrics:) forPropertyKey:@"backgroundImage"];

    [objectClassDescriptor setArgumentDescriptors:@[imageArg, [CASArgumentDescriptor argWithName:@"leftSegmentState" valuesByName:controlStateMap], [CASArgumentDescriptor argWithName:@"rightSegmentState" valuesByName:controlStateMap], barMetricsArg] setter:@selector(setDividerImage:forLeftSegmentState:rightSegmentState:barMetrics:) forPropertyKey:@"dividerImage"];

    NSDictionary *segmentedControlSegmentMap = @{
        @"any"    : @(UISegmentedControlSegmentAny),
        @"left"   : @(UISegmentedControlSegmentLeft),
        @"center" : @(UISegmentedControlSegmentCenter),
        @"right"  : @(UISegmentedControlSegmentRight),
        @"alone"  : @(UISegmentedControlSegmentAlone),
    };
    [objectClassDescriptor setArgumentDescriptors:@[offsetArg, [CASArgumentDescriptor argWithName:@"segmentType" valuesByName:segmentedControlSegmentMap], barMetricsArg] setter:@selector(setContentPositionAdjustment:forSegmentType:barMetrics:) forPropertyKey:@"contentPositionAdjustment"];

    [objectClassDescriptor setArgumentDescriptors:@[dictionaryArg, stateArg] setter:@selector(setTitleTextAttributes:forState:) forPropertyKey:@"titleTextAttributes"];
    
    // UIStepper
    objectClassDescriptor = [self objectClassDescriptorForClass:UIStepper.class];

    [objectClassDescriptor setArgumentDescriptors:@[imageArg, stateArg] setter:@selector(setBackgroundImage:forState:) forPropertyKey:@"backgroundImage"];

    [objectClassDescriptor setArgumentDescriptors:@[imageArg, [CASArgumentDescriptor argWithName:@"leftSegmentState" valuesByName:controlStateMap], [CASArgumentDescriptor argWithName:@"rightSegmentState" valuesByName:controlStateMap]] setter:@selector(setDividerImage:forLeftSegmentState:rightSegmentState:) forPropertyKey:@"dividerImage"];

    [objectClassDescriptor setArgumentDescriptors:@[imageArg, stateArg] setter:@selector(setDecrementImage:forState:) forPropertyKey:@"decrementImage"];

    [objectClassDescriptor setArgumentDescriptors:@[imageArg, stateArg] setter:@selector(setIncrementImage:forState:) forPropertyKey:@"incrementImage"];

    // UITabBar
    objectClassDescriptor = [self objectClassDescriptorForClass:UITabBar.class];
    if (CASKeyDeviceSystemMajorVersion() >= 7) {
        NSDictionary *tabBarItemPositioningMap = @{
            @"auto"      : @(UITabBarItemPositioningAutomatic),
            @"automatic" : @(UITabBarItemPositioningAutomatic),
            @"fill"      : @(UITabBarItemPositioningFill),
            @"centered"  : @(UITabBarItemPositioningCentered),
        };
        [objectClassDescriptor setArgumentDescriptors:@[[CASArgumentDescriptor argWithValuesByName:tabBarItemPositioningMap]] forPropertyKey:@cas_propertykey(UITabBar, itemPositioning)];

        NSDictionary *barStyleMap = @{
            @"default" : @(UIBarStyleDefault),
            @"black"   : @(UIBarStyleBlack),
        };
        [objectClassDescriptor setArgumentDescriptors:@[[CASArgumentDescriptor argWithValuesByName:barStyleMap]] forPropertyKey:@cas_propertykey(UITabBar, barStyle)];
        
        [objectClassDescriptor setArgumentDescriptors:@[boolArg] forPropertyKey:@cas_propertykey(UITabBar, translucent)];
    }

    // UITabBarItem
    objectClassDescriptor = [self objectClassDescriptorForClass:UITabBarItem.class];
    [objectClassDescriptor setArgumentDescriptors:@[[CASArgumentDescriptor argWithObjCType:@encode(UIOffset)]] forPropertyKey:@cas_propertykey(UITabBarItem, titlePositionAdjustment)];

    // UIToolBar
    objectClassDescriptor = [self objectClassDescriptorForClass:UIToolbar.class];

    [objectClassDescriptor setArgumentDescriptors:@[imageArg, [CASArgumentDescriptor argWithName:@"toolbarPosition" valuesByName:barPositionMap], barMetricsArg] setter:@selector(setBackgroundImage:forToolbarPosition:barMetrics:) forPropertyKey:@"backgroundImage"];

    [objectClassDescriptor setArgumentDescriptors:@[imageArg, [CASArgumentDescriptor argWithName:@"toolbarPosition" valuesByName:barPositionMap]] setter:@selector(setShadowImage:forToolbarPosition:) forPropertyKey:@"shadowImage"];

    // CASTextAttributes
    objectClassDescriptor = [self objectClassDescriptorForClass:CASTextAttributes.class];

    NSDictionary *underlineStyleMap;
    if (CASKeyDeviceSystemMajorVersion() >= 7) {
        underlineStyleMap = @{
            @"none"      : @(NSUnderlineStyleNone),
            @"single"    : @(NSUnderlineStyleSingle),
            @"thick"     : @(NSUnderlineStyleThick),
            @"double"    : @(NSUnderlineStyleDouble),
            @"solid"     : @(NSUnderlinePatternSolid),
            @"dot"       : @(NSUnderlinePatternDot),
            @"dash"      : @(NSUnderlinePatternDash),
            @"dashDot"   : @(NSUnderlinePatternDashDot),
            @"dotDotDot" : @(NSUnderlinePatternDashDotDot),
            @"byWord"    : @(NSUnderlineByWord),
        };
    } else {
        underlineStyleMap = @{
            @"none"    : @(NSUnderlineStyleNone),
            @"single"  : @(NSUnderlineStyleSingle),
        };
    }

    CASArgumentDescriptor *underlineStyleArg = [CASArgumentDescriptor argWithValuesByName:underlineStyleMap];
    [objectClassDescriptor setArgumentDescriptors:@[underlineStyleArg] forPropertyKey:@cas_propertykey(CASTextAttributes, underlineStyle)];
    [objectClassDescriptor setArgumentDescriptors:@[underlineStyleArg] forPropertyKey:@cas_propertykey(CASTextAttributes, strikethroughStyle)];

    // NSParagraphStyle
    objectClassDescriptor = [self objectClassDescriptorForClass:NSParagraphStyle.class];
    [objectClassDescriptor setArgumentDescriptors:@[textAlignmentArg] forPropertyKey:@cas_propertykey(NSParagraphStyle, alignment)];
    [objectClassDescriptor setArgumentDescriptors:@[lineBreakModeArg] forPropertyKey:@cas_propertykey(NSParagraphStyle, lineBreakMode)];


    // NSShadow
    objectClassDescriptor = [self objectClassDescriptorForClass:NSShadow.class];
    [objectClassDescriptor setArgumentDescriptors:@[colorArg] forPropertyKey:@cas_propertykey(NSShadow, shadowColor)];

    // UISlider
    objectClassDescriptor = [self objectClassDescriptorForClass:UISlider.class];
    [objectClassDescriptor setArgumentDescriptors:@[imageArg, stateArg] setter:@selector(setMinimumTrackImage:forState:) forPropertyKey:@"minimumTrackImage"];
    [objectClassDescriptor setArgumentDescriptors:@[imageArg, stateArg] setter:@selector(setMaximumTrackImage:forState:) forPropertyKey:@"maximumTrackImage"];
    [objectClassDescriptor setArgumentDescriptors:@[imageArg, stateArg] setter:@selector(setThumbImage:forState:) forPropertyKey:@"thumbImage"];
}

- (CASObjectClassDescriptor *)objectClassDescriptorForClass:(Class)aClass {
    CASObjectClassDescriptor *objectClassDescriptor = [self.objectClassDescriptorCache objectForKey:aClass];
    if (!objectClassDescriptor) {
        objectClassDescriptor = [[CASObjectClassDescriptor alloc] initWithClass:aClass];
        if (aClass.superclass && ![NSObject.class isSubclassOfClass:aClass.superclass] && ![UIResponder.class isSubclassOfClass:aClass.superclass]) {
            objectClassDescriptor.parent = [self objectClassDescriptorForClass:aClass.superclass];
        }
        [self.objectClassDescriptorCache setObject:objectClassDescriptor forKey:aClass];
    }
    return objectClassDescriptor;
}

#pragma mark - sceduling

- (void)updateScheduledItems {
    for (id<CASStyleableItem> item in self.scheduledItems.allObjects.copy) {
        if (!item) continue;
        [item cas_updateStylingIfNeeded];
    }

    if (self.scheduledItems.allObjects.count == 0) {
        [self.updateTimer invalidate];
        self.updateTimer = nil;
    }
}

- (void)scheduleUpdateForItem:(id<CASStyleableItem>)item {
    [self.scheduledItems addObject:item];

    if (self.scheduledItems.allObjects.count && !self.updateTimer.isValid) {
        self.updateTimer = [NSTimer timerWithTimeInterval:0.0 target:self selector:@selector(updateScheduledItems) userInfo:nil repeats:YES];
        [NSRunLoop.mainRunLoop addTimer:self.updateTimer forMode:NSRunLoopCommonModes];
    }
}

- (void)unscheduleUpdateForItem:(id<CASStyleableItem>)item {
    [self.scheduledItems removeObject:item];

    if (self.scheduledItems.allObjects.count == 0) {
        [self.updateTimer invalidate];
        self.updateTimer = nil;
    }
}

#pragma mark - file watcher

- (void)setWatchFilePath:(NSString *)watchFilePath {
    _watchFilePath = watchFilePath;
    self.filePath = watchFilePath;
}

- (void)reloadOnChangesToFilePath:(NSString *)filePath {
    dispatch_source_t source = [self.class watchForChangesToFilePath:filePath withCallback:^{
        dispatch_async(dispatch_get_main_queue(), ^{
            // reload styles
            _filePath = nil;
            self.filePath = _watchFilePath;

            // reapply styles
            for (UIWindow *window in self.targetWindows) {
                [self styleSubviewsOfView:window];
            }
        });
    }];
    [self.fileWatchers addObject:source];
}

- (void)styleSubviewsOfView:(UIView *)view {
    for (UIView *subview in view.subviews) {
        [subview cas_updateStyling];
        [self styleSubviewsOfView:subview];
    }
}

+ (dispatch_source_t)watchForChangesToFilePath:(NSString *)filePath withCallback:(dispatch_block_t)callback {
    dispatch_queue_t queue = dispatch_get_global_queue(DISPATCH_QUEUE_PRIORITY_DEFAULT, 0);
    int fileDescriptor = open([filePath UTF8String], O_EVTONLY);

    NSAssert(fileDescriptor > 0, @"Error could subscribe to events for file at path: %@", filePath);

    __block dispatch_source_t source = dispatch_source_create(DISPATCH_SOURCE_TYPE_VNODE, fileDescriptor,
                                                              DISPATCH_VNODE_DELETE | DISPATCH_VNODE_WRITE | DISPATCH_VNODE_EXTEND,
                                                              queue);
    dispatch_source_set_event_handler(source, ^{
        unsigned long flags = dispatch_source_get_data(source);
        if (flags) {
            dispatch_source_cancel(source);
            callback();
            [self watchForChangesToFilePath:filePath withCallback:callback];
        }
    });
    dispatch_source_set_cancel_handler(source, ^(void){
        close(fileDescriptor);
    });
    dispatch_resume(source);
    return source;
}

@end


================================================
FILE: Classy/Parser/CASTextAttributes.h
================================================
//
//  CASTextAttributes.h
//  
//
//  Created by Jonas Budelmann on 4/11/13.
//
//

#import <Foundation/Foundation.h>
#import <UIKit/UIKit.h>

/**
 *  Each property maps directly to a attribute name
 *  ie font = NSFontAttributeName
 */
@interface CASTextAttributes : NSObject

@property (nonatomic, strong) UIFont *font;
@property (nonatomic, strong) NSMutableParagraphStyle *paragraphStyle;
@property (nonatomic, strong) UIColor *foregroundColor;
@property (nonatomic, strong) UIColor *backgroundColor;
@property (nonatomic, assign) NSInteger ligature;
@property (nonatomic, assign) CGFloat kern;
@property (nonatomic, assign) NSUnderlineStyle strikethroughStyle;
@property (nonatomic, assign) NSUnderlineStyle underlineStyle;
@property (nonatomic, strong) UIColor *strokeColor;
@property (nonatomic, assign) CGFloat strokeWidth;
@property (nonatomic, strong) NSShadow *shadow;
@property (nonatomic, assign) CGFloat baselineOffset;

/**
 *  Transformer receiver into appropriate NSDictionary
 *
 *  @return NSDictionary containing text attribute keys and values
 */
- (NSDictionary *)dictionary;

@end


================================================
FILE: Classy/Parser/CASTextAttributes.m
================================================
//
//  CASTextAttributes.m
//  
//
//  Created by Jonas Budelmann on 4/11/13.
//
//

#import "CASTextAttributes.h"
#import "CASUtilities.h"

@implementation CASTextAttributes

- (NSMutableParagraphStyle *)paragraphStyle {
    if (!_paragraphStyle) {
        _paragraphStyle = [NSParagraphStyle defaultParagraphStyle].mutableCopy;
    }
    return _paragraphStyle;
}

- (NSShadow *)shadow {
    if (!_shadow) {
        _shadow = NSShadow.new;
    }
    return _shadow;
}

- (NSDictionary *)dictionary {
    NSMutableDictionary *dictionary = NSMutableDictionary.new;

    if (self.font) {
        dictionary[NSFontAttributeName] = self.font;
    }

    if (_paragraphStyle) {
        dictionary[NSParagraphStyleAttributeName] = _paragraphStyle;
    }

    if (self.foregroundColor) {
        dictionary[NSForegroundColorAttributeName] = self.foregroundColor;
    }

    if (self.backgroundColor) {
        dictionary[NSBackgroundColorAttributeName] = self.backgroundColor;
    }

    dictionary[NSLigatureAttributeName] = @(self.ligature);
    dictionary[NSKernAttributeName] = @(self.kern);
    dictionary[NSStrikethroughStyleAttributeName] = @(self.strikethroughStyle);
    dictionary[NSUnderlineStyleAttributeName] = @(self.underlineStyle);
    
    if (CASKeyDeviceSystemMajorVersion() >= 7) {
        dictionary[NSBaselineOffsetAttributeName] = @(self.baselineOffset);
    }
    
    if (self.strokeColor) {
        dictionary[NSStrokeColorAttributeName] = self.strokeColor;
    }

    dictionary[NSStrokeWidthAttributeName] = @(self.strokeWidth);

    if (_shadow) {
        dictionary[NSShadowAttributeName] = _shadow;
    }

#if __IPHONE_OS_VERSION_MIN_REQUIRED < __IPHONE_7_0
    if (CASKeyDeviceSystemMajorVersion() < 7) {
        if (self.font) {
            dictionary[UITextAttributeFont] = self.font;
        }
        if (self.foregroundColor) {
            dictionary[UITextAttributeTextColor] = self.foregroundColor;
        }
        if (_shadow.shadowColor) {
            dictionary[UITextAttributeTextShadowColor] = _shadow.shadowColor;
        }
        dictionary[UITextAttributeTextShadowOffset] = [NSValue valueWithCGSize:_shadow.shadowOffset];
    }
#endif
    return dictionary;
}

@end


================================================
FILE: Classy/Parser/CASToken.h
================================================
//
//  CASToken.h
//  Classy
//
//  Created by Jonas Budelmann on 16/09/13.
//  Copyright (c) 2013 cloudling. All rights reserved.
//

#import <Foundation/Foundation.h>

typedef NS_ENUM(NSInteger, CASTokenType) {
    CASTokenTypeUnknown,
    CASTokenTypeIndent,
    CASTokenTypeOutdent,
    CASTokenTypeEOS,
    CASTokenTypeSemiColon,
    CASTokenTypeCarat,
    CASTokenTypeNewline,
    CASTokenTypeLeftSquareBrace,
    CASTokenTypeRightSquareBrace,
    CASTokenTypeLeftCurlyBrace,
    CASTokenTypeRightCurlyBrace,
    CASTokenTypeLeftRoundBrace,
    CASTokenTypeRightRoundBrace,
    CASTokenTypeColor,
    CASTokenTypeString,
    CASTokenTypeUnit,
    CASTokenTypeBoolean,
    CASTokenTypeRef,
    CASTokenTypeOperator,
    CASTokenTypeSpace,
    CASTokenTypeSelector,
};

@interface CASToken : NSObject <NSCoding>

/**
 *  The type of the token, may not represent the true type which is determined in context of other tokens
 */
@property (nonatomic, assign, readonly) CASTokenType type;

/**
 *  The value of the token, could be a boxed primitive or a `NSString`, `UIColor`, ...
 */
@property (nonatomic, strong) id value;

/**
 *  The line number at which the token appeared in the style file, used for debug and error messages
 */
@property (nonatomic, assign) NSInteger lineNumber;

/**
 *  Factory method for creating tokens with a particular `CASTokenType`
 */
+ (instancetype)tokenOfType:(CASTokenType)type;

/**
 *  Factory method for creating tokens with a particular `CASTokenType` and value
 */
+ (instancetype)tokenOfType:(CASTokenType)type value:(id)value;

/**
 *  Returns a `NSString` representation of a `CASTokenType`
 *  Mainly used for debug output
 *
 *  @param type The `CASTokenType` to convert to a `NSString`
 *
 *  @return a `NSString` representing the passed `CASTokenType`
 */
+ (NSString *)stringForType:(CASTokenType)type;

/**
 *  Returns value of receiver as a string
 */
- (NSString *)stringValue;

/**
 *  Returns whether receiver is a whitespace token or not
 *
 *  @return `YES` if receiver is one of the following types indent, outdent, space, new line.
 */
- (BOOL)isWhitespace;

/**
 * Returns whether receiver's value is equal to the given value
 *
 * @param value the value for comparison
 *
 * @return `YES` if receiver is equal to the given value
 */
- (BOOL)valueIsEqualTo:(id)value;

/**
 *  Returns whether the receiver could be a valid selector token.
 *  However context will determine if it is definitely a selector
 *
 *  @return `YES` if it is possible that the receiver is a selector
 */
- (BOOL)isPossiblySelector;

/**
 *  Returns whether the receiver could be a valid variable token.
 *  However context will determine if it is definitely a variable
 *
 *  @return `YES` if it is possible that the receiver is a variable
 */
- (BOOL)isPossiblyVar;

/**
 *  Returns whether the receiver could be a valid expression token.
 *  However context will determine if it is definitely part of an expression
 *
 *  @return `YES` if it is possible that the receiver is a expression token
 */
- (BOOL)isPossiblyExpression;


/**
 *  Returns whether the receiver could be a valid selector delimiting token.
 *  However context will determine if it is definitely a selector delimiter
 *
 *  @return `YES` if it is possible that the receiver is a selector delimiter
 */
- (BOOL)isPossiblySelectorDelimiter;

@end


================================================
FILE: Classy/Parser/CASToken.m
================================================
//
//  CASToken.m
//  Classy
//
//  Created by Jonas Budelmann on 16/09/13.
//  Copyright (c) 2013 cloudling. All rights reserved.
//

#import "CASToken.h"

@interface CASToken ()

@property (nonatomic, assign, readwrite) CASTokenType type;

@end

@implementation CASToken

#pragma mark - debug

+ (NSString *)stringForType:(CASTokenType)type {
    switch (type) {
        case CASTokenTypeUnknown:
            return @"unknown";
        case CASTokenTypeIndent:
            return @"indent";
        case CASTokenTypeOutdent:
            return @"outdent";
        case CASTokenTypeEOS:
            return @"EOS";
        case CASTokenTypeSemiColon:
            return @"semicolon";
        case CASTokenTypeCarat:
            return @"carat";
        case CASTokenTypeNewline:
            return @"newline";
        case CASTokenTypeLeftSquareBrace:
            return @"left square brace";
        case CASTokenTypeRightSquareBrace:
            return @"right square brace";
        case CASTokenTypeLeftCurlyBrace:
            return @"left curly brace";
        case CASTokenTypeRightCurlyBrace:
            return @"right curly brace";
        case CASTokenTypeLeftRoundBrace:
            return @"left round brace";
        case CASTokenTypeRightRoundBrace:
            return @"right round brace";
        case CASTokenTypeColor:
            return @"color";
        case CASTokenTypeString:
            return @"string";
        case CASTokenTypeUnit:
            return @"unit";
        case CASTokenTypeBoolean:
            return @"boolean";
        case CASTokenTypeRef:
            return @"ref";
        case CASTokenTypeOperator:
            return @"operator";
        case CASTokenTypeSpace:
            return @"space";
        case CASTokenTypeSelector:
            return @"selector";
    }
}

- (NSString *)description {
    if (self.value) {
        return [NSString stringWithFormat:@"%@ %@", [self.class stringForType:self.type], self.value];
    }
    return [self.class stringForType:self.type];
}

#pragma mark - Factory

+ (instancetype)tokenOfType:(CASTokenType)type {
    CASToken *token = self.class.new;
    token.type = type;
    return token;
}

+ (instancetype)tokenOfType:(CASTokenType)type value:(id)value {
    CASToken *token = self.class.new;
    token.type = type;
    token.value = value;
    return token;
}

#pragma mark - Helpers

- (NSString *)stringValue {
    if ([self.value isKindOfClass:NSString.class]) {
        return self.value;
    }
    return [self.value stringValue];
}

- (BOOL)isWhitespace {
    return self.type == CASTokenTypeIndent
        || self.type == CASTokenTypeOutdent
        || self.type == CASTokenTypeNewline
        || self.type == CASTokenTypeSpace;
}

- (BOOL)valueIsEqualTo:(id)value {
    return [self.value isEqual:value];
}

- (BOOL)isPossiblySelector {
    return self.type == CASTokenTypeRef
        || self.type == CASTokenTypeCarat
        || self.type == CASTokenTypeLeftSquareBrace
        || self.type == CASTokenTypeRightSquareBrace
        || self.type == CASTokenTypeSelector
        || self.type == CASTokenTypeNewline
        || self.type == CASTokenTypeSpace
        || self.type == CASTokenTypeOperator
        || [self valueIsEqualTo:@":"]
        || [self valueIsEqualTo:@","];
}

- (BOOL)isPossiblyVar {
    return self.type == CASTokenTypeIndent
        || self.type == CASTokenTypeSpace
        || self.type == CASTokenTypeRef
        || [self valueIsEqualTo:@"="];
}

- (BOOL)isPossiblyExpression {
    return self.type == CASTokenTypeUnit
        || self.type == CASTokenTypeSpace
        || self.type == CASTokenTypeLeftRoundBrace
        || self.type == CASTokenTypeRightRoundBrace
        || (self.type == CASTokenTypeOperator);
}

- (BOOL)isPossiblySelectorDelimiter{
    return self.type == CASTokenTypeLeftCurlyBrace || self.type == CASTokenTypeIndent;
}

- (BOOL)isEqual:(id)object
{
    if ([object isKindOfClass:[CASToken class]]) {
        CASToken *other = object;
        
        return other.type == self.type && ([other.value isEqual:self.value] || other.value == self.value);
    }
    else {
        return NO;
    }
}

#pragma mark - NSCoding

- (id)initWithCoder:(NSCoder *)aDecoder {
    self = [self init];
    if (self != nil) {
        self.type       = [aDecoder decodeIntegerForKey:NSStringFromSelector(@selector(type))];
        self.value      = [aDecoder decodeObjectForKey:NSStringFromSelector(@selector(value))];
    }
    
    return self;
}

- (void)encodeWithCoder:(NSCoder *)aCoder {
    [aCoder encodeInteger:self.type forKey:NSStringFromSelector(@selector(type))];
    [aCoder encodeObject:self.value forKey:NSStringFromSelector(@selector(value))];
}

@end


================================================
FILE: Classy/Parser/CASUnitToken.h
================================================
//
//  CASUnitToken.h
//  Classy
//
//  Created by Jonas Budelmann on 23/11/13.
//  Copyright (c) 2013 cloudling. All rights reserved.
//

#import "CASToken.h"

@interface CASUnitToken : CASToken

@property (nonatomic, copy) NSString *suffix;
@property (nonatomic, copy) NSString *rawValue;

@end


================================================
FILE: Classy/Parser/CASUnitToken.m
================================================
//
//  CASUnitToken.m
//  Classy
//
//  Created by Jonas Budelmann on 23/11/13.
//  Copyright (c) 2013 cloudling. All rights reserved.
//

#import "CASUnitToken.h"

@implementation CASUnitToken

#pragma mark - Helpers

- (NSString *)stringValue {
    return self.rawValue;
}

@end


================================================
FILE: Classy/Parser/CASUtilities.h
================================================
//
//  CASUtilities.h
//  
//
//  Created by Jonas Budelmann on 22/10/13.
//
//

#import <Foundation/Foundation.h>

// Logging
#ifdef DEBUG
#   define CASLog(fmt, ...) NSLog((@"[Classy] %s " fmt), __PRETTY_FUNCTION__, ##__VA_ARGS__);
#else
#   define CASLog(...)
#endif

// Keypath compile check
#define cas_propertykey(classname, property) \
(((void)(NO && ((void)(( classname *)(nil)).property, NO)), # property))

// Path resolution
#define CASAbsoluteFilePath(relativePath) \
_CASAbsoluteFilePath(__FILE__, relativePath)
NSString *_CASAbsoluteFilePath(const char *currentFilePath, NSString *relativeFilePath);

// Device versions
NSUInteger CASKeyDeviceSystemMajorVersion(void);

BOOL CASDeviceSystemVersionIsEqualTo(NSString *systemVersion);

BOOL CASDeviceSystemVersionIsGreaterThan(NSString *systemVersion);

BOOL CASDeviceSystemVersionIsGreaterThanOrEqualTo(NSString *systemVersion);

BOOL CASDeviceSystemVersionIsLessThan(NSString *systemVersion);

BOOL CASDeviceSystemVersionIsLessThanOrEqualTo(NSString *systemVersion);


================================================
FILE: Classy/Parser/CASUtilities.m
================================================
//
//  CASUtilities.m
//  
//
//  Created by Jonas Budelmann on 22/10/13.
//
//

#import "CASUtilities.h"
#import <UIKit/UIKit.h>

NSString *_CASAbsoluteFilePath(const char *currentFilePath, NSString *relativeFilePath) {
    NSString *currentDirectory = [[NSString stringWithUTF8String:currentFilePath] stringByDeletingLastPathComponent];
    return [currentDirectory stringByAppendingPathComponent:relativeFilePath];
}

NSUInteger CASKeyDeviceSystemMajorVersion(void) {
    static NSUInteger _deviceSystemMajorVersion = -1;
    static dispatch_once_t onceToken;
    dispatch_once(&onceToken, ^{
        _deviceSystemMajorVersion = [[[UIDevice.currentDevice.systemVersion componentsSeparatedByString:@"."] objectAtIndex:0] intValue];
    });
    return _deviceSystemMajorVersion;
}

BOOL CASDeviceSystemVersionIsEqualTo(NSString *systemVersion) {
    return [UIDevice.currentDevice.systemVersion compare:systemVersion options:NSNumericSearch] == NSOrderedSame;
}

BOOL CASDeviceSystemVersionIsGreaterThan(NSString *systemVersion) {
    return [UIDevice.currentDevice.systemVersion compare:systemVersion options:NSNumericSearch] == NSOrderedDescending;
}

BOOL CASDeviceSystemVersionIsGreaterThanOrEqualTo(NSString *systemVersion) {
    return [UIDevice.currentDevice.systemVersion compare:systemVersion options:NSNumericSearch] != NSOrderedAscending;
}

BOOL CASDeviceSystemVersionIsLessThan(NSString *systemVersion) {
    return [UIDevice.currentDevice.systemVersion compare:systemVersion options:NSNumericSearch] == NSOrderedAscending;
}

BOOL CASDeviceSystemVersionIsLessThanOrEqualTo(NSString *systemVersion) {
    return [UIDevice.currentDevice.systemVersion compare:systemVersion options:NSNumericSearch] != NSOrderedDescending;
}


================================================
FILE: Classy/Reflection/CASArgumentDescriptor.h
================================================
//
//  CASArgumentDescriptor.h
//  Classy
//
//  Created by Jonas Budelmann on 12/10/13.
//  Copyright (c) 2013 cloudling. All rights reserved.
//

#import <Foundation/Foundation.h>

/**
 *  Supported primitive argument types
 */
typedef NS_ENUM(NSUInteger, CASPrimitiveType) {
    CASPrimitiveTypeNone,
    CASPrimitiveTypeUnsupported,
    CASPrimitiveTypeBOOL,
    CASPrimitiveTypeFloat,
    CASPrimitiveTypeDouble,
    CASPrimitiveTypeInteger,
    CASPrimitiveTypeCGPoint,
    CASPrimitiveTypeCGSize
Download .txt
gitextract_4fisv63u/

├── .gitignore
├── .travis.yml
├── CHANGELOG.md
├── CONTRIBUTING.md
├── Classy/
│   ├── Additions/
│   │   ├── CASStyleClassUtilities.h
│   │   ├── CASStyleClassUtilities.m
│   │   ├── CASStyleableItem.h
│   │   ├── CASStyleableItem.m
│   │   ├── NSObject+CASSwizzle.h
│   │   ├── NSObject+CASSwizzle.m
│   │   ├── NSRegularExpression+CASAdditions.h
│   │   ├── NSRegularExpression+CASAdditions.m
│   │   ├── NSString+CASAdditions.h
│   │   ├── NSString+CASAdditions.m
│   │   ├── UIBarItem+CASAdditions.h
│   │   ├── UIBarItem+CASAdditions.m
│   │   ├── UIColor+CASAdditions.h
│   │   ├── UIColor+CASAdditions.m
│   │   ├── UINavigationBar+CASAdditions.h
│   │   ├── UINavigationBar+CASAdditions.m
│   │   ├── UINavigationItem+CASAdditions.h
│   │   ├── UINavigationItem+CASAdditions.m
│   │   ├── UISlider+CASAdditions.h
│   │   ├── UISlider+CASAdditions.m
│   │   ├── UITabBar+CASAdditions.h
│   │   ├── UITabBar+CASAdditions.m
│   │   ├── UITextField+CASAdditions.h
│   │   ├── UITextField+CASAdditions.m
│   │   ├── UIToolbar+CASAdditions.h
│   │   ├── UIToolbar+CASAdditions.m
│   │   ├── UIView+CASAdditions.h
│   │   ├── UIView+CASAdditions.m
│   │   ├── UIViewController+CASAdditions.h
│   │   └── UIViewController+CASAdditions.m
│   ├── Classy.h
│   ├── Parser/
│   │   ├── CASDeviceOSVersionItem.h
│   │   ├── CASDeviceOSVersionItem.m
│   │   ├── CASDeviceScreenSizeItem.h
│   │   ├── CASDeviceScreenSizeItem.m
│   │   ├── CASDeviceSelector.h
│   │   ├── CASDeviceSelector.m
│   │   ├── CASDeviceSelectorItem.h
│   │   ├── CASDeviceTypeItem.h
│   │   ├── CASDeviceTypeItem.m
│   │   ├── CASExpressionSolver.h
│   │   ├── CASExpressionSolver.m
│   │   ├── CASInvocation.h
│   │   ├── CASInvocation.m
│   │   ├── CASLexer.h
│   │   ├── CASLexer.m
│   │   ├── CASParser.h
│   │   ├── CASParser.m
│   │   ├── CASStyleNode.h
│   │   ├── CASStyleNode.m
│   │   ├── CASStyleProperty.h
│   │   ├── CASStyleProperty.m
│   │   ├── CASStyleSelector.h
│   │   ├── CASStyleSelector.m
│   │   ├── CASStyler.h
│   │   ├── CASStyler.m
│   │   ├── CASTextAttributes.h
│   │   ├── CASTextAttributes.m
│   │   ├── CASToken.h
│   │   ├── CASToken.m
│   │   ├── CASUnitToken.h
│   │   ├── CASUnitToken.m
│   │   ├── CASUtilities.h
│   │   └── CASUtilities.m
│   ├── Reflection/
│   │   ├── CASArgumentDescriptor.h
│   │   ├── CASArgumentDescriptor.m
│   │   ├── CASAssociatedObjectsWeakWrapper.h
│   │   ├── CASAssociatedObjectsWeakWrapper.m
│   │   ├── CASObjectClassDescriptor.h
│   │   ├── CASObjectClassDescriptor.m
│   │   ├── CASPropertyDescriptor.h
│   │   ├── CASPropertyDescriptor.m
│   │   ├── CASRuntimeExtensions.h
│   │   └── CASRuntimeExtensions.m
│   └── Supporting Files/
│       └── Info.plist
├── Classy.podspec
├── Classy.xcodeproj/
│   ├── project.pbxproj
│   └── xcshareddata/
│       └── xcschemes/
│           └── Classy.xcscheme
├── Example/
│   ├── ClassyExample/
│   │   ├── CASAppDelegate.h
│   │   ├── CASAppDelegate.m
│   │   ├── CASCatalogViewController.h
│   │   ├── CASCatalogViewController.m
│   │   ├── CASRootViewController.h
│   │   ├── CASRootViewController.m
│   │   ├── CASSimpleFormViewController.h
│   │   ├── CASSimpleFormViewController.m
│   │   ├── ClassyExample-Info.plist
│   │   ├── ClassyExample-Prefix.pch
│   │   ├── Images.xcassets/
│   │   │   ├── AppIcon.appiconset/
│   │   │   │   └── Contents.json
│   │   │   └── LaunchImage.launchimage/
│   │   │       └── Contents.json
│   │   ├── Stylesheets/
│   │   │   ├── stylesheet.cas
│   │   │   └── variables.cas
│   │   ├── en.lproj/
│   │   │   └── InfoPlist.strings
│   │   └── main.m
│   └── ClassyExample.xcodeproj/
│       ├── project.pbxproj
│       └── xcshareddata/
│           └── xcschemes/
│               └── ClassyExample.xcscheme
├── LICENSE
├── Podfile
├── README.md
├── Tests/
│   ├── ClassyTests-Info.plist
│   ├── ClassyTests-Prefix.pch
│   ├── ClassyTests.xcodeproj/
│   │   ├── project.pbxproj
│   │   ├── project.xcworkspace/
│   │   │   ├── contents.xcworkspacedata
│   │   │   └── xcshareddata/
│   │   │       └── ClassyTests.xccheckout
│   │   └── xcshareddata/
│   │       └── xcschemes/
│   │           └── ClassyTests.xcscheme
│   ├── ClassyTestsLoader/
│   │   ├── CASTestsAppDelegate.h
│   │   ├── CASTestsAppDelegate.m
│   │   ├── Classy-Prefix.pch
│   │   ├── ClassyTestsLoader-Info.plist
│   │   ├── Images.xcassets/
│   │   │   ├── AppIcon.appiconset/
│   │   │   │   └── Contents.json
│   │   │   └── LaunchImage.launchimage/
│   │   │       └── Contents.json
│   │   └── main.m
│   ├── Specs/
│   │   ├── CASExampleView.h
│   │   ├── CASExampleView.m
│   │   ├── CASExampleViewController.h
│   │   ├── CASExampleViewController.m
│   │   ├── CASSwizzler.h
│   │   ├── GcovTestObserver.m
│   │   ├── Integration Tests/
│   │   │   ├── CASCustomViewSpec.m
│   │   │   ├── CASUIAppearanceSpec.m
│   │   │   └── CASUIKitSpec.m
│   │   ├── UIDevice+CASMockDevice.h
│   │   ├── UIDevice+CASMockDevice.m
│   │   ├── Unit Tests/
│   │   │   ├── CASArgumentDescriptorSpec.m
│   │   │   ├── CASLexerSpec.m
│   │   │   ├── CASParserSpec.m
│   │   │   ├── CASRuntimeExtensionsSpec.m
│   │   │   ├── CASStylePropertySpec.m
│   │   │   ├── CASStyleSelectorSpec.m
│   │   │   ├── CASStylerSpec.m
│   │   │   ├── CASTokenSpec.m
│   │   │   └── UIView_CASAdditionsSpec.m
│   │   └── XCTest+Spec.h
│   ├── Stylesheets/
│   │   ├── CustomView-Basic.cas
│   │   ├── Import-1.cas
│   │   ├── Import-2.cas
│   │   ├── Import-Base.cas
│   │   ├── Injected-File.cas
│   │   ├── Precedence-1.cas
│   │   ├── Precedence-2.cas
│   │   ├── Properties-Args.cas
│   │   ├── Properties-Basic.cas
│   │   ├── Properties-Nested.cas
│   │   ├── Selectors-Complex.cas
│   │   ├── Selectors-Hierarchy.cas
│   │   ├── Selectors-Indentation.cas
│   │   ├── Selectors-Media-Queries-styler.cas
│   │   ├── Selectors-Media-Queries.cas
│   │   ├── Selectors-Messy.cas
│   │   ├── Selectors-Nested.cas
│   │   ├── UIAppearance-Basic.cas
│   │   ├── UIKit-Basic.cas
│   │   ├── UIKit-MultipleClasses.cas
│   │   ├── Variables-Basic.cas
│   │   └── Variables-Injection.cas
│   └── UIAppearance-setters.md
└── script/
    └── coveralls.sh
Download .txt
SYMBOL INDEX (5 symbols across 4 files)

FILE: Classy/Parser/CASToken.h
  type CASTokenTypeUnknown (line 11) | typedef NS_ENUM(NSInteger, CASTokenType) {

FILE: Classy/Reflection/CASArgumentDescriptor.h
  type CASPrimitiveTypeNone (line 14) | typedef NS_ENUM(NSUInteger, CASPrimitiveType) {

FILE: Classy/Reflection/CASRuntimeExtensions.h
  type cas_propertyMemoryManagementPolicy (line 24) | typedef enum {
  type cas_propertyAttributes (line 44) | typedef struct {

FILE: Tests/Specs/CASSwizzler.h
  function SwizzleClassMethod (line 12) | void SwizzleClassMethod(Class class, SEL orig, SEL new) {
Condensed preview — 161 files, each showing path, character count, and a content snippet. Download the .json file or copy for the full structured content (539K chars).
[
  {
    "path": ".gitignore",
    "chars": 383,
    "preview": "# Xcode\nbuild/*\n*.pbxuser\n!default.pbxuser\n*.mode1v3\n!default.mode1v3\n*.mode2v3\n!default.mode2v3\n*.perspectivev3\n!defaul"
  },
  {
    "path": ".travis.yml",
    "chars": 391,
    "preview": "language: objective-c\n\nbefore_install:\n  - sudo easy_install cpp-coveralls\n\nscript:\n  - pod install\n  - xctool -workspac"
  },
  {
    "path": "CHANGELOG.md",
    "chars": 2620,
    "preview": "v1.0.0\n======\n* Promoted v0.2.6 to v1.0.0 as basic functionaltiy is complete and functional.\n\nv0.2.6\n======\n\n* Updated l"
  },
  {
    "path": "CONTRIBUTING.md",
    "chars": 1475,
    "preview": "# Contributing to Classy\n\nLooking to contribute something to Classy? We would love to have you involved!\n\nTo make the pr"
  },
  {
    "path": "Classy/Additions/CASStyleClassUtilities.h",
    "chars": 760,
    "preview": "//\n//  CASStyleClassUtilities.h\n//  Pods\n//\n//  Created by Jonas Budelmann on 27/03/14.\n//\n//\n\n#import <Foundation/Found"
  },
  {
    "path": "Classy/Additions/CASStyleClassUtilities.m",
    "chars": 2428,
    "preview": "//\n//  CASStyleClassUtilities.m\n//  Pods\n//\n//  Created by Jonas Budelmann on 27/03/14.\n//\n//\n\n#import \"CASStyleClassUti"
  },
  {
    "path": "Classy/Additions/CASStyleableItem.h",
    "chars": 1901,
    "preview": "//\n//  CASStyleableItem.h\n//  \n//\n//  Created by Jonas Budelmann on 31/10/13.\n//\n//\n\n#import <Foundation/Foundation.h>\n\n"
  },
  {
    "path": "Classy/Additions/CASStyleableItem.m",
    "chars": 157,
    "preview": "//\n//  CASStyleableItem.h\n//\n//\n//  Created by Alexander Ney on 11/03/14.\n//\n//\n\n#import \"CASStyleableItem.h\"\n\nNSString "
  },
  {
    "path": "Classy/Additions/NSObject+CASSwizzle.h",
    "chars": 333,
    "preview": "//\n//  NSObject+CASSwizzle.h\n//  Classy\n//\n//  Created by Jonas Budelmann on 15/10/13.\n//  Copyright (c) 2013 cloudling."
  },
  {
    "path": "Classy/Additions/NSObject+CASSwizzle.m",
    "chars": 1105,
    "preview": "//\n//  NSObject+CASSwizzle.m\n//  Classy\n//\n//  Created by Jonas Budelmann on 15/10/13.\n//  Copyright (c) 2013 cloudling."
  },
  {
    "path": "Classy/Additions/NSRegularExpression+CASAdditions.h",
    "chars": 439,
    "preview": "//\n//  NSRegularExpression+CASAdditions.h\n//  Classy\n//\n//  Created by Jonas Budelmann on 17/09/13.\n//  Copyright (c) 20"
  },
  {
    "path": "Classy/Additions/NSRegularExpression+CASAdditions.m",
    "chars": 1036,
    "preview": "//\n//  NSRegularExpression+CASAdditions.m\n//  Classy\n//\n//  Created by Jonas Budelmann on 17/09/13.\n//  Copyright (c) 20"
  },
  {
    "path": "Classy/Additions/NSString+CASAdditions.h",
    "chars": 450,
    "preview": "//\n//  NSString+CASAdditions.h\n//  Classy\n//\n//  Created by Jonas Budelmann on 14/10/13.\n//  Copyright (c) 2013 cloudlin"
  },
  {
    "path": "Classy/Additions/NSString+CASAdditions.m",
    "chars": 1370,
    "preview": "//\n//  NSString+CASAdditions.m\n//  Classy\n//\n//  Created by Jonas Budelmann on 14/10/13.\n//  Copyright (c) 2013 cloudlin"
  },
  {
    "path": "Classy/Additions/UIBarItem+CASAdditions.h",
    "chars": 302,
    "preview": "//\n//  UIBarItem+CASAdditions.h\n//  \n//\n//  Created by Jonas Budelmann on 5/11/13.\n//\n//\n\n#import <UIKit/UIKit.h>\n#impor"
  },
  {
    "path": "Classy/Additions/UIBarItem+CASAdditions.m",
    "chars": 1867,
    "preview": "//\n//  UIBarItem+CASAdditions.m\n//  \n//\n//  Created by Jonas Budelmann on 5/11/13.\n//\n//\n\n#import \"UIBarItem+CASAddition"
  },
  {
    "path": "Classy/Additions/UIColor+CASAdditions.h",
    "chars": 1486,
    "preview": "//\n//  UIColor+CASAdditions.h\n//  Classy\n//\n//  Created by Jonas Budelmann on 16/09/13.\n//  Copyright (c) 2013 cloudling"
  },
  {
    "path": "Classy/Additions/UIColor+CASAdditions.m",
    "chars": 2778,
    "preview": "//\n//  UIColor+CASAdditions.m\n//  Classy\n//\n//  Created by Jonas Budelmann on 16/09/13.\n//  Copyright (c) 2013 cloudling"
  },
  {
    "path": "Classy/Additions/UINavigationBar+CASAdditions.h",
    "chars": 170,
    "preview": "//\n//  UINavigationBar+CASAdditions.h\n//  \n//\n//  Created by Jonas Budelmann on 31/10/13.\n//\n//\n\n#import <UIKit/UIKit.h>"
  },
  {
    "path": "Classy/Additions/UINavigationBar+CASAdditions.m",
    "chars": 786,
    "preview": "//\n//  UINavigationBar+CASAdditions.m\n//  \n//\n//  Created by Jonas Budelmann on 31/10/13.\n//\n//\n\n#import \"UINavigationBa"
  },
  {
    "path": "Classy/Additions/UINavigationItem+CASAdditions.h",
    "chars": 195,
    "preview": "//\n//  UINavigationItem+CASAdditions.h\n//  \n//\n//  Created by Joseph Ridenour on 1/21/15.\n//\n//\n\n#import <UIKit/UIKit.h>"
  },
  {
    "path": "Classy/Additions/UINavigationItem+CASAdditions.m",
    "chars": 1842,
    "preview": "//\n//  UINavigationItem+CASAdditions.m\n//  \n//\n//  Created by Joseph Ridenour on 1/21/15.\n//\n//\n\n#import \"UINavigationIt"
  },
  {
    "path": "Classy/Additions/UISlider+CASAdditions.h",
    "chars": 153,
    "preview": "//\n//  UISlider+CASAdditions.h\n//  \n//\n//  Created by Cail Borrell on 19/02/14.\n//\n//\n\n#import <UIKit/UIKit.h>\n\n@interfa"
  },
  {
    "path": "Classy/Additions/UISlider+CASAdditions.m",
    "chars": 226,
    "preview": "//\n//  UISlider+CASAdditions.m\n//  \n//\n//  Created by Cail Borrell on 19/02/14.\n//\n//\n\n#import \"UISlider+CASAdditions.h\""
  },
  {
    "path": "Classy/Additions/UITabBar+CASAdditions.h",
    "chars": 155,
    "preview": "//\n//  UITabBar+CASAdditions.h\n//  \n//\n//  Created by Jonas Budelmann on 1/11/13.\n//\n//\n\n#import <UIKit/UIKit.h>\n\n@inter"
  },
  {
    "path": "Classy/Additions/UITabBar+CASAdditions.m",
    "chars": 439,
    "preview": "//\n//  UITabBar+CASAdditions.m\n//  \n//\n//  Created by Jonas Budelmann on 1/11/13.\n//\n//\n\n#import \"UITabBar+CASAdditions."
  },
  {
    "path": "Classy/Additions/UITextField+CASAdditions.h",
    "chars": 308,
    "preview": "//\n//  UITextField+CASAdditions.h\n//  Classy\n//\n//  Created by Jonas Budelmann on 14/10/13.\n//  Copyright (c) 2013 cloud"
  },
  {
    "path": "Classy/Additions/UITextField+CASAdditions.m",
    "chars": 1387,
    "preview": "//\n//  UITextField+CASAdditions.m\n//  Classy\n//\n//  Created by Jonas Budelmann on 14/10/13.\n//  Copyright (c) 2013 cloud"
  },
  {
    "path": "Classy/Additions/UIToolbar+CASAdditions.h",
    "chars": 157,
    "preview": "//\n//  UIToolbar+CASAdditions.h\n//  \n//\n//  Created by Jonas Budelmann on 1/11/13.\n//\n//\n\n#import <UIKit/UIKit.h>\n\n@inte"
  },
  {
    "path": "Classy/Additions/UIToolbar+CASAdditions.m",
    "chars": 445,
    "preview": "//\n//  UIToolbar+CASAdditions.m\n//  \n//\n//  Created by Jonas Budelmann on 1/11/13.\n//\n//\n\n#import \"UIToolbar+CASAddition"
  },
  {
    "path": "Classy/Additions/UIView+CASAdditions.h",
    "chars": 483,
    "preview": "//\n//  UIView+CASAdditions.h\n//  Classy\n//\n//  Created by Jonas Budelmann on 30/09/13.\n//  Copyright (c) 2013 cloudling."
  },
  {
    "path": "Classy/Additions/UIView+CASAdditions.m",
    "chars": 2584,
    "preview": "//\n//  UIView+CASAdditions.m\n//  Classy\n//\n//  Created by Jonas Budelmann on 30/09/13.\n//  Copyright (c) 2013 cloudling."
  },
  {
    "path": "Classy/Additions/UIViewController+CASAdditions.h",
    "chars": 244,
    "preview": "//\n//  UIViewController+CASAdditions.h\n//  \n//\n//  Created by Jonas Budelmann on 17/11/13.\n//\n//\n\n#import <UIKit/UIKit.h"
  },
  {
    "path": "Classy/Additions/UIViewController+CASAdditions.m",
    "chars": 2490,
    "preview": "//\n//  UIViewController+CASAdditions.m\n//  \n//\n//  Created by Jonas Budelmann on 17/11/13.\n//\n//\n\n#import \"UIViewControl"
  },
  {
    "path": "Classy/Classy.h",
    "chars": 681,
    "preview": "//\n//  Classy.h\n//  Classy\n//\n//  Created by Jonas Budelmann on 16/09/13.\n//  Copyright (c) 2013 cloudling. All rights r"
  },
  {
    "path": "Classy/Parser/CASDeviceOSVersionItem.h",
    "chars": 383,
    "preview": "//\n//  CASDeviceOSVersionItem.h\n//  Classy\n//\n//  Created by Jonas Budelmann on 25/11/13.\n//  Copyright (c) 2013 cloudli"
  },
  {
    "path": "Classy/Parser/CASDeviceOSVersionItem.m",
    "chars": 1689,
    "preview": "//\n//  CASDeviceOSVersionItem.m\n//  Classy\n//\n//  Created by Jonas Budelmann on 25/11/13.\n//  Copyright (c) 2013 cloudli"
  },
  {
    "path": "Classy/Parser/CASDeviceScreenSizeItem.h",
    "chars": 459,
    "preview": "//\n//  Created by Ole Gammelgaard Poulsen on 05/04/14.\n//  Copyright (c) 2014 SHAPE A/S. All rights reserved.\n//\n\n#impor"
  },
  {
    "path": "Classy/Parser/CASDeviceScreenSizeItem.m",
    "chars": 2350,
    "preview": "//\n//  Created by Ole Gammelgaard Poulsen on 05/04/14.\n//  Copyright (c) 2014 SHAPE A/S. All rights reserved.\n//\n\n#impor"
  },
  {
    "path": "Classy/Parser/CASDeviceSelector.h",
    "chars": 854,
    "preview": "//\n//  CASStyleMediaSelector.h\n//  Classy\n//\n//  Created by Jonas Budelmann on 24/11/13.\n//  Copyright (c) 2013 cloudlin"
  },
  {
    "path": "Classy/Parser/CASDeviceSelector.m",
    "chars": 4442,
    "preview": "//\n//  CASStyleMediaSelector.m\n//  Classy\n//\n//  Created by Jonas Budelmann on 24/11/13.\n//  Copyright (c) 2013 cloudlin"
  },
  {
    "path": "Classy/Parser/CASDeviceSelectorItem.h",
    "chars": 536,
    "preview": "//\n//  CASDeviceSelectorItem.h\n//  Classy\n//\n//  Created by Jonas Budelmann on 25/11/13.\n//  Copyright (c) 2013 cloudlin"
  },
  {
    "path": "Classy/Parser/CASDeviceTypeItem.h",
    "chars": 433,
    "preview": "//\n//  CASDeviceTypeItem.h\n//  Classy\n//\n//  Created by Jonas Budelmann on 25/11/13.\n//  Copyright (c) 2013 cloudling. A"
  },
  {
    "path": "Classy/Parser/CASDeviceTypeItem.m",
    "chars": 990,
    "preview": "//\n//  CASDeviceTypeItem.m\n//  Classy\n//\n//  Created by Jonas Budelmann on 25/11/13.\n//  Copyright (c) 2013 cloudling. A"
  },
  {
    "path": "Classy/Parser/CASExpressionSolver.h",
    "chars": 483,
    "preview": "//\n//  CASExpressionSolver.h\n//  Classy\n//\n//  Created by Jonas Budelmann on 18/10/13.\n//  Copyright (c) 2013 cloudling."
  },
  {
    "path": "Classy/Parser/CASExpressionSolver.m",
    "chars": 7235,
    "preview": "//\n//  CASExpressionSolver.m\n//  Classy\n//\n//  Created by Jonas Budelmann on 18/10/13.\n//  Copyright (c) 2013 cloudling."
  },
  {
    "path": "Classy/Parser/CASInvocation.h",
    "chars": 676,
    "preview": "//\n//  CASInvocation.h\n//  \n//\n//  Created by Jonas Budelmann on 5/11/13.\n//\n//\n\n#import <Foundation/Foundation.h>\n\n@int"
  },
  {
    "path": "Classy/Parser/CASInvocation.m",
    "chars": 687,
    "preview": "//\n//  CASInvocation.m\n//  \n//\n//  Created by Jonas Budelmann on 5/11/13.\n//\n//\n\n#import \"CASInvocation.h\"\n\n@interface C"
  },
  {
    "path": "Classy/Parser/CASLexer.h",
    "chars": 1587,
    "preview": "//\n//  CASLexer.h\n//  Classy\n//\n//  Created by Jonas Budelmann on 15/09/13.\n//  Copyright (c) 2013 cloudling. All rights"
  },
  {
    "path": "Classy/Parser/CASLexer.m",
    "chars": 14673,
    "preview": "//\n//  CASLexer.m\n//  Classy\n//\n//  Created by Jonas Budelmann on 15/09/13.\n//  Copyright (c) 2013 cloudling. All rights"
  },
  {
    "path": "Classy/Parser/CASParser.h",
    "chars": 636,
    "preview": "//\n//  CASParser.h\n//  Classy\n//\n//  Created by Jonas Budelmann on 15/09/13.\n//  Copyright (c) 2013 cloudling. All right"
  },
  {
    "path": "Classy/Parser/CASParser.m",
    "chars": 30513,
    "preview": "//\n//  CASParser.m\n//  Classy\n//\n//  Created by Jonas Budelmann on 15/09/13.\n//  Copyright (c) 2013 cloudling. All right"
  },
  {
    "path": "Classy/Parser/CASStyleNode.h",
    "chars": 885,
    "preview": "//\n//  CASStyleGroup.h\n//  Classy\n//\n//  Created by Jonas Budelmann on 25/09/13.\n//  Copyright (c) 2013 cloudling. All r"
  },
  {
    "path": "Classy/Parser/CASStyleNode.m",
    "chars": 1382,
    "preview": "//\n//  CASStyleGroup.m\n//  Classy\n//\n//  Created by Jonas Budelmann on 25/09/13.\n//  Copyright (c) 2013 cloudling. All r"
  },
  {
    "path": "Classy/Parser/CASStyleProperty.h",
    "chars": 3564,
    "preview": "//\n//  MODStyleProperty.h\n//  Mod\n//\n//  Created by Jonas Budelmann on 25/09/13.\n//  Copyright (c) 2013 cloudling. All r"
  },
  {
    "path": "Classy/Parser/CASStyleProperty.m",
    "chars": 15525,
    "preview": "//\n//  CASStyleProperty.m\n//  Classy\n//\n//  Created by Jonas Budelmann on 25/09/13.\n//  Copyright (c) 2013 cloudling. Al"
  },
  {
    "path": "Classy/Parser/CASStyleSelector.h",
    "chars": 2085,
    "preview": "//\n//  CASStyleSelector.h\n//  Classy\n//\n//  Created by Jonas Budelmann on 29/09/13.\n//  Copyright (c) 2013 cloudling. Al"
  },
  {
    "path": "Classy/Parser/CASStyleSelector.m",
    "chars": 6388,
    "preview": "//\n//  CASStyleSelector.m\n//  Classy\n//\n//  Created by Jonas Budelmann on 29/09/13.\n//  Copyright (c) 2013 cloudling. Al"
  },
  {
    "path": "Classy/Parser/CASStyler.h",
    "chars": 2660,
    "preview": "//\n//  CASStyler.h\n//  Classy\n//\n//  Created by Jonas Budelmann on 16/09/13.\n//  Copyright (c) 2013 cloudling. All right"
  },
  {
    "path": "Classy/Parser/CASStyler.m",
    "chars": 39890,
    "preview": "\n//\n//  CASStyler.m\n//  Classy\n//\n//  Created by Jonas Budelmann on 16/09/13.\n//  Copyright (c) 2013 cloudling. All righ"
  },
  {
    "path": "Classy/Parser/CASTextAttributes.h",
    "chars": 1105,
    "preview": "//\n//  CASTextAttributes.h\n//  \n//\n//  Created by Jonas Budelmann on 4/11/13.\n//\n//\n\n#import <Foundation/Foundation.h>\n#"
  },
  {
    "path": "Classy/Parser/CASTextAttributes.m",
    "chars": 2211,
    "preview": "//\n//  CASTextAttributes.m\n//  \n//\n//  Created by Jonas Budelmann on 4/11/13.\n//\n//\n\n#import \"CASTextAttributes.h\"\n#impo"
  },
  {
    "path": "Classy/Parser/CASToken.h",
    "chars": 3354,
    "preview": "//\n//  CASToken.h\n//  Classy\n//\n//  Created by Jonas Budelmann on 16/09/13.\n//  Copyright (c) 2013 cloudling. All rights"
  },
  {
    "path": "Classy/Parser/CASToken.m",
    "chars": 4697,
    "preview": "//\n//  CASToken.m\n//  Classy\n//\n//  Created by Jonas Budelmann on 16/09/13.\n//  Copyright (c) 2013 cloudling. All rights"
  },
  {
    "path": "Classy/Parser/CASUnitToken.h",
    "chars": 297,
    "preview": "//\n//  CASUnitToken.h\n//  Classy\n//\n//  Created by Jonas Budelmann on 23/11/13.\n//  Copyright (c) 2013 cloudling. All ri"
  },
  {
    "path": "Classy/Parser/CASUnitToken.m",
    "chars": 281,
    "preview": "//\n//  CASUnitToken.m\n//  Classy\n//\n//  Created by Jonas Budelmann on 23/11/13.\n//  Copyright (c) 2013 cloudling. All ri"
  },
  {
    "path": "Classy/Parser/CASUtilities.h",
    "chars": 1031,
    "preview": "//\n//  CASUtilities.h\n//  \n//\n//  Created by Jonas Budelmann on 22/10/13.\n//\n//\n\n#import <Foundation/Foundation.h>\n\n// L"
  },
  {
    "path": "Classy/Parser/CASUtilities.m",
    "chars": 1737,
    "preview": "//\n//  CASUtilities.m\n//  \n//\n//  Created by Jonas Budelmann on 22/10/13.\n//\n//\n\n#import \"CASUtilities.h\"\n#import <UIKit"
  },
  {
    "path": "Classy/Reflection/CASArgumentDescriptor.h",
    "chars": 1239,
    "preview": "//\n//  CASArgumentDescriptor.h\n//  Classy\n//\n//  Created by Jonas Budelmann on 12/10/13.\n//  Copyright (c) 2013 cloudlin"
  },
  {
    "path": "Classy/Reflection/CASArgumentDescriptor.m",
    "chars": 3512,
    "preview": "//\n//  CASArgumentDescriptor.m\n//  Classy\n//\n//  Created by Jonas Budelmann on 12/10/13.\n//  Copyright (c) 2013 cloudlin"
  },
  {
    "path": "Classy/Reflection/CASAssociatedObjectsWeakWrapper.h",
    "chars": 1437,
    "preview": "//\n//  CASAssociatedObjectsWeakWrapper.h\n//  Pods\n//\n//  Created by apple on 14/11/20.\n//\n//\n\n#import <Foundation/Founda"
  },
  {
    "path": "Classy/Reflection/CASAssociatedObjectsWeakWrapper.m",
    "chars": 344,
    "preview": "//\n//  CASAssociatedObjectsWeakWrapper.m\n//  Pods\n//\n//  Created by apple on 14/11/20.\n//\n//\n\n#import \"CASAssociatedObje"
  },
  {
    "path": "Classy/Reflection/CASObjectClassDescriptor.h",
    "chars": 1016,
    "preview": "//\n//  CASObjectClassDescriptor.h\n//  Classy\n//\n//  Created by Jonas Budelmann on 30/09/13.\n//  Copyright (c) 2013 cloud"
  },
  {
    "path": "Classy/Reflection/CASObjectClassDescriptor.m",
    "chars": 4282,
    "preview": "//\n//  CASObjectClassDescriptor.m\n//  Classy\n//\n//  Created by Jonas Budelmann on 30/09/13.\n//  Copyright (c) 2013 cloud"
  },
  {
    "path": "Classy/Reflection/CASPropertyDescriptor.h",
    "chars": 637,
    "preview": "//\n//  CASPropertyDescriptor.h\n//  Classy\n//\n//  Created by Jonas Budelmann on 12/10/13.\n//  Copyright (c) 2013 cloudlin"
  },
  {
    "path": "Classy/Reflection/CASPropertyDescriptor.m",
    "chars": 1165,
    "preview": "//\n//  CASPropertyDescriptor.m\n//  Classy\n//\n//  Created by Jonas Budelmann on 12/10/13.\n//  Copyright (c) 2013 cloudlin"
  },
  {
    "path": "Classy/Reflection/CASRuntimeExtensions.h",
    "chars": 3296,
    "preview": "//\n//  CASRuntimeExtensions.h\n//  Classy\n//\n//  Created by Jonas Budelmann on 14/10/13.\n//  Renamespaced version of EXTR"
  },
  {
    "path": "Classy/Reflection/CASRuntimeExtensions.m",
    "chars": 7127,
    "preview": "//\n//  EXTRuntimeExtensions.m\n//  extobjc\n//\n//  Created by Justin Spahr-Summers on 2011-03-05.\n//  Copyright (C) 2012 J"
  },
  {
    "path": "Classy/Supporting Files/Info.plist",
    "chars": 806,
    "preview": "<?xml version=\"1.0\" encoding=\"UTF-8\"?>\n<!DOCTYPE plist PUBLIC \"-//Apple//DTD PLIST 1.0//EN\" \"http://www.apple.com/DTDs/P"
  },
  {
    "path": "Classy.podspec",
    "chars": 933,
    "preview": "Pod::Spec.new do |s|\n  s.name     = 'Classy'\n  s.version  = '1.0.0'\n  s.license  = 'MIT'\n  s.summary  = 'Expressive, fle"
  },
  {
    "path": "Classy.xcodeproj/project.pbxproj",
    "chars": 47388,
    "preview": "// !$*UTF8*$!\n{\n\tarchiveVersion = 1;\n\tclasses = {\n\t};\n\tobjectVersion = 46;\n\tobjects = {\n\n/* Begin PBXBuildFile section *"
  },
  {
    "path": "Classy.xcodeproj/xcshareddata/xcschemes/Classy.xcscheme",
    "chars": 2881,
    "preview": "<?xml version=\"1.0\" encoding=\"UTF-8\"?>\n<Scheme\n   LastUpgradeVersion = \"0900\"\n   version = \"1.3\">\n   <BuildAction\n      "
  },
  {
    "path": "Example/ClassyExample/CASAppDelegate.h",
    "chars": 298,
    "preview": "//\n//  CASAppDelegate.h\n//  ClassyExample\n//\n//  Created by Jonas Budelmann on 21/10/13.\n//  Copyright (c) 2013 Jonas Bu"
  },
  {
    "path": "Example/ClassyExample/CASAppDelegate.m",
    "chars": 1582,
    "preview": "//\n//  CASAppDelegate.m\n//  ClassyExample\n//\n//  Created by Jonas Budelmann on 21/10/13.\n//  Copyright (c) 2013 Jonas Bu"
  },
  {
    "path": "Example/ClassyExample/CASCatalogViewController.h",
    "chars": 250,
    "preview": "//\n//  CASCatalogViewController.h\n//  ClassyExample\n//\n//  Created by Jonas Budelmann on 21/10/13.\n//  Copyright (c) 201"
  },
  {
    "path": "Example/ClassyExample/CASCatalogViewController.m",
    "chars": 461,
    "preview": "//\n//  CASCatalogViewController.m\n//  ClassyExample\n//\n//  Created by Jonas Budelmann on 21/10/13.\n//  Copyright (c) 201"
  },
  {
    "path": "Example/ClassyExample/CASRootViewController.h",
    "chars": 259,
    "preview": "//\n//  CASRootViewController.h\n//  ClassyExample\n//\n//  Created by Jonas Budelmann on 21/10/13.\n//  Copyright (c) 2013 J"
  },
  {
    "path": "Example/ClassyExample/CASRootViewController.m",
    "chars": 1744,
    "preview": "//\n//  CASRootViewController.m\n//  ClassyExample\n//\n//  Created by Jonas Budelmann on 21/10/13.\n//  Copyright (c) 2013 J"
  },
  {
    "path": "Example/ClassyExample/CASSimpleFormViewController.h",
    "chars": 256,
    "preview": "//\n//  CASSimpleFormViewController.h\n//  ClassyExample\n//\n//  Created by Jonas Budelmann on 21/10/13.\n//  Copyright (c) "
  },
  {
    "path": "Example/ClassyExample/CASSimpleFormViewController.m",
    "chars": 1028,
    "preview": "//\n//  CASSimpleFormViewController.m\n//  ClassyExample\n//\n//  Created by Jonas Budelmann on 21/10/13.\n//  Copyright (c) "
  },
  {
    "path": "Example/ClassyExample/ClassyExample-Info.plist",
    "chars": 1441,
    "preview": "<?xml version=\"1.0\" encoding=\"UTF-8\"?>\n<!DOCTYPE plist PUBLIC \"-//Apple//DTD PLIST 1.0//EN\" \"http://www.apple.com/DTDs/P"
  },
  {
    "path": "Example/ClassyExample/ClassyExample-Prefix.pch",
    "chars": 344,
    "preview": "//\n//  Prefix header\n//\n//  The contents of this file are implicitly included at the beginning of every source file.\n//\n"
  },
  {
    "path": "Example/ClassyExample/Images.xcassets/AppIcon.appiconset/Contents.json",
    "chars": 825,
    "preview": "{\n  \"images\" : [\n    {\n      \"idiom\" : \"iphone\",\n      \"size\" : \"29x29\",\n      \"scale\" : \"2x\"\n    },\n    {\n      \"idiom\""
  },
  {
    "path": "Example/ClassyExample/Images.xcassets/LaunchImage.launchimage/Contents.json",
    "chars": 1100,
    "preview": "{\n  \"images\" : [\n    {\n      \"orientation\" : \"portrait\",\n      \"idiom\" : \"iphone\",\n      \"extent\" : \"full-screen\",\n     "
  },
  {
    "path": "Example/ClassyExample/Stylesheets/stylesheet.cas",
    "chars": 376,
    "preview": "//TODO flesh out example\n@import \"variables.cas\"\n\nUITableView UILabel {\n  text-color $main-color\n}\n\n^UIViewController > "
  },
  {
    "path": "Example/ClassyExample/Stylesheets/variables.cas",
    "chars": 17,
    "preview": "$main-color = red"
  },
  {
    "path": "Example/ClassyExample/en.lproj/InfoPlist.strings",
    "chars": 45,
    "preview": "/* Localized versions of Info.plist keys */\n\n"
  },
  {
    "path": "Example/ClassyExample/main.m",
    "chars": 358,
    "preview": "//\n//  main.m\n//  ClassyExample\n//\n//  Created by Jonas Budelmann on 21/10/13.\n//  Copyright (c) 2013 Jonas Budelmann. A"
  },
  {
    "path": "Example/ClassyExample.xcodeproj/project.pbxproj",
    "chars": 20874,
    "preview": "// !$*UTF8*$!\n{\n\tarchiveVersion = 1;\n\tclasses = {\n\t};\n\tobjectVersion = 46;\n\tobjects = {\n\n/* Begin PBXBuildFile section *"
  },
  {
    "path": "Example/ClassyExample.xcodeproj/xcshareddata/xcschemes/ClassyExample.xcscheme",
    "chars": 3863,
    "preview": "<?xml version=\"1.0\" encoding=\"UTF-8\"?>\n<Scheme\n   LastUpgradeVersion = \"0900\"\n   version = \"1.3\">\n   <BuildAction\n      "
  },
  {
    "path": "LICENSE",
    "chars": 1082,
    "preview": "The MIT License (MIT)\n\nCopyright (c) 2013 Jonas Budelmann\n\nPermission is hereby granted, free of charge, to any person o"
  },
  {
    "path": "Podfile",
    "chars": 1508,
    "preview": "workspace 'Classy'\n\nplatform :ios, '9.0'\n\nproject 'Example/ClassyExample'\n  \ntarget 'ClassyExample' do\n  pod 'Classy', :"
  },
  {
    "path": "README.md",
    "chars": 3234,
    "preview": "# Classy [![Build Status](https://travis-ci.org/cloudkite/Classy.svg?branch=master)](https://travis-ci.org/cloudkite/Cla"
  },
  {
    "path": "Tests/ClassyTests-Info.plist",
    "chars": 674,
    "preview": "<?xml version=\"1.0\" encoding=\"UTF-8\"?>\n<!DOCTYPE plist PUBLIC \"-//Apple//DTD PLIST 1.0//EN\" \"http://www.apple.com/DTDs/P"
  },
  {
    "path": "Tests/ClassyTests-Prefix.pch",
    "chars": 421,
    "preview": "//\n//  Prefix header\n//\n//  The contents of this file are implicitly included at the beginning of every source file.\n//\n"
  },
  {
    "path": "Tests/ClassyTests.xcodeproj/project.pbxproj",
    "chars": 47599,
    "preview": "// !$*UTF8*$!\n{\n\tarchiveVersion = 1;\n\tclasses = {\n\t};\n\tobjectVersion = 46;\n\tobjects = {\n\n/* Begin PBXBuildFile section *"
  },
  {
    "path": "Tests/ClassyTests.xcodeproj/project.xcworkspace/contents.xcworkspacedata",
    "chars": 156,
    "preview": "<?xml version=\"1.0\" encoding=\"UTF-8\"?>\n<Workspace\n   version = \"1.0\">\n   <FileRef\n      location = \"self:ClassyTests.xco"
  },
  {
    "path": "Tests/ClassyTests.xcodeproj/project.xcworkspace/xcshareddata/ClassyTests.xccheckout",
    "chars": 1496,
    "preview": "<?xml version=\"1.0\" encoding=\"UTF-8\"?>\n<!DOCTYPE plist PUBLIC \"-//Apple//DTD PLIST 1.0//EN\" \"http://www.apple.com/DTDs/P"
  },
  {
    "path": "Tests/ClassyTests.xcodeproj/xcshareddata/xcschemes/ClassyTests.xcscheme",
    "chars": 3871,
    "preview": "<?xml version=\"1.0\" encoding=\"UTF-8\"?>\n<Scheme\n   LastUpgradeVersion = \"0900\"\n   version = \"1.3\">\n   <BuildAction\n      "
  },
  {
    "path": "Tests/ClassyTestsLoader/CASTestsAppDelegate.h",
    "chars": 256,
    "preview": "//\n//  CASTestsAppDelegate.h\n//  ClassyTests\n//\n//  Created by Jonas Budelmann on 19/11/13.\n//  Copyright (c) 2013 Jonas"
  },
  {
    "path": "Tests/ClassyTestsLoader/CASTestsAppDelegate.m",
    "chars": 538,
    "preview": "//\n//  CASTestsAppDelegate.m\n//  ClassyTests\n//\n//  Created by Jonas Budelmann on 19/11/13.\n//  Copyright (c) 2013 Jonas"
  },
  {
    "path": "Tests/ClassyTestsLoader/Classy-Prefix.pch",
    "chars": 248,
    "preview": "//\n//  Prefix header\n//\n//  The contents of this file are implicitly included at the beginning of every source file.\n//\n"
  },
  {
    "path": "Tests/ClassyTestsLoader/ClassyTestsLoader-Info.plist",
    "chars": 1441,
    "preview": "<?xml version=\"1.0\" encoding=\"UTF-8\"?>\n<!DOCTYPE plist PUBLIC \"-//Apple//DTD PLIST 1.0//EN\" \"http://www.apple.com/DTDs/P"
  },
  {
    "path": "Tests/ClassyTestsLoader/Images.xcassets/AppIcon.appiconset/Contents.json",
    "chars": 825,
    "preview": "{\n  \"images\" : [\n    {\n      \"idiom\" : \"iphone\",\n      \"size\" : \"29x29\",\n      \"scale\" : \"2x\"\n    },\n    {\n      \"idiom\""
  },
  {
    "path": "Tests/ClassyTestsLoader/Images.xcassets/LaunchImage.launchimage/Contents.json",
    "chars": 1100,
    "preview": "{\n  \"images\" : [\n    {\n      \"orientation\" : \"portrait\",\n      \"idiom\" : \"iphone\",\n      \"extent\" : \"full-screen\",\n     "
  },
  {
    "path": "Tests/ClassyTestsLoader/main.m",
    "chars": 365,
    "preview": "//\n//  main.m\n//  ClassyTests\n//\n//  Created by Jonas Budelmann on 18/10/13.\n//  Copyright (c) 2013 Jonas Budelmann. All"
  },
  {
    "path": "Tests/Specs/CASExampleView.h",
    "chars": 459,
    "preview": "//\n//  CASExampleView.h\n//  Classy\n//\n//  Created by Jonas Budelmann on 11/10/13.\n//  Copyright (c) 2013 cloudling. All "
  },
  {
    "path": "Tests/Specs/CASExampleView.m",
    "chars": 206,
    "preview": "//\n//  CASExampleView.m\n//  Classy\n//\n//  Created by Jonas Budelmann on 11/10/13.\n//  Copyright (c) 2013 cloudling. All "
  },
  {
    "path": "Tests/Specs/CASExampleViewController.h",
    "chars": 248,
    "preview": "//\n//  CASExampleViewController.h\n//  ClassyTests\n//\n//  Created by Jonas Budelmann on 18/11/13.\n//  Copyright (c) 2013 "
  },
  {
    "path": "Tests/Specs/CASExampleViewController.m",
    "chars": 332,
    "preview": "//\n//  CASExampleViewController.m\n//  ClassyTests\n//\n//  Created by Jonas Budelmann on 18/11/13.\n//  Copyright (c) 2013 "
  },
  {
    "path": "Tests/Specs/CASSwizzler.h",
    "chars": 723,
    "preview": "//\n//  CASSwizzler.h\n//  ClassyTests\n//\n//  Created by Jonas Budelmann on 26/11/13.\n//  Copyright (c) 2013 Jonas Budelma"
  },
  {
    "path": "Tests/Specs/GcovTestObserver.m",
    "chars": 465,
    "preview": "//\n//  GcovTestObserver.m\n//  ClassyTests\n//\n//  Created by Jonas Budelmann on 19/11/13.\n//  Copyright (c) 2013 Jonas Bu"
  },
  {
    "path": "Tests/Specs/Integration Tests/CASCustomViewSpec.m",
    "chars": 794,
    "preview": "//\n//  CASCustomViewSpec.m\n//  ClassyTests\n//\n//  Created by Jonas Budelmann on 25/10/13.\n//  Copyright (c) 2013 Jonas B"
  },
  {
    "path": "Tests/Specs/Integration Tests/CASUIAppearanceSpec.m",
    "chars": 14553,
    "preview": "//\n//  CASUIAppearanceSpec.m\n//  ClassyTests\n//\n//  Created by Jonas Budelmann on 25/10/13.\n//  Copyright (c) 2013 Jonas"
  },
  {
    "path": "Tests/Specs/Integration Tests/CASUIKitSpec.m",
    "chars": 2779,
    "preview": "//\n//  CASUIKitSpec.m\n//  ClassyTests\n//\n//  Created by Jonas Budelmann on 25/10/13.\n//  Copyright (c) 2013 Jonas Budelm"
  },
  {
    "path": "Tests/Specs/UIDevice+CASMockDevice.h",
    "chars": 270,
    "preview": "//\n//  UIDevice+CASMockDevice.h\n//  ClassyTests\n//\n//  Created by Jonas Budelmann on 26/11/13.\n//  Copyright (c) 2013 Jo"
  },
  {
    "path": "Tests/Specs/UIDevice+CASMockDevice.m",
    "chars": 718,
    "preview": "//\n//  UIDevice+CASMockDevice.m\n//  ClassyTests\n//\n//  Created by Jonas Budelmann on 26/11/13.\n//  Copyright (c) 2013 Jo"
  },
  {
    "path": "Tests/Specs/Unit Tests/CASArgumentDescriptorSpec.m",
    "chars": 2661,
    "preview": "//\n//  CASArgumentDescriptorSpec.m\n//  Classy\n//\n//  Created by Jonas Budelmann on 16/10/13.\n//  Copyright (c) 2013 clou"
  },
  {
    "path": "Tests/Specs/Unit Tests/CASLexerSpec.m",
    "chars": 13019,
    "preview": "//\n//  CASLexerSpec.m\n//  Classy\n//\n//  Created by Jonas Budelmann on 17/09/13.\n//  Copyright (c) 2013 cloudling. All ri"
  },
  {
    "path": "Tests/Specs/Unit Tests/CASParserSpec.m",
    "chars": 28275,
    "preview": "//\n//  CASParserSpec.m\n//  Classy\n//\n//  Created by Jonas Budelmann on 15/09/13.\n//  Copyright (c) 2013 cloudling. All r"
  },
  {
    "path": "Tests/Specs/Unit Tests/CASRuntimeExtensionsSpec.m",
    "chars": 8370,
    "preview": "//\n//  CASRuntimeExtensionsSpec.m\n//  Classy\n//\n//  Created by Jonas Budelmann on 15/10/13.\n//  Copyright (c) 2013 cloud"
  },
  {
    "path": "Tests/Specs/Unit Tests/CASStylePropertySpec.m",
    "chars": 14457,
    "preview": "//\n//  CASStylePropertySpec.m\n//  Classy\n//\n//  Created by Jonas Budelmann on 16/10/13.\n//  Copyright (c) 2013 cloudling"
  },
  {
    "path": "Tests/Specs/Unit Tests/CASStyleSelectorSpec.m",
    "chars": 9179,
    "preview": "//\n//  CASStyleSelectorSpec.m\n//  ClassyTests\n//\n//  Created by Jonas Budelmann on 30/10/13.\n//  Copyright (c) 2013 Jona"
  },
  {
    "path": "Tests/Specs/Unit Tests/CASStylerSpec.m",
    "chars": 10848,
    "preview": "//\n//  CASRendererSpec.m\n//  Classy\n//\n//  Created by Jonas Budelmann on 16/09/13.\n//  Copyright (c) 2013 cloudling. All"
  },
  {
    "path": "Tests/Specs/Unit Tests/CASTokenSpec.m",
    "chars": 3045,
    "preview": "//\n//  CASTokenSpec.m\n//  Classy\n//\n//  Created by Jonas Budelmann on 24/09/13.\n//  Copyright (c) 2013 cloudling. All ri"
  },
  {
    "path": "Tests/Specs/Unit Tests/UIView_CASAdditionsSpec.m",
    "chars": 1310,
    "preview": "//\n//  UIView_CASAdditionsSpec.m\n//  ClassyTests\n//\n//  Created by Jonas Budelmann on 18/11/13.\n//  Copyright (c) 2013 J"
  },
  {
    "path": "Tests/Specs/XCTest+Spec.h",
    "chars": 417,
    "preview": "//\n//  Spec.h\n//  ClassyTests\n//\n//  Created by Jonas Budelmann on 18/10/13.\n//  Copyright (c) 2013 Jonas Budelmann. All"
  },
  {
    "path": "Tests/Stylesheets/CustomView-Basic.cas",
    "chars": 191,
    "preview": "// You can set properties on your custom UIView subclasses. No config required\n\nCASExampleView {\n  testCGFloat: 4.5;\n  t"
  },
  {
    "path": "Tests/Stylesheets/Import-1.cas",
    "chars": 205,
    "preview": "@import Import-2.cas;\n\n$var3 = 3\n$var4 = 4\n\nUIView.one {\n  background-color blue\n  \n  layer @{\n    corner-radius: $var5;"
  },
  {
    "path": "Tests/Stylesheets/Import-2.cas",
    "chars": 131,
    "preview": "$var5 = 5\n$var6 = 6\n\nUIView.two {\n  background-color purple\n  \n  layer @{\n    corner-radius: $var6;\n    shadow-offset: $"
  },
  {
    "path": "Tests/Stylesheets/Import-Base.cas",
    "chars": 182,
    "preview": "@import \"Import-1.cas\"\n\nUIView.base {\n  background-color red\n  layer @{\n    corner-radius: $var3;\n    shadow-offset: $va"
  },
  {
    "path": "Tests/Stylesheets/Injected-File.cas",
    "chars": 41,
    "preview": "UIView {\n  background-color $namedColor\n}"
  },
  {
    "path": "Tests/Stylesheets/Precedence-1.cas",
    "chars": 189,
    "preview": "UITextField.twenty {\n  text-insets: 20\n}\n\nUITextField {\n  text-insets: 10\n}\n\nUIView {\n  UITextField {\n    text-insets: 5"
  },
  {
    "path": "Tests/Stylesheets/Precedence-2.cas",
    "chars": 127,
    "preview": "^UIView {\n  UITextField {\n    text-insets: 5\n  }\n}\n\nUITextField {\n  text-insets: 10\n}\n\nUITextField.twenty {\n  text-inset"
  },
  {
    "path": "Tests/Stylesheets/Properties-Args.cas",
    "chars": 143,
    "preview": "UIButton {\n    background-color[state:selected] : #ffffff;\n    font-name [ state : highlighted ] helvetica\n    font-size"
  },
  {
    "path": "Tests/Stylesheets/Properties-Basic.cas",
    "chars": 514,
    "preview": "// [state:selected] has no effect at present\n// may allow setting property arguments for all properties in a selector at"
  },
  {
    "path": "Tests/Stylesheets/Properties-Nested.cas",
    "chars": 506,
    "preview": "//some properties have child properties. this is only used for text attributes at moment, but could be utilised for othe"
  },
  {
    "path": "Tests/Stylesheets/Selectors-Complex.cas",
    "chars": 560,
    "preview": "// [state:selected, coolness:alot] has no effect at present\n// may allow setting property arguments for all properties i"
  },
  {
    "path": "Tests/Stylesheets/Selectors-Hierarchy.cas",
    "chars": 657,
    "preview": "// `>` means select only if UIButton is direct superview.\n// leaving this out means UIButton can be an indirect supervie"
  },
  {
    "path": "Tests/Stylesheets/Selectors-Indentation.cas",
    "chars": 378,
    "preview": "// `{` and `}` are optional\n\nUIButton UIControl, UIButton UIImageView.starImage\n      background-color:#ffffff;\n      bo"
  },
  {
    "path": "Tests/Stylesheets/Selectors-Media-Queries-styler.cas",
    "chars": 227,
    "preview": "@device iphone {\n    UILabel.label2 {\n        @media (version: < 7.0) {\n            text-color: #0f0;\n        }\n        "
  },
  {
    "path": "Tests/Stylesheets/Selectors-Media-Queries.cas",
    "chars": 940,
    "preview": "// nesting! use indentation or { }\n\nUIButton\n  background-color1:#fff;\n  @media ipad, (version:>= 6)\n    background-colo"
  },
  {
    "path": "Tests/Stylesheets/Selectors-Messy.cas",
    "chars": 214,
    "preview": "// testing out messy formatting\n\nUIView {\nbackground-color:#fff\n}\nUIControl{\n    background-color:#ffffff\n    border-wid"
  },
  {
    "path": "Tests/Stylesheets/Selectors-Nested.cas",
    "chars": 527,
    "preview": "// nesting! use indentation or { }\n\nUIButton\n  background-color:#fff;\n  border-width 1\n  > UIControl\n    amazing 23\n  UI"
  },
  {
    "path": "Tests/Stylesheets/UIAppearance-Basic.cas",
    "chars": 5013,
    "preview": "// Support for all relevant UIAppearance setter methods\n// A few methods are not included\n// https://github.com/cloudkit"
  },
  {
    "path": "Tests/Stylesheets/UIKit-Basic.cas",
    "chars": 673,
    "preview": "// Support for common UIKit properties\n\n$border-width = 2;\n//look vars can be on same line\n$textInsets = 4 3 2 1; $defau"
  },
  {
    "path": "Tests/Stylesheets/UIKit-MultipleClasses.cas",
    "chars": 157,
    "preview": "UIView.class1 {\n    background-color: red;\n}\n\nUIView.class2 {\n    layer: @{\n        corner-radius: 2;\n    }\n}\n\nUIView.cl"
  },
  {
    "path": "Tests/Stylesheets/Variables-Basic.cas",
    "chars": 386,
    "preview": "$border-width = 2\n\n// vars delimited by ; or by newline\n$textInsets = 4 3 2 1; $defaultFontName = Avenir-Heavy;\n\n// semi"
  },
  {
    "path": "Tests/Stylesheets/Variables-Injection.cas",
    "chars": 133,
    "preview": "@import $filename\n\nUITextField {\n  text-color $hexColor\n  clearsOnBeginEditing $bool\n  text-insets $insets\n  minimumFont"
  },
  {
    "path": "Tests/UIAppearance-setters.md",
    "chars": 13324,
    "preview": "Extracted using technique described at https://gist.github.com/mattt/5135521\n\nThe information below was extracted from t"
  },
  {
    "path": "script/coveralls.sh",
    "chars": 488,
    "preview": "#!/bin/bash\n\nsource script/env.sh\ndeclare -r gcov_dir=\"${OBJECT_FILE_DIR_normal}/${CURRENT_ARCH}/\"\n\n## ======\n\ngenerateG"
  }
]

About this extraction

This page contains the full source code of the cloudkite/Classy GitHub repository, extracted and formatted as plain text for AI agents and large language models (LLMs). The extraction includes 161 files (492.9 KB), approximately 138.0k tokens, and a symbol index with 5 extracted functions, classes, methods, constants, and types. Use this with OpenClaw, Claude, ChatGPT, Cursor, Windsurf, or any other AI tool that accepts text input. You can copy the full output to your clipboard or download it as a .txt file.

Extracted by GitExtract — free GitHub repo to text converter for AI. Built by Nikandr Surkov.

Copied to clipboard!