motion-addressbook 0.1.1 → 0.1.2

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
data/.gitignore CHANGED
@@ -3,8 +3,7 @@ build
3
3
  resources/*.nib
4
4
  resources/*.momd
5
5
  resources/*.storyboardc
6
-
7
- resources/photos/[1-9]*/*
6
+ Gemfile.lock
8
7
 
9
8
  vendor/Pods/Pods.xcodeproj/project.pbxproj
10
9
  vendor/Pods/build*
data/README.md CHANGED
@@ -20,6 +20,62 @@ Or install it yourself as:
20
20
 
21
21
  ## Usage
22
22
 
23
+ ### Requesting access
24
+
25
+ iOS 6 requires asking the user for permission before it allows an app to access the AddressBook. There are 3 ways to interact with this
26
+
27
+ 1 - Let the gem take care of it for you
28
+
29
+ ```ruby
30
+ people = AddressBook::Person.all
31
+ # A dialog may be presented to the user before "people" was returned
32
+ ```
33
+
34
+ 2 - Manually decide when to ask the user for authorization
35
+
36
+ ```ruby
37
+ # asking whether we are already authorized
38
+ if AddressBook.authorized?
39
+ puts "This app is authorized?"
40
+ else
41
+ puts "This app is not authorized?"
42
+ end
43
+
44
+ # ask the user to authorize us
45
+ if AddressBook.authorize
46
+ # do something now that the user has said "yes"
47
+ else
48
+ # do something now that the user has said "no"
49
+ end
50
+ ```
51
+
52
+ 3 - Manually ask the user but do it asynchronously (this is how Apple's API works)
53
+
54
+ ```ruby
55
+ # ask the user to authorize us
56
+ if AddressBook.authorize do |granted|
57
+ # this block is invoked sometime later
58
+ if granted
59
+ # do something now that the user has said "yes"
60
+ else
61
+ # do something now that the user has said "no"
62
+ end
63
+ end
64
+ # do something here before the user has decided
65
+ ```
66
+
67
+ ### Showing the ABPeoplePicker
68
+
69
+ ```ruby
70
+ AddressBook.pick { |person|
71
+ if person
72
+ # person is an AddressBook::Person object
73
+ else
74
+ # canceled
75
+ end
76
+ }
77
+ ```
78
+
23
79
  ### Instantiating a person object
24
80
 
25
81
  There are 3 ways to instantiate a person object
data/Rakefile CHANGED
@@ -5,4 +5,4 @@ require "bundler/gem_tasks"
5
5
  Bundler.setup
6
6
  Bundler.require
7
7
 
8
- require 'bubble-wrap/test'
8
+ require 'bubble-wrap/test'
@@ -1,5 +1,5 @@
1
1
  module Motion
2
2
  module Addressbook
3
- VERSION = "0.1.1"
3
+ VERSION = "0.1.2"
4
4
  end
5
5
  end
@@ -1,5 +1,10 @@
1
1
  require "motion-addressbook/version"
2
2
 
3
- BubbleWrap.require 'motion/address_book.rb'
3
+ BubbleWrap.require 'motion/address_book.rb' do
4
+ file('motion/address_book.rb').uses_framework('AddressBook')
5
+ end
4
6
  BW.require 'motion/address_book/multi_value.rb'
5
7
  BW.require 'motion/address_book/person.rb'
8
+ BW.require 'motion/address_book/picker.rb' do
9
+ file('motion/address_book/picker.rb').uses_framework('AddressBookUI')
10
+ end
@@ -17,14 +17,14 @@ module AddressBook
17
17
  def attribute_map
18
18
  self.class.attribute_map
19
19
  end
20
-
20
+
21
21
  def alex
22
22
  ABMultiValueGetIdentifierAtIndex @ab_multi_values, 0
23
23
  end
24
24
 
25
25
  def initialize(attributes={}, existing_ab_multi_values=nil)
26
26
  @attributes = {}
27
- if existing_ab_multi_values
27
+ if existing_ab_multi_values
28
28
  @ab_multi_values = ABMultiValueCreateMutableCopy(existing_ab_multi_values)
29
29
  load_attributes_from_ab
30
30
  else
@@ -49,7 +49,7 @@ module AddressBook
49
49
  return false if attribute_name.nil?
50
50
  attribute_map.include?(attribute_name.to_sym) || [:email, :phone_number].include?( attribute_name.to_sym)
51
51
  end
52
-
52
+
53
53
  def getter?(method_name)
54
54
  if self.class.is_attribute? method_name
55
55
  method_name
@@ -69,7 +69,7 @@ module AddressBook
69
69
  def get(attribute_name)
70
70
  attributes[attribute_name.to_sym] ||= get_field(attribute_map[attribute_name])
71
71
  end
72
-
72
+
73
73
  def set(attribute_name, value)
74
74
  set_field(attribute_map[attribute_name.to_sym], value)
75
75
  attributes[attribute_name.to_sym] = value
@@ -117,4 +117,4 @@ module AddressBook
117
117
  end
118
118
 
119
119
  end
120
- end
120
+ end
@@ -14,10 +14,11 @@ module AddressBook
14
14
  end
15
15
 
16
16
  def self.all
17
- address_book = ABAddressBookCreate()
18
- ABAddressBookCopyArrayOfAllPeople(address_book).map do |ab_person|
17
+ people = ABAddressBookCopyArrayOfAllPeople(AddressBook.address_book).map do |ab_person|
19
18
  new({}, ab_person)
20
19
  end
20
+ people.sort! { |a,b| "#{a.first_name} #{a.last_name}" <=> "#{b.first_name} #{b.last_name}" }
21
+ people
21
22
  end
22
23
 
23
24
  def self.create(attributes)
@@ -32,13 +33,13 @@ module AddressBook
32
33
  @address_book = nil #force refresh
33
34
  @new_record = false
34
35
  end
35
-
36
+
36
37
  def self.where(conditions)
37
38
  all.select do |person|
38
39
  person.meets? conditions
39
40
  end
40
41
  end
41
-
42
+
42
43
  def meets?(conditions)
43
44
  conditions.keys.all? do |attribute|
44
45
  send(attribute) == conditions[attribute]
@@ -77,12 +78,12 @@ module AddressBook
77
78
  else
78
79
  super
79
80
  end
80
- end
81
+ end
81
82
  def self.is_attribute?(attribute_name)
82
83
  return false if attribute_name.nil?
83
84
  attribute_map.include?(attribute_name.to_sym) || [:email, :phone_number].include?( attribute_name.to_sym)
84
85
  end
85
-
86
+
86
87
  def getter?(method_name)
87
88
  if self.class.is_attribute? method_name
88
89
  method_name
@@ -122,16 +123,16 @@ module AddressBook
122
123
  nil
123
124
  end
124
125
  end
125
-
126
+
126
127
  def get(attribute_name)
127
128
  attributes[attribute_name.to_sym] ||= get_field(attribute_map[attribute_name])
128
129
  end
129
-
130
+
130
131
  def set(attribute_name, value)
131
132
  set_field(attribute_map[attribute_name.to_sym], value)
132
133
  attributes[attribute_name.to_sym] = value
133
134
  end
134
-
135
+
135
136
  def self.find_all_by(attribute_name, criteria)
136
137
  where({attribute_name.to_sym => criteria})
137
138
  end
@@ -141,7 +142,7 @@ module AddressBook
141
142
  def self.find_or_new_by(attribute_name, criteria)
142
143
  find_by(attribute_name, criteria) || new({attribute_name.to_sym => criteria})
143
144
  end
144
-
145
+
145
146
  def photo
146
147
  ABPersonCopyImageData(ab_person)
147
148
  end
@@ -166,7 +167,7 @@ module AddressBook
166
167
  def email_values
167
168
  emails.values
168
169
  end
169
-
170
+
170
171
  # UGH - kinda arbitrary way to deal with multiple values. DO SOMETHING BETTER.
171
172
  def email
172
173
  @attributes[:email] ||= email_values.first
@@ -247,8 +248,8 @@ module AddressBook
247
248
  end
248
249
 
249
250
  def address_book
250
- @address_book ||= ABAddressBookCreate()
251
+ @address_book ||= AddressBook.address_book
251
252
  end
252
253
 
253
254
  end
254
- end
255
+ end
@@ -0,0 +1,60 @@
1
+ module AddressBook
2
+ class Picker
3
+ class << self
4
+ attr_accessor :showing
5
+ end
6
+ def self.show(&after)
7
+ raise "Cannot show two Pickers" if showing?
8
+ @picker = Picker.new(&after)
9
+ @picker.show
10
+ @picker
11
+ end
12
+
13
+ def self.showing?
14
+ !!showing
15
+ end
16
+
17
+ def initialize(&after)
18
+ @after = after
19
+ end
20
+
21
+ def show
22
+ self.class.showing = true
23
+
24
+ @people_picker_ctlr = ABPeoplePickerNavigationController.alloc.init
25
+ @people_picker_ctlr.peoplePickerDelegate = self
26
+ UIApplication.sharedApplication.keyWindow.rootViewController.presentViewController(@people_picker_ctlr, animated:true, completion:nil)
27
+ end
28
+
29
+ def hide(ab_person=nil)
30
+ person = ab_person ? AddressBook::Person.new({}, ab_person) : nil
31
+
32
+ UIApplication.sharedApplication.keyWindow.rootViewController.dismissViewControllerAnimated(true, completion:lambda{
33
+ @after.call(person) if @after
34
+ self.class.showing = false
35
+ })
36
+ end
37
+
38
+ def peoplePickerNavigationController(people_picker, shouldContinueAfterSelectingPerson:ab_person)
39
+ hide(ab_person)
40
+ false
41
+ end
42
+
43
+ def peoplePickerNavigationController(people_picker, shouldContinueAfterSelectingPerson:ab_person, property:property, identifier:id)
44
+ hide(ab_person)
45
+ false
46
+ end
47
+
48
+ def peoplePickerNavigationControllerDidCancel(people_picker)
49
+ hide
50
+ end
51
+ end
52
+ end
53
+
54
+ module AddressBook
55
+ module_function
56
+ def pick(&after)
57
+ AddressBook::Picker.show &after
58
+ end
59
+ end
60
+
@@ -0,0 +1,60 @@
1
+ module AddressBook
2
+ module_function
3
+
4
+ def address_book
5
+ if UIDevice.currentDevice.systemVersion >= '6'
6
+ ios6_create
7
+ else
8
+ ios5_create
9
+ end
10
+ end
11
+
12
+ def ios6_create
13
+ error = nil
14
+ address_book = ABAddressBookCreateWithOptions(nil, error)
15
+ request_authorization unless authorized?
16
+ address_book
17
+ end
18
+
19
+ def ios5_create
20
+ ABAddressBookCreate()
21
+ end
22
+
23
+ def request_authorization(&block)
24
+ synchronous = !!block
25
+ access_callback = lambda { |granted, error|
26
+ # not sure what to do with error ... so we're ignoring it
27
+ @address_book_access_granted = granted
28
+ block.call(@address_book_access_granted) unless block.nil?
29
+ }
30
+
31
+ ABAddressBookRequestAccessWithCompletion address_book, access_callback
32
+ if synchronous
33
+ # Wait on the asynchronous callback before returning.
34
+ while @address_book_access_granted.nil? do
35
+ sleep 0.1
36
+ end
37
+ end
38
+ @address_book_access_granted
39
+ end
40
+
41
+ def authorized?
42
+ authorization_status == :authorized
43
+ end
44
+
45
+ def authorization_status
46
+ return :authorized unless UIDevice.currentDevice.systemVersion >= '6'
47
+
48
+ status_map = { KABAuthorizationStatusNotDetermined => :not_determined,
49
+ KABAuthorizationStatusRestricted => :restricted,
50
+ KABAuthorizationStatusDenied => :denied,
51
+ KABAuthorizationStatusAuthorized => :authorized
52
+ }
53
+ status_map[ABAddressBookGetAuthorizationStatus()]
54
+ end
55
+
56
+ def create_with_options_available?
57
+ error = nil
58
+ ABAddressBookCreateWithOptions(nil, error) rescue false
59
+ end
60
+ end
@@ -2,12 +2,12 @@ describe AddressBook::MultiValue do
2
2
  describe 'properties on a new multivalue' do
3
3
  describe 'initializing with values' do
4
4
  before do
5
- @attributes = { :mobile => '123-456-7890', :iphone => '222-333-4444', :main => '555-1212',
5
+ @attributes = { :mobile => '123-456-7890', :iphone => '222-333-4444', :main => '555-1212',
6
6
  :home_fax => '1-617-555-8000', :work_fax => '1-212-555-0000', :pager => '99-9999-9999',
7
7
  :work => 'alex@work.com', :home => 'alex@home.com', :other => 'alex@other.com'}
8
8
  @multi_value = AddressBook::MultiValue.new @attributes
9
9
  end
10
-
10
+
11
11
  it 'should be able to get each of the single value fields' do
12
12
  @multi_value.mobile.should.equal @attributes[:mobile ]
13
13
  @multi_value.iphone.should.equal @attributes[:iphone ]
@@ -19,7 +19,7 @@ describe AddressBook::MultiValue do
19
19
  @multi_value.home.should.equal @attributes[:home ]
20
20
  @multi_value.other.should.equal @attributes[:other ]
21
21
  end
22
-
22
+
23
23
  it 'should give all the values in a list' do
24
24
  @multi_value.values.should == ["123-456-7890", "222-333-4444", "555-1212", "1-617-555-8000", "1-212-555-0000", "99-9999-9999", "alex@work.com", "alex@home.com", "alex@other.com"]
25
25
  end
@@ -68,13 +68,13 @@ describe AddressBook::MultiValue do
68
68
  end
69
69
  end
70
70
  end
71
-
71
+
72
72
  describe 'an existing multivalue' do
73
73
  before do
74
74
  @first_multi_value = AddressBook::MultiValue.new :mobile => '123-456-7890', :iphone => '99-8888888-7777777-66'
75
75
  @multi_value = AddressBook::MultiValue.new({:mobile => '987654321', :home_fax => '777-6666-4444'}, @first_multi_value.ab_multi_values)
76
76
  end
77
-
77
+
78
78
  it 'should ' do
79
79
  @multi_value.mobile.should == '987654321'
80
80
  @multi_value.iphone.should == '99-8888888-7777777-66'
@@ -1,5 +1,4 @@
1
1
  describe AddressBook::Person do
2
-
3
2
  describe 'ways of creating and finding people' do
4
3
  describe 'new' do
5
4
  before do
@@ -12,7 +11,7 @@ describe AddressBook::Person do
12
11
  @alex.email_values.should == ['alex_testy@example.com']
13
12
  end
14
13
  end
15
-
14
+
16
15
  describe 'existing' do
17
16
  before do
18
17
  @email = unique_email
@@ -60,7 +59,7 @@ describe AddressBook::Person do
60
59
  alexes.should == []
61
60
  end
62
61
  end
63
-
62
+
64
63
  describe '.all' do
65
64
  it 'should have the person we created' do
66
65
  all_names = AddressBook::Person.all.map do |person|
@@ -68,13 +67,13 @@ describe AddressBook::Person do
68
67
  end
69
68
  all_names.should.include? [@alex.first_name, @alex.last_name]
70
69
  end
71
-
70
+
72
71
  it 'should get bigger when we create another' do
73
72
  initial_people_count = AddressBook::Person.all.size
74
73
  @person = AddressBook::Person.create({:first_name => 'Alex2', :last_name=>'Rothenberg2'})
75
74
  AddressBook::Person.all.size.should == (initial_people_count + 1)
76
75
  end
77
- end
76
+ end
78
77
  end
79
78
 
80
79
  describe '.find_or_new_by_XXX - new or existing' do
@@ -107,17 +106,17 @@ describe AddressBook::Person do
107
106
  :email => unique_email
108
107
  }
109
108
  end
110
-
109
+
111
110
  describe 'a new person' do
112
111
  before do
113
112
  @ab_person = AddressBook::Person.new(@attributes)
114
113
  end
115
-
114
+
116
115
  it 'should not be existing' do
117
116
  @ab_person.should.be.new_record
118
117
  @ab_person.should.not.be.exists
119
118
  end
120
-
119
+
121
120
  it 'should be able to get each of the single value fields' do
122
121
  @ab_person.first_name.should.equal @attributes[:first_name ]
123
122
  @ab_person.last_name.should.equal @attributes[:last_name ]
@@ -125,7 +124,7 @@ describe AddressBook::Person do
125
124
  @ab_person.department.should.equal @attributes[:department ]
126
125
  @ab_person.organization.should.equal @attributes[:organization]
127
126
  end
128
-
127
+
129
128
  describe 'setting each field' do
130
129
  it 'should be able to set the first name' do
131
130
  @ab_person.first_name = 'new first name'
@@ -147,7 +146,7 @@ describe AddressBook::Person do
147
146
  @ab_person.organization = 'new organization'
148
147
  @ab_person.organization.should.equal 'new organization'
149
148
  end
150
-
149
+
151
150
  it 'should be able to set the phot' do
152
151
  image = CIImage.emptyImage
153
152
  data = UIImagePNGRepresentation(UIImage.imageWithCIImage image)
@@ -155,11 +154,11 @@ describe AddressBook::Person do
155
154
  UIImagePNGRepresentation(@ab_person.photo).should.equal data
156
155
  end
157
156
  end
158
-
157
+
159
158
  it 'should be able to get the phone numbers' do
160
159
  @ab_person.phone_number_values.should.equal [@attributes[:mobile_phone], @attributes[:office_phone] ]
161
160
  end
162
-
161
+
163
162
  it 'should be able to get the emails' do
164
163
  @ab_person.email_values.should.equal [@attributes[:email] ]
165
164
  end
@@ -173,7 +172,7 @@ describe AddressBook::Person do
173
172
  end
174
173
  end
175
174
  end
176
-
175
+
177
176
  describe 'updating an existing person' do
178
177
  before do
179
178
  AddressBook::Person.new(@attributes).save
@@ -182,14 +181,14 @@ describe AddressBook::Person do
182
181
  @attributes[:department ] = nil
183
182
  @ab_person = AddressBook::Person.find_or_new_by_email(@attributes[:email])
184
183
  end
185
-
184
+
186
185
  it 'should know it is not new' do
187
186
  @ab_person.should.not.be.new_record
188
187
  @ab_person.should.be.exists
189
188
  @ab_person.first_name.should == 'Alex'
190
189
  @ab_person.department.should == 'Development'
191
190
  end
192
-
191
+
193
192
  describe 'updating' do
194
193
  it 'should be able to get each of the single value fields' do
195
194
  @ab_person.save
@@ -199,11 +198,11 @@ describe AddressBook::Person do
199
198
  AddressBook::Person.find_by_email(@ab_person.email).first_name.should == 'New First Name'
200
199
  end
201
200
  end
202
-
201
+
203
202
  end
204
-
203
+
205
204
  end
206
-
205
+
207
206
  describe 'method missing magic' do
208
207
  before do
209
208
  @person = AddressBook::Person.new
@@ -0,0 +1,34 @@
1
+ # I don't know how to test a class wrapping a controller like this yet
2
+ #
3
+ # describe AddressBook::Picker do
4
+ # describe 'IOS UI for finding people' do
5
+ # before do
6
+ # @selected_person = nil
7
+ # @picker = AddressBook.pick do |person|
8
+ # @selected_person = person
9
+ # end
10
+ # end
11
+
12
+ # it 'should yield the selected person' do
13
+ # ab_person = AddressBook::Person.new(first_name: 'Colin').ab_person
14
+ # @picker.peoplePickerNavigationController(@picker_nav_controller, shouldContinueAfterSelectingPerson: ab_person)
15
+ # @selected_person.should.not == nil
16
+ # @selected_person.first_name.should == 'Colin'
17
+ # end
18
+
19
+ # it 'should yield the selected person' do
20
+ # property = :some_property
21
+ # id = :some_id
22
+ # ab_person = AddressBook::Person.new(first_name: 'Colin').ab_person
23
+ # @picker.peoplePickerNavigationController(@picker_nav_controller, shouldContinueAfterSelectingPerson: ab_person, property:property, identifier:id)
24
+ # @selected_person.should.not == nil
25
+ # @selected_person.first_name.should == 'Colin'
26
+ # end
27
+
28
+ # it 'should yield nil when cancelled' do
29
+ # ab_person = AddressBook::Person.new(first_name: 'Colin').ab_person
30
+ # @picker.peoplePickerNavigationControllerDidCancel(@picker_nav_controller)
31
+ # @selected_person.should == nil
32
+ # end
33
+ # end
34
+ # end
@@ -1,3 +1,6 @@
1
- # The value of these constants is undefined until one of the following functions has been called: ABAddressBookCreate, ABPersonCreate, ABGroupCreate.
2
- # see https://developer.apple.com/library/ios/#documentation/AddressBook/Reference/ABPersonRef_iPhoneOS/Reference/reference.html and search for "Discussion"
3
- ABAddressBookCreate()
1
+ # The value of these constants is undefined until one of the following functions has been called:
2
+ # ABAddressBookCreate, ABPersonCreate, ABGroupCreate.
3
+ # see https://developer.apple.com/library/ios/#documentation/AddressBook/Reference/ABPersonRef_iPhoneOS/Reference/reference.html
4
+ # and search for "Discussion"
5
+ # WTF: Why would an API define a constant this way?!?!
6
+ AddressBook.address_book
metadata CHANGED
@@ -1,7 +1,7 @@
1
1
  --- !ruby/object:Gem::Specification
2
2
  name: motion-addressbook
3
3
  version: !ruby/object:Gem::Version
4
- version: 0.1.1
4
+ version: 0.1.2
5
5
  prerelease:
6
6
  platform: ruby
7
7
  authors:
@@ -9,7 +9,7 @@ authors:
9
9
  autorequire:
10
10
  bindir: bin
11
11
  cert_chain: []
12
- date: 2012-08-08 00:00:00.000000000 Z
12
+ date: 2012-11-08 00:00:00.000000000 Z
13
13
  dependencies:
14
14
  - !ruby/object:Gem::Dependency
15
15
  name: bubble-wrap
@@ -68,17 +68,19 @@ extra_rdoc_files: []
68
68
  files:
69
69
  - .gitignore
70
70
  - Gemfile
71
- - Gemfile.lock
72
71
  - LICENSE
73
72
  - README.md
74
73
  - Rakefile
75
74
  - lib/motion-addressbook.rb
76
75
  - lib/motion-addressbook/version.rb
77
76
  - motion-addressbook.gemspec
77
+ - motion/address_book.rb
78
78
  - motion/address_book/multi_value.rb
79
79
  - motion/address_book/person.rb
80
+ - motion/address_book/picker.rb
80
81
  - spec/address_book/multi_value_spec.rb
81
82
  - spec/address_book/person_spec.rb
83
+ - spec/address_book/picker_spec.rb
82
84
  - spec/helpers/bacon_matchers.rb
83
85
  - spec/helpers/hacks.rb
84
86
  - spec/helpers/person_helpers.rb
@@ -96,7 +98,7 @@ required_ruby_version: !ruby/object:Gem::Requirement
96
98
  version: '0'
97
99
  segments:
98
100
  - 0
99
- hash: 2733254916178873065
101
+ hash: 4255124381672907442
100
102
  required_rubygems_version: !ruby/object:Gem::Requirement
101
103
  none: false
102
104
  requirements:
@@ -105,7 +107,7 @@ required_rubygems_version: !ruby/object:Gem::Requirement
105
107
  version: '0'
106
108
  segments:
107
109
  - 0
108
- hash: 2733254916178873065
110
+ hash: 4255124381672907442
109
111
  requirements: []
110
112
  rubyforge_project:
111
113
  rubygems_version: 1.8.24
@@ -115,6 +117,7 @@ summary: A RubyMotion wrapper around the iOS Address Book framework
115
117
  test_files:
116
118
  - spec/address_book/multi_value_spec.rb
117
119
  - spec/address_book/person_spec.rb
120
+ - spec/address_book/picker_spec.rb
118
121
  - spec/helpers/bacon_matchers.rb
119
122
  - spec/helpers/hacks.rb
120
123
  - spec/helpers/person_helpers.rb
data/Gemfile.lock DELETED
@@ -1,29 +0,0 @@
1
- PATH
2
- remote: .
3
- specs:
4
- motion-addressbook (0.1.1)
5
- bubble-wrap
6
-
7
- GEM
8
- remote: https://rubygems.org/
9
- specs:
10
- bubble-wrap (1.1.1)
11
- diff-lcs (1.1.3)
12
- rake (0.9.2.2)
13
- rspec (2.10.0)
14
- rspec-core (~> 2.10.0)
15
- rspec-expectations (~> 2.10.0)
16
- rspec-mocks (~> 2.10.0)
17
- rspec-core (2.10.1)
18
- rspec-expectations (2.10.0)
19
- diff-lcs (~> 1.1.3)
20
- rspec-mocks (2.10.1)
21
-
22
- PLATFORMS
23
- ruby
24
-
25
- DEPENDENCIES
26
- bubble-wrap
27
- motion-addressbook!
28
- rake
29
- rspec