AXElements/accessibility_core

View on GitHub
ext/accessibility/bridge/bridge.c

Summary

Maintainability
Test Coverage
#include "bridge.h"
#include "ruby/encoding.h"
#include "assert.h"

//For versions OS X < 10.11 use old constants
#ifndef MAC_OS_X_VERSION_10_11
#define    kAXValueTypeIllegal kAXValueIllegalType
#define kAXValueTypeCGPoint kAXValueCGPointType
#define kAXValueTypeCGSize kAXValueCGSizeType
#define kAXValueTypeCGRect kAXValueCGRectType
#define kAXValueTypeCFRange kAXValueCFRangeType
#define kAXValueTypeAXError kAXValueAXErrorType
#endif

void
spin(const double seconds)
{
  NSDate* const interval = [NSDate dateWithTimeIntervalSinceNow:seconds];
  [[NSRunLoop currentRunLoop] runUntilDate:interval];
  [interval release];
}


VALUE rb_cData;
VALUE rb_cAttributedString;
VALUE rb_mAccessibility;
VALUE rb_cElement;
VALUE rb_cCGPoint;
VALUE rb_cCGSize;
VALUE rb_cCGRect;
VALUE rb_mURI; // URI module
VALUE rb_cURI; // URI::Generic class
VALUE rb_cScreen;

ID sel_x;
ID sel_y;
ID sel_width;
ID sel_height;
ID sel_origin;
ID sel_size;
ID sel_to_point;
ID sel_to_size;
ID sel_to_rect;
ID sel_to_s;
ID sel_parse;


VALUE
wrap_unknown(CFTypeRef const obj)
{
    // TODO: this will leak...
    CFStringRef const description = CFCopyDescription(obj);

    rb_raise(rb_eRuntimeError,
             "accessibility-core doesn't know how to wrap `%s` objects yet",
             CFStringGetCStringPtr(description, kCFStringEncodingMacRoman));

    return Qnil; // unreachable
}

CFTypeRef
unwrap_unknown(const VALUE obj)
{
  // TODO: rb_check_convert_type instead?
  VALUE obj_string = rb_funcall(obj, sel_to_s, 0);
  rb_raise(rb_eRuntimeError,
       "accessibility-core doesn't know how to unwrap `%s'",
       StringValuePtr(obj_string));
  return NULL; // unreachable
}


VALUE
wrap_point(const CGPoint point)
{
  return rb_struct_new(rb_cCGPoint, DBL2NUM(point.x), DBL2NUM(point.y));
}

CGPoint
unwrap_point(const VALUE point)
{
    const  VALUE p = rb_funcall(point, sel_to_point, 0);
    const double x = NUM2DBL(rb_struct_getmember(p, sel_x));
    const double y = NUM2DBL(rb_struct_getmember(p, sel_y));
    return CGPointMake(x, y);
}


VALUE
wrap_size(const CGSize size)
{
  return rb_struct_new(rb_cCGSize, DBL2NUM(size.width), DBL2NUM(size.height));
}

CGSize
unwrap_size(const VALUE size)
{
    const  VALUE      s = rb_funcall(size, sel_to_size, 0);
    const double width  = NUM2DBL(rb_struct_getmember(s, sel_width));
    const double height = NUM2DBL(rb_struct_getmember(s, sel_height));
    return CGSizeMake(width, height);
}


VALUE
wrap_rect(const CGRect rect)
{
    const VALUE point = wrap_point(rect.origin);
    const VALUE  size = wrap_size(rect.size);
    return rb_struct_new(rb_cCGRect, point, size);
}


VALUE
coerce_to_rect(const VALUE obj)
{
  return rb_funcall(obj, sel_to_rect, 0);
}

CGRect
unwrap_rect(const VALUE rect)
{
    const   VALUE      r = rb_funcall(rect, sel_to_rect, 0);
    const CGPoint origin = unwrap_point(rb_struct_getmember(r, sel_origin));
    const CGSize    size = unwrap_size(rb_struct_getmember(r, sel_size));
    return CGRectMake(origin.x, origin.y, size.width, size.height);
}


VALUE
convert_cf_range(const CFRange range)
{
    const CFIndex end_index =
        range.location + (range.length ? range.length - 1 : 0);
    return rb_range_new(INT2FIX(range.location), INT2FIX(end_index), 0);
}

CFRange
convert_rb_range(const VALUE range)
{
    VALUE b, e;
    int exclusive;

    rb_range_values(range, &b, &e, &exclusive);

    const int begin = NUM2INT(b);
    const int   end = NUM2INT(e);

    if (begin < 0 || end < 0)
        // We don't know what the max length of the range will be, so we
        // can't count backwards.
        rb_raise(rb_eArgError,
                 "negative values are not allowed in ranges "
                 "that are converted to CFRange structures.");

    const int length = exclusive ? end-begin : end-begin + 1;
    return CFRangeMake(begin, length);
}


#define WRAP_VALUE(type, cookie, wrapper)                       \
    type st;                                                    \
    const Boolean result = AXValueGetValue(value, cookie, &st);    \
    assert(result);                                             \
    return wrapper(st);

VALUE wrap_value_point(AXValueRef const value) { WRAP_VALUE(CGPoint,  kAXValueTypeCGPoint, wrap_point) }
VALUE wrap_value_size(AXValueRef  const value) { WRAP_VALUE(CGSize,   kAXValueTypeCGSize,  wrap_size)  }
VALUE wrap_value_rect(AXValueRef  const value) { WRAP_VALUE(CGRect,   kAXValueTypeCGRect,  wrap_rect)  }
VALUE wrap_value_range(AXValueRef const value) { WRAP_VALUE(CFRange,  kAXValueTypeCFRange, convert_cf_range) }
VALUE wrap_value_error(AXValueRef const value) { WRAP_VALUE(AXError,  kAXValueTypeAXError, INT2NUM)    }

#define UNWRAP_VALUE(type, value, unwrapper)     \
    const type st = unwrapper(val);              \
    return AXValueCreate(value, &st);

AXValueRef unwrap_value_point(VALUE val) { UNWRAP_VALUE(CGPoint, kAXValueTypeCGPoint, unwrap_point) }
AXValueRef unwrap_value_size(VALUE val)  { UNWRAP_VALUE(CGSize,  kAXValueTypeCGSize,  unwrap_size)  }
AXValueRef unwrap_value_rect(VALUE val)  { UNWRAP_VALUE(CGRect,  kAXValueTypeCGRect,  unwrap_rect)  }
AXValueRef unwrap_value_range(VALUE val) { UNWRAP_VALUE(CFRange, kAXValueTypeCFRange, convert_rb_range) }

VALUE
wrap_value(AXValueRef const value)
{
    switch (AXValueGetType(value)) {
    case kAXValueTypeIllegal:
        rb_raise(rb_eArgError, "cannot wrap %s objects", rb_class2name(CLASS_OF(value)));
    case kAXValueTypeCGPoint:
        return wrap_value_point(value);
    case kAXValueTypeCGSize:
        return wrap_value_size(value);
    case kAXValueTypeCGRect:
        return wrap_value_rect(value);
    case kAXValueTypeCFRange:
        return wrap_value_range(value);
    case kAXValueTypeAXError:
        return wrap_value_error(value);
    default:
        // TODO better error message
        rb_raise(rb_eRuntimeError,
                 "Either accessibility_core is out of date or your system "
                 "has had a serious error");
    }

    return Qnil; // unreachable
}

AXValueRef
unwrap_value(const VALUE value)
{
    const VALUE type = CLASS_OF(value);
    if (type == rb_cCGPoint)
        return unwrap_value_point(value);
    else if (type == rb_cCGSize)
        return unwrap_value_size(value);
    else if (type == rb_cCGRect)
        return unwrap_value_rect(value);
    else if (type == rb_cRange)
        return unwrap_value_range(value);

    rb_raise(rb_eArgError, "could not wrap %s", rb_class2name(type));
    return NULL; // unreachable
}

VALUE wrap_array_values(CFArrayRef const array) { WRAP_ARRAY(wrap_value) }


VALUE wrap_ref(AXUIElementRef const obj) { WRAP_OBJC(rb_cElement, cf_finalizer); }

AXUIElementRef
unwrap_ref(const VALUE obj)
{
    AXUIElementRef* ref;
    Data_Get_Struct(obj, AXUIElementRef, ref);
    // TODO we should return *ref? but that seems to fuck things up...
    return (AXUIElementRef)ref;
}

VALUE wrap_array_refs(CFArrayRef const array) { WRAP_ARRAY(wrap_ref) }


VALUE
wrap_string(CFStringRef const string)
{
    CFDataRef const data =
        CFStringCreateExternalRepresentation(NULL,
                                             string,
                                             kCFStringEncodingUTF8,
                                             0);
    if (data) {
        const VALUE rb_str =
            rb_enc_str_new((char*)CFDataGetBytePtr(data),
                           CFDataGetLength(data),
                           rb_utf8_encoding());
        CFRelease(data);
        return rb_str;
    }

    CFRelease(data);
    CFShow(string); // uhh....why?
    rb_raise(rb_eRuntimeError, "Could not convert a string to a Ruby string");
    return Qnil;
}

VALUE
wrap_nsstring(NSString* const string)
{
  return wrap_string((CFStringRef)string);
}

CFStringRef
unwrap_string(const VALUE string)
{
    volatile VALUE str = string; // StringValue needs volatility guarantees :(
    return CFStringCreateWithBytes(NULL,
                                   (UInt8*)StringValueCStr(str),
                                   RSTRING_LEN(string),
                                   kCFStringEncodingUTF8,
                                   false);
}

NSString*
unwrap_nsstring(const VALUE string)
{
  return (NSString*)unwrap_string(string);
}

VALUE wrap_array_strings(CFArrayRef const array) { WRAP_ARRAY(wrap_string); }
VALUE wrap_array_nsstrings(NSArray* const ary)
{
  CFArrayRef const array = (CFArrayRef const)ary;
  WRAP_ARRAY(wrap_string);
}


VALUE
wrap_attributed_string(CFAttributedStringRef const string)
{
  return wrap_nsattributed_string((NSAttributedString* const)string);
}

VALUE
wrap_nsattributed_string(NSAttributedString* const obj)
{
  WRAP_OBJC(rb_cAttributedString, objc_finalizer);
}

CFAttributedStringRef
unwrap_attributed_string(const VALUE string)
{
  return (CFAttributedStringRef)unwrap_nsattributed_string(string);
}

NSAttributedString*
unwrap_nsattributed_string(const VALUE obj)
{
  UNWRAP_OBJC(NSAttributedString);
}

VALUE
wrap_array_attributed_strings(CFArrayRef const array)
{
  WRAP_ARRAY(wrap_attributed_string);
}

VALUE wrap_array_nsattributed_strings(NSArray* const ary)
{
  CFArrayRef const array = (CFArrayRef)ary;
  WRAP_ARRAY(wrap_nsattributed_string);
}

static
VALUE
rb_astring_alloc(const VALUE self)
{
  return wrap_nsattributed_string([NSAttributedString alloc]);
}

static
VALUE
rb_astring_init_with_string(const int argc, VALUE* const argv, const VALUE self)
{
    if (!argc)
        rb_raise(rb_eArgError, "wrong number of arguments (0 for 1+)");

    NSString* const              nsstring =
        unwrap_nsstring(argv[0]);
    NSAttributedString* const old_astring =
        unwrap_nsattributed_string(self);
    NSAttributedString* const new_astring =
        [old_astring initWithString:nsstring];
    [nsstring release];

    if (old_astring == new_astring)
        return self;
    return wrap_nsattributed_string(new_astring);
}

static
VALUE
rb_astring_string(const VALUE self)
{
    NSString* const string = [unwrap_nsattributed_string(self) string];
    const VALUE     rb_str = wrap_nsstring(string);
    return rb_str;
}

static
VALUE
rb_astring_length(const VALUE self)
{
  return ULONG2NUM([unwrap_nsattributed_string(self) length]);
}

static
VALUE
rb_astring_equality(const VALUE self, const VALUE other)
{
  OBJC_EQUALITY(rb_cAttributedString, unwrap_nsattributed_string);
}


#define WRAP_NUM(type, cookie, macro)                           \
    type value;                            \
    if (CFNumberGetValue(num, cookie, &value))            \
        return macro(value);                    \
    rb_raise(rb_eRuntimeError, "I goofed wrapping a number");    \
    return Qnil;

VALUE wrap_long(CFNumberRef const num)      { WRAP_NUM(long,      kCFNumberLongType,     LONG2FIX) }
VALUE wrap_long_long(CFNumberRef const num) { WRAP_NUM(long long, kCFNumberLongLongType, LL2NUM)   }
VALUE wrap_float(CFNumberRef const num)     { WRAP_NUM(double,    kCFNumberDoubleType,   DBL2NUM)  }

#define UNWRAP_NUM(type, cookie, macro)         \
    const type base = macro(num);        \
    return CFNumberCreate(NULL, cookie, &base);

CFNumberRef unwrap_long(const VALUE num)      { UNWRAP_NUM(long,      kCFNumberLongType,     NUM2LONG) }
CFNumberRef unwrap_long_long(const VALUE num) { UNWRAP_NUM(long long, kCFNumberLongLongType, NUM2LL)   }
CFNumberRef unwrap_float(const VALUE num)     { UNWRAP_NUM(double,    kCFNumberDoubleType,   NUM2DBL)  }

VALUE
wrap_number(CFNumberRef const number)
{
  switch (CFNumberGetType(number))
    {
    case kCFNumberSInt8Type:
    case kCFNumberSInt16Type:
    case kCFNumberSInt32Type:
    case kCFNumberSInt64Type:
      return wrap_long(number);
    case kCFNumberFloat32Type:
    case kCFNumberFloat64Type:
      return wrap_float(number);
    case kCFNumberCharType:
    case kCFNumberShortType:
    case kCFNumberIntType:
    case kCFNumberLongType:
      return wrap_long(number);
    case kCFNumberLongLongType:
      return wrap_long_long(number);
    case kCFNumberFloatType:
    case kCFNumberDoubleType:
      return wrap_float(number);
    case kCFNumberCFIndexType:
    case kCFNumberNSIntegerType:
      return wrap_long(number);
    case kCFNumberCGFloatType: // == kCFNumberMaxType
      return wrap_float(number);
    default:
      return INT2NUM(0); // unreachable unless system goofed
    }
}

CFNumberRef
unwrap_number(const VALUE number)
{
  switch (TYPE(number))
    {
    case T_FIXNUM:
      return unwrap_long(number);
    case T_FLOAT:
      return unwrap_float(number);
    default: {
        volatile VALUE num = number;
        rb_raise(rb_eRuntimeError,
                 "wrapping %s is not supported; log a bug?",
                 rb_string_value_cstr(&num));
        return kCFNumberNegativeInfinity; // unreachable
    }
    }
}

VALUE wrap_array_numbers(CFArrayRef const array) { WRAP_ARRAY(wrap_number) }



VALUE
wrap_url(CFURLRef const url)
{
  // @note CFURLGetString does not need to be CFReleased since it is a Get
  return rb_funcall(rb_mURI, sel_parse, 1, wrap_string(CFURLGetString(url)));
}

VALUE
wrap_nsurl(NSURL* const url)
{
  NSString* const str = [url absoluteString];
  const VALUE  rb_str = wrap_nsstring(str);
  [str release];
  return rb_funcall(rb_mURI, sel_parse, 1, rb_str);
}

CFURLRef
unwrap_url(const VALUE url)
{
  // TODO: should also force encoding to UTF-8 first?
    VALUE url_string = rb_funcall(url, sel_to_s, 0);
    CFStringRef const string =
        CFStringCreateWithCString(NULL,
                                  StringValuePtr(url_string),
                                  kCFStringEncodingUTF8);
    CFURLRef const url_ref = CFURLCreateWithString(NULL, string, NULL);
    CFRelease(string);
    return url_ref;
}

NSURL*
unwrap_nsurl(const VALUE url)
{
  return (NSURL*)unwrap_url(url);
}

VALUE wrap_array_urls(CFArrayRef const array) { WRAP_ARRAY(wrap_url) }


VALUE
wrap_date(CFDateRef const date)
{
    const NSTimeInterval time = [(NSDate*)date timeIntervalSince1970];
    return rb_time_new((time_t)time, 0);
}

VALUE
wrap_nsdate(NSDate* const date)
{
  return wrap_date((CFDateRef)date);
}

CFDateRef
unwrap_date(const VALUE date)
{
    const struct timeval t = rb_time_timeval(date);
    NSDate* const ns_date = [NSDate dateWithTimeIntervalSince1970:t.tv_sec];
    return (CFDateRef)ns_date;
}

VALUE wrap_array_dates(CFArrayRef const array) { WRAP_ARRAY(wrap_date) }


VALUE
wrap_boolean(CFBooleanRef const bool_val)
{
  return (CFBooleanGetValue(bool_val) ? Qtrue : Qfalse);
}

CFBooleanRef
unwrap_boolean(const VALUE bool_val)
{
  return (bool_val == Qtrue ? kCFBooleanTrue : kCFBooleanFalse);
}

VALUE wrap_array_booleans(CFArrayRef const array) { WRAP_ARRAY(wrap_boolean) }


VALUE
wrap_array(CFArrayRef const array)
{
    const CFIndex length = CFArrayGetCount(array);
    if (length) {
        const CFTypeRef obj = CFArrayGetValueAtIndex(array, 0);
        const CFTypeID   di = CFGetTypeID(obj);
        if      (di == AXUIElementGetTypeID())  return wrap_array_refs(array);
        else if (di == AXValueGetTypeID())      return wrap_array_values(array);
        else if (di == CFStringGetTypeID())     return wrap_array_strings(array);
        else if (di == CFNumberGetTypeID())     return wrap_array_numbers(array);
        else if (di == CFBooleanGetTypeID())    return wrap_array_booleans(array);
        else if (di == CFURLGetTypeID())        return wrap_array_urls(array);
        else if (di == CFDateGetTypeID())       return wrap_array_dates(array);
        else if (di == CFDictionaryGetTypeID()) return wrap_array_dictionaries(array);
        else                                    return wrap_unknown(obj);
    }
    return rb_ary_new();
}


VALUE
wrap_dictionary(NSDictionary* const dict)
{
    const VALUE hash = rb_hash_new();

    [dict enumerateKeysAndObjectsUsingBlock:
     ^(const id key, const id obj, BOOL* const stop) {
            rb_hash_aset(hash, to_ruby(key), to_ruby(obj));
        }];

  return hash;
}

VALUE wrap_array_dictionaries(CFArrayRef const array) { WRAP_ARRAY(wrap_dictionary); }


VALUE
to_ruby(CFTypeRef const obj)
{
    const CFTypeID di = CFGetTypeID(obj);
    if      (di == CFArrayGetTypeID())            return wrap_array(obj);
    else if (di == AXUIElementGetTypeID())        return wrap_ref(obj);
    else if (di == AXValueGetTypeID())            return wrap_value(obj);
    else if (di == CFStringGetTypeID())           return wrap_string(obj);
    else if (di == CFNumberGetTypeID())           return wrap_number(obj);
    else if (di == CFBooleanGetTypeID())          return wrap_boolean(obj);
    else if (di == CFURLGetTypeID())              return wrap_url(obj);
    else if (di == CFDateGetTypeID())             return wrap_date(obj);
    else if (di == CFDataGetTypeID())             return wrap_data(obj);
    else if (di == CFAttributedStringGetTypeID()) return wrap_attributed_string(obj);
    else if (di == CFDictionaryGetTypeID())       return wrap_dictionary(obj);
    else                                          return wrap_unknown(obj);
}

CFTypeRef
to_ax(const VALUE obj)
{
    switch (TYPE(obj)) {
    case T_STRING:
        return unwrap_string(obj);
    case T_FIXNUM:
        return unwrap_number(obj);
    case T_STRUCT:
        return unwrap_value(obj);
    case T_FLOAT:
        return unwrap_number(obj);
    case T_TRUE:
        return kCFBooleanTrue;
    case T_FALSE:
        return kCFBooleanFalse;
    }

    const VALUE type = CLASS_OF(obj);
    if (type == rb_cTime)
        return unwrap_date(obj);
    else if (type == rb_cRange)
        return unwrap_value_range(obj);
    else if (type == rb_cAttributedString)
        return unwrap_attributed_string(obj);
    else if (type == rb_cData)
        return unwrap_data(obj);

    if (rb_obj_is_kind_of(obj, rb_cURI))
        return unwrap_url(obj);

    // give up if we get this far
    return unwrap_unknown(obj);
}


VALUE
wrap_data(CFDataRef const data)
{
  return wrap_nsdata((NSData*)data);
}

VALUE
wrap_nsdata(NSData* const obj)
{
  WRAP_OBJC(rb_cData, objc_finalizer);
}

VALUE wrap_array_data(CFArrayRef const array) { WRAP_ARRAY(wrap_data); }
VALUE
wrap_array_nsdata(NSArray* const ary)
{
  CFArrayRef const array = (CFArrayRef)ary;
  WRAP_ARRAY(wrap_data);
}

CFDataRef
unwrap_data(const VALUE data)
{
  return (CFDataRef)unwrap_nsdata(data);
}

NSData*
unwrap_nsdata(const VALUE obj)
{
  UNWRAP_OBJC(NSData);
}


static
VALUE
rb_data_data(const VALUE self)
{
  return wrap_nsdata([NSData data]);
}

static
VALUE
rb_data_with_contents_of_url(const VALUE self, const VALUE url)
{
    NSData* const data = [NSData dataWithContentsOfURL:unwrap_nsurl(url)];
    if (data)
        return wrap_nsdata(data);
    return Qnil;
}

static
VALUE
rb_data_length(const VALUE self)
{
  return ULONG2NUM([unwrap_nsdata(self) length]);
}

static
VALUE
rb_data_equality(const VALUE self, const VALUE other)
{
  OBJC_EQUALITY(rb_cData, unwrap_nsdata);
}

static
VALUE
rb_data_write_to_file(const int argc, VALUE* const argv, const VALUE self)
{
    if (argc < 2)
        rb_raise(rb_eArgError,
                 "wrong number of arguments, got %d, expected 2",
                 argc);

    NSString* const path = unwrap_nsstring(argv[0]);
    const BOOL    result = [unwrap_nsdata(self) writeToFile:path
                                                 atomically:(argv[1] == Qtrue)];

    [path release];

    return (result ? Qtrue : Qfalse);
}


static
VALUE
rb_data_to_str(const VALUE self)
{
    NSData* const      data = unwrap_nsdata(self);
    const void* const bytes = [data bytes];
    const NSUInteger length = [data length];
    return rb_enc_str_new(bytes, length, rb_utf8_encoding());
}

static
VALUE
rb_str_to_data(const VALUE self)
{
    VALUE self_string = self;
    NSData* const data = [NSData dataWithBytes:(void*)StringValuePtr(self_string)
                                        length:RSTRING_LEN(self)];
    if (data)
        return wrap_nsdata(data);
    return Qnil; // I don't think this is possible except in case of ENOMEM
}


static VALUE
rb_spin(const int argc, VALUE* const argv, const VALUE self)
{
    if (argc == 0)
        spin(0);
    else
        spin(NUM2DBL(argv[0]));

    return self;
}


void
Init_bridge()
{
    sel_x        = rb_intern("x");
    sel_y        = rb_intern("y");
    sel_width    = rb_intern("width");
    sel_height   = rb_intern("height");
    sel_origin   = rb_intern("origin");
    sel_size     = rb_intern("size");
    sel_to_point = rb_intern("to_point");
    sel_to_size  = rb_intern("to_size");
    sel_to_rect  = rb_intern("to_rect");
    sel_to_s     = rb_intern("to_s");
    sel_parse    = rb_intern("parse");

    rb_mAccessibility = rb_define_module("Accessibility");
    rb_cElement       = rb_define_class_under(rb_mAccessibility, "Element", rb_cObject);
    rb_cCGPoint       = rb_const_get(rb_cObject, rb_intern("CGPoint"));
    rb_cCGSize        = rb_const_get(rb_cObject, rb_intern("CGSize"));
    rb_cCGRect        = rb_const_get(rb_cObject, rb_intern("CGRect"));
    rb_mURI           = rb_const_get(rb_cObject, rb_intern("URI"));
    rb_cURI           = rb_const_get(rb_mURI, rb_intern("Generic"));


    /*
     * Document-class: NSAttributedString
     *
     * A 90% drop-in replacement for Cocoa's `NSAttributedString` class. The most likely
     * to use methods have been bridged. Remaining methods can be bridged upon request.
     *
     * See [Apple's Developer Reference](https://developer.apple.com/library/mac/#documentation/Cocoa/Reference/Foundation/Classes/NSAttributedString_Class/Reference/Reference.html)
     * for documentation on the methods available in this class.
     */
    rb_cAttributedString = rb_define_class("NSAttributedString", rb_cObject);

    // LOL, but required to be a drop-in replacement
    rb_define_singleton_method(rb_cAttributedString, "alloc", rb_astring_alloc, 0);

    // TODO: all these methods :(
    rb_define_method(rb_cAttributedString, "initWithString", rb_astring_init_with_string, -1);
    /* rb_define_method(rb_cAttributedString, "initWithAttributedString", rb_astring_init_with_astring, 2); */
    rb_define_method(rb_cAttributedString, "string", rb_astring_string, 0);
    rb_define_method(rb_cAttributedString, "length", rb_astring_length, 0);
    /* rb_define_method(rb_cAttributedString, "attributesAtIndex", rb_astring_attrs_at_index, -1); */
    rb_define_method(rb_cAttributedString, "isEqualToAttributedString", rb_astring_equality, 1);
    /* rb_define_method(rb_cAttributedString, "attributedSubstringFromRange", rb_astring_astring_from_range, 1); */

    rb_define_alias(rb_cAttributedString, "to_s",   "string");
    rb_define_alias(rb_cAttributedString, "to_str", "string");
    rb_define_alias(rb_cAttributedString, "==",     "isEqualToAttributedString");


    /*
     * Document-class: NSData
     *
     * A 50% drop-in replacement for Cocoa's `NSData` class. Almost all
     * non-deprecated methods have been bridged.
     *
     * See [Apple's Developer Reference](https://developer.apple.com/library/mac/#documentation/Cocoa/Reference/Foundation/Classes/NSData_Class/Reference/Reference.html)
     * for documentation on the methods available in this class.
     */
    rb_cData = rb_define_class("NSData", rb_cObject);

    // TODO: implement commented out methods
    rb_define_singleton_method(rb_cData, "data", rb_data_data, 0);
    //rb_define_singleton_method(rb_cData, "dataWithBytes", rb_data_with_bytes, 2);
    //rb_define_singleton_method(rb_cData, "dataWithContentsOfFile", rb_data_with_contents_of_file, 1);
    rb_define_singleton_method(rb_cData, "dataWithContentsOfURL", rb_data_with_contents_of_url, 1);
    //rb_define_singleton_method(rb_cData, "dataWithData", rb_data_with_data, 1);

    //rb_define_method(rb_cData, "bytes",            rb_data_bytes, 0);
    //rb_define_method(rb_cData, "description",      rb_data_description, 0);
    //rb_define_method(rb_cData, "subdataWithRange", rb_data_subrange, 1);
    rb_define_method(rb_cData, "isEqualToData",    rb_data_equality, 1);
    rb_define_method(rb_cData, "length",           rb_data_length, 0);
    rb_define_method(rb_cData, "writeToFile",      rb_data_write_to_file, -1);
    //rb_define_method(rb_cData, "writeToURL",       rb_data_write_to_url, -1);
    rb_define_method(rb_cData, "to_str",           rb_data_to_str, 0);

    rb_define_alias(rb_cData,  "==", "isEqualToData");


    // misc freedom patches
    rb_define_method(rb_cString, "to_data", rb_str_to_data, 0);
    rb_define_method(rb_cObject, "spin", rb_spin, -1); // semi-private method
}