sox 0.0.1

Sign up to get free protection for your applications and to get access to all the features.
@@ -0,0 +1,4 @@
1
+ pkg/*
2
+ build/
3
+ .DS_Store
4
+ .repl_history
data/Gemfile ADDED
@@ -0,0 +1,3 @@
1
+ source 'https://rubygems.org'
2
+
3
+ gemspec
@@ -0,0 +1,22 @@
1
+ PATH
2
+ remote: .
3
+ specs:
4
+ sox (0.0.1)
5
+ bubble-wrap (~> 1.3.0)
6
+
7
+ GEM
8
+ remote: https://rubygems.org/
9
+ specs:
10
+ bacon (1.2.0)
11
+ bubble-wrap (1.3.0)
12
+ rake (10.1.0)
13
+ webstub (0.6.1)
14
+
15
+ PLATFORMS
16
+ ruby
17
+
18
+ DEPENDENCIES
19
+ bacon
20
+ rake
21
+ sox!
22
+ webstub
data/LICENSE ADDED
@@ -0,0 +1,22 @@
1
+ Copyright (c) 2013, Brilliant Fantastic, LLC.
2
+
3
+ Permission is hereby granted, free of charge, to any person
4
+ obtaining a copy of this software and associated documentation
5
+ files (the "Software"), to deal in the Software without
6
+ restriction, including without limitation the rights to use,
7
+ copy, modify, merge, publish, distribute, sublicense, and/or sell
8
+ copies of the Software, and to permit persons to whom the
9
+ Software is furnished to do so, subject to the following
10
+ conditions:
11
+
12
+ The above copyright notice and this permission notice shall be
13
+ included in all copies or substantial portions of the Software.
14
+
15
+ THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND,
16
+ EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES
17
+ OF MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND
18
+ NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT
19
+ HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY,
20
+ WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING
21
+ FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR
22
+ OTHER DEALINGS IN THE SOFTWARE.
@@ -0,0 +1,91 @@
1
+ Sox
2
+ ====
3
+
4
+ A RubyMotion wrapper for the Freshbooks API.
5
+
6
+ ## Installation
7
+
8
+ Add this to your RubyMotion application's `Gemfile`:
9
+
10
+ ```ruby
11
+ gem 'sox'
12
+ ```
13
+
14
+ And then execute:
15
+
16
+ ```sh
17
+ bundle
18
+ ```
19
+
20
+ Then add this to your RubyMotion application's `Rakefile`:
21
+
22
+ ```ruby
23
+ require 'sox'
24
+ ```
25
+
26
+ ## Usage
27
+
28
+ All requests can go through the `Sox::Client` object. To create a client object, you pass it your authorization credentials.
29
+ Currenly Sox only supports basic authentication using your Freshbooks API token but we there is a plan to add OAuth support.
30
+
31
+ You can retrieve your Freshbooks API token from the 'My Account' page on Freshbooks.
32
+
33
+ ```ruby
34
+ @client = Sox::Client 'your-freshbooks-subdomain', 'api-token'
35
+ ```
36
+
37
+ You can then use your client object to perform the CRUD operations on Freshbooks.
38
+
39
+ ```ruby
40
+ projects = @client.projects.all
41
+ ```
42
+
43
+ Sox will convert the XML that is returned from the Freshbooks API into modern day hashes. Yay!
44
+
45
+ ```ruby
46
+ puts projects[:response][:projects][:project][0][:client_id]
47
+ ```
48
+
49
+ ## TODO
50
+
51
+ This is a very early release. All the base classes and supporting classes should be in place to easily add the additional proxies and API calls (specifically find, update, create, and delete).
52
+
53
+ * Currently only setup for OSX
54
+ * Add integration tests for real API data
55
+ * Support for OAuth authentication
56
+
57
+ * Clients (create, update, get, delete)
58
+ * Projects (create, update, get, delete)
59
+ * Categories (create, update, get, delete, list)
60
+ * Estimates (create, update, get, delete, list, sendByEmail)
61
+ * Categories (create, update, get, delete, list)
62
+ * Expenses (create, update, get, delete, list)
63
+ * Gateways (list)
64
+ * Invoices (create, update, get, delete, list, sendByEmail, sendbySnailMail)
65
+ * Invoice Items (add, delete, update)
66
+ * Items (create, update, get, delete, list)
67
+ * Languages (list)
68
+ * Payments (create, update, get, delete, list)
69
+ * Receipts (create, update, get, delete)
70
+ * Recurring (create, update, get, delete, list)
71
+ * Recurring Items (add, delete, update)
72
+ * Staff (current, get, list)
73
+ * Tasks (create, update, get, delete)
74
+ * Taxes (create, update, get, delete)
75
+ * Time entries (create, update, get, delete)
76
+
77
+ ## Contributing
78
+
79
+ 1. Fork it
80
+ 1. Run `bundle` to get the Gem dependency
81
+ 1. Run `rake spec` to run all of the tests to ensure they pass
82
+ 1. Create your feature branch (`git checkout -b my-awesome-sauce`)
83
+ 1. Write your specs
84
+ 1. Code!
85
+ 1. Commit your changes (`git commit -am 'Added some awesome sauce'`)
86
+ 1. Push your new branch (`git push origin my-awesome-sauce`)
87
+ 1. Create a pull request (`hub pull-request -b brilliantfantastic:master -h yourrepo:my-awesome-sauce`)
88
+
89
+ ## License
90
+
91
+ MIT. See [LICENSE](LICENSE)
@@ -0,0 +1,26 @@
1
+ $:.unshift("/Library/RubyMotion/lib")
2
+
3
+ #if ENV['osx']
4
+ require 'motion/project/template/osx'
5
+ #else
6
+ # require 'motion/project/template/ios'
7
+ #end
8
+
9
+ require 'bundler'
10
+ require 'bubble-wrap'
11
+ require 'webstub'
12
+
13
+ Bundler.setup
14
+ Bundler.require
15
+
16
+ Bundler::GemHelper.install_tasks
17
+
18
+ namespace :spec do
19
+ task :lib do
20
+ sh "bacon #{Dir.glob("spec/lib/**/*_spec.rb").join(' ')}"
21
+ end
22
+
23
+ task :motion => 'spec'
24
+
25
+ task :all => [:lib, :motion]
26
+ end
@@ -0,0 +1,9 @@
1
+ unless defined?(Motion::Project::Config)
2
+ raise "This file must be required within a RubyMotion project Rakefile."
3
+ end
4
+
5
+ Motion::Project::App.setup do |app|
6
+ Dir.glob(File.join(File.dirname(__FILE__), 'sox/*.rb')).each do |file|
7
+ app.files.unshift(file)
8
+ end
9
+ end
@@ -0,0 +1,24 @@
1
+ module Sox
2
+ class Client
3
+ include Sox::Proxy
4
+
5
+ attr_reader :base_url
6
+
7
+ def initialize(subdomain, api_token)
8
+ @base_url = "https://#{subdomain}.freshbooks.com/api/#{Sox::API_VERSION}/xml-in"
9
+ @api_token = api_token
10
+ end
11
+
12
+ def auth
13
+ { username: @api_token, password: 'X' }
14
+ end
15
+
16
+ def clients
17
+ proxy(:client)
18
+ end
19
+
20
+ def projects
21
+ proxy(:project)
22
+ end
23
+ end
24
+ end
@@ -0,0 +1,11 @@
1
+ module Sox
2
+ module Proxy
3
+ def proxy(name)
4
+ @proxies = {} unless @proxies
5
+ unless @proxies.key? name
6
+ @proxies[name] = RestfulOperation.new(base_url, auth, name)
7
+ end
8
+ @proxies[name]
9
+ end
10
+ end
11
+ end
@@ -0,0 +1,40 @@
1
+ module Sox
2
+ class RequestOptions
3
+ def request
4
+ generate_xml
5
+ end
6
+
7
+ def initialize(method, options={})
8
+ @method = method
9
+ @options = options
10
+ end
11
+
12
+ private
13
+
14
+ def generate_xml
15
+ root = NSXMLElement.alloc.initWithName 'request'
16
+ root.addAttribute NSXMLNode.attributeWithName('method', stringValue: @method)
17
+
18
+ # Add all the options if there are any
19
+ @options.each { |key, value| generate_xml_node root, key, value }
20
+
21
+ document = NSXMLDocument.alloc.initWithRootElement root
22
+ data = document.XMLDataWithOptions NSXMLDocumentTidyXML
23
+ NSString.alloc.initWithData(data, encoding: NSUTF8StringEncoding)
24
+ end
25
+
26
+ def generate_xml_node(element, key, value)
27
+ if value.is_a? Hash
28
+ parent = NSXMLElement.alloc.initWithName(key)
29
+ value.each do |k, v|
30
+ el = generate_xml_node parent, k, v
31
+ element.addChild el
32
+ end
33
+ else
34
+ el = NSXMLElement.alloc.initWithName(key, stringValue: value.to_s)
35
+ element.addChild el
36
+ end
37
+ element
38
+ end
39
+ end
40
+ end
@@ -0,0 +1,21 @@
1
+ module Sox
2
+ class RestfulOperation
3
+ attr_reader :prefix
4
+
5
+ def initialize(base_url, auth, prefix)
6
+ @base_url = base_url
7
+ @auth = auth
8
+ @prefix = prefix
9
+ end
10
+
11
+ def all(options={}, &block)
12
+ payload = RequestOptions.new("#{@prefix}.list", options).request
13
+ opts = { credentials: @auth }
14
+ opts.merge!({ payload: payload })
15
+ BW::HTTP.post(@base_url, opts) do |response|
16
+ XML::Parser.new(response.body).parse { |hash| block.call hash }
17
+ end
18
+ end
19
+ end
20
+ end
21
+
@@ -0,0 +1,4 @@
1
+ module Sox
2
+ API_VERSION = '2.1'
3
+ VERSION = '0.0.1'
4
+ end
@@ -0,0 +1,64 @@
1
+ module Sox
2
+ module XML
3
+ class ParserError < StandardError; end
4
+
5
+ class Parser
6
+ def initialize(data)
7
+ @data = data
8
+ end
9
+
10
+ def parse(&block)
11
+ @callback = block
12
+ parse_data @data
13
+ end
14
+
15
+ def parserDidStartDocument(parser)
16
+ @result = [{}]
17
+ end
18
+
19
+ def parser(parser, didStartElement:element, namespaceURI:uri, qualifiedName:name, attributes:attrs)
20
+ element = element.to_sym
21
+ parent = @result.last
22
+
23
+ child = {}
24
+ attrs.each { |k, v| child[k.to_sym] = v } if attrs
25
+
26
+ existing = parent[element]
27
+
28
+ if existing
29
+ values = existing.is_a?(Array) ? existing << child : [existing, child]
30
+ parent[element] = values
31
+ else
32
+ parent[element] = child
33
+ end
34
+
35
+ @result << child
36
+ end
37
+
38
+ def parser(parser, didEndElement:element, namespaceURI:uri, qualifiedName:name)
39
+ @result.pop
40
+ end
41
+
42
+ def parser(parser, foundCharacters:string)
43
+ @result.last[:data] = string.strip
44
+ end
45
+
46
+ def parserDidEndDocument(parser)
47
+ @callback.call(@result.first) if @callback
48
+ end
49
+
50
+ def parser(parser, parseErrorOccurred:parse_error)
51
+ raise ParserError.new(parse_error.localizedDescription)
52
+ end
53
+
54
+ private
55
+
56
+ def parse_data(data)
57
+ parser = NSXMLParser.alloc.initWithData(data)
58
+ parser.shouldProcessNamespaces = true
59
+ parser.delegate = self
60
+ parser.parse
61
+ end
62
+ end
63
+ end
64
+ end
@@ -0,0 +1,21 @@
1
+ # -*- encoding: utf-8 -*-
2
+ require File.expand_path('../lib/sox/version', __FILE__)
3
+
4
+ Gem::Specification.new do |gem|
5
+ gem.authors = ['Jamie Wright', 'Keith Thompson']
6
+ gem.email = ['jamie@brilliantfantastic.com', 'keith@brilliantfantastic.com']
7
+ gem.description = 'A RubyMotion wrapper for the Freshbooks API'
8
+ gem.summary = 'A RubyMotion wrapper for the Freshbooks API'
9
+
10
+ gem.files = `git ls-files`.split($\)
11
+ gem.test_files = gem.files.grep(%r{^(test|spec|lib_spec|features)/})
12
+ gem.name = 'sox'
13
+ gem.require_paths = ['lib']
14
+ gem.version = Sox::VERSION
15
+
16
+ gem.add_dependency 'bubble-wrap', '~> 1.3.0'
17
+
18
+ gem.add_development_dependency 'webstub'
19
+ gem.add_development_dependency 'bacon'
20
+ gem.add_development_dependency 'rake'
21
+ end
@@ -0,0 +1,6 @@
1
+ module SpecHelper
2
+ def load_fixture(fixture)
3
+ fixture_path = File.join File.expand_path(File.dirname(__FILE__)), 'fixtures'
4
+ File.readlines(File.join(fixture_path, fixture)).join "\n"
5
+ end
6
+ end
@@ -0,0 +1,14 @@
1
+ <response xmlns="http://www.freshbooks.com/api/" status="ok">
2
+ <clients page="1" per_page="25" pages="1" total="2">
3
+ <client>
4
+ <client_id>1</client_id>
5
+ <first_name>Jane</first_name>
6
+ <last_name>Doe</last_name>
7
+ </client>
8
+ <client>
9
+ <client_id>2</client_id>
10
+ <first_name>John</first_name>
11
+ <last_name>Doe</last_name>
12
+ </client>
13
+ </clients>
14
+ </response>
@@ -0,0 +1,45 @@
1
+ <response xmlns="http://www.freshbooks.com/api/" status="ok">
2
+ <projects page="1" per_page="15" pages="1" total="2">
3
+ <project>
4
+ <project_id>6</project_id>
5
+ <name>Super Fun Project</name>
6
+ <description></description>
7
+ <rate>11000</rate>
8
+ <bill_method>flat-rate</bill_method>
9
+ <client_id>119</client_id>
10
+ <hour_budget>100</hour_budget>
11
+ <tasks>
12
+ <task>
13
+ <task_id>5</task_id>
14
+ </task>
15
+ <task>
16
+ <task_id>12</task_id>
17
+ </task>
18
+ <task>
19
+ <task_id>6</task_id>
20
+ <rate>25</rate>
21
+ </task>
22
+ <task>
23
+ <task_id>7</task_id>
24
+ <rate>35</rate>
25
+ </task>
26
+ </tasks>
27
+ <staff>
28
+ <staff>
29
+ <staff_id>1</staff_id>
30
+ </staff>
31
+ <staff>
32
+ <staff_id>2</staff_id>
33
+ </staff>
34
+ <staff>
35
+ <staff_id>3</staff_id>
36
+ </staff>
37
+ <staff_id deprecated="true">1</staff_id>
38
+ <staff_id deprecated="true">2</staff_id>
39
+ <staff_id deprecated="true">3</staff_id>
40
+ </staff>
41
+ </project>
42
+ <project>
43
+ </project>
44
+ </projects>
45
+ </response>
@@ -0,0 +1,36 @@
1
+ describe Sox::Client do
2
+ before do
3
+ @subdomain = 'fake'
4
+ @api_token = '1234'
5
+ @client = Sox::Client.new @subdomain, @api_token
6
+ end
7
+
8
+ describe '#initialize' do
9
+ it 'sets the base url' do
10
+ @client.base_url.should == "https://#{@subdomain}.freshbooks.com/api/#{Sox::API_VERSION}/xml-in"
11
+ end
12
+ end
13
+
14
+ describe '#auth' do
15
+ it 'sets the auth header using basic auth' do
16
+ @client.auth.should == { username: @api_token, password: 'X' }
17
+ end
18
+ end
19
+
20
+ describe '#clients' do
21
+ it 'returns a proxy object' do
22
+ @client.clients.prefix.should == :client
23
+ end
24
+ end
25
+
26
+ describe '#projects' do
27
+ it 'returns a proxy object' do
28
+ @client.projects.prefix.should == :project
29
+ end
30
+ end
31
+
32
+ it 'can have multiple proxies' do
33
+ @client.clients.prefix.should == :client
34
+ @client.projects.prefix.should == :project
35
+ end
36
+ end
@@ -0,0 +1,28 @@
1
+ describe 'Freshbooks API clients operations' do
2
+ extend WebStub::SpecHelpers
3
+ extend SpecHelper
4
+
5
+ before do
6
+ disable_network_access!
7
+ @response = load_fixture('all_clients_successful_response.xml')
8
+ @client = Sox::Client.new 'fake', '12345'
9
+ end
10
+
11
+ it 'can fetch all of the clients' do
12
+ stub_request(:post, @client.base_url).to_return body: @response, content_type: 'application/xml'
13
+
14
+ @client.clients.all do |response|
15
+ @response = response
16
+ resume
17
+ end
18
+ wait_max 1.0 do
19
+ response = @response[:response]
20
+ response[:status].should == 'ok'
21
+ response[:clients][:total].should == '2'
22
+ response[:clients][:client][0][:client_id][:data].should == '1'
23
+ response[:clients][:client][0][:first_name][:data].should == 'Jane'
24
+ response[:clients][:client][1][:client_id][:data].should == '2'
25
+ response[:clients][:client][1][:first_name][:data].should == 'John'
26
+ end
27
+ end
28
+ end
@@ -0,0 +1,27 @@
1
+ describe 'Freshbooks API projects operations' do
2
+ extend WebStub::SpecHelpers
3
+ extend SpecHelper
4
+
5
+ before do
6
+ disable_network_access!
7
+ @response = load_fixture('all_projects_successful_response.xml')
8
+ @client = Sox::Client.new 'fake', '12345'
9
+ end
10
+
11
+ it 'can fetch all of the projects' do
12
+ stub_request(:post, @client.base_url).to_return body: @response, content_type: 'application/xml'
13
+
14
+ @client.projects.all do |response|
15
+ @response = response
16
+ resume
17
+ end
18
+ wait_max 1.0 do
19
+ response = @response[:response]
20
+ response[:status].should == 'ok'
21
+ response[:projects][:total].should == '2'
22
+ response[:projects][:project][0][:project_id][:data].should == '6'
23
+ response[:projects][:project][0][:name][:data].should == 'Super Fun Project'
24
+ response[:projects][:project][0][:client_id][:data].should == '119'
25
+ end
26
+ end
27
+ end
@@ -0,0 +1,21 @@
1
+ describe Sox::RequestOptions do
2
+ it 'specifies the request method in the xml' do
3
+ request = Sox::RequestOptions.new('client.list').request
4
+ request.should == '<request method="client.list"></request>'
5
+ end
6
+
7
+ it 'converts a simple hash to xml' do
8
+ request = Sox::RequestOptions.new('client.list', { client_id: 1 }).request
9
+ request.should == '<request method="client.list"><client_id>1</client_id></request>'
10
+ end
11
+
12
+ it 'converts an embedded hash to xml' do
13
+ request = Sox::RequestOptions.new('client.list', { client: { client_id: 1 } }).request
14
+ request.should == '<request method="client.list"><client><client_id>1</client_id></client></request>'
15
+ end
16
+
17
+ it 'converts a super duper embedded hash to xml' do
18
+ request = Sox::RequestOptions.new('client.list', { client: { address: { street: '123 Main St.' } } }).request
19
+ request.should == '<request method="client.list"><client><address><street>123 Main St.</street></address></client></request>'
20
+ end
21
+ end
@@ -0,0 +1,163 @@
1
+ describe Sox::XML::Parser do
2
+ describe '#parse' do
3
+ describe 'with a simple single element' do
4
+ before do
5
+ document = "<element></element>"
6
+ @parser = Sox::XML::Parser.new(document.dataUsingEncoding(NSUTF8StringEncoding, allowLossyConversion: true))
7
+ end
8
+
9
+ it 'yields to the block once parsing has finished' do
10
+ hash = nil
11
+ @parser.parse { |hsh| hash = hsh }
12
+ hash.should != nil
13
+ end
14
+
15
+ it 'creates a hash representing the document' do
16
+ hash = nil
17
+ @parser.parse { |hsh| hash = hsh }
18
+ hash.should == { element: {} }
19
+ end
20
+ end
21
+
22
+ describe 'with a simple single element and data' do
23
+ before do
24
+ document = "<element>value</element>"
25
+ @parser = Sox::XML::Parser.new(document.dataUsingEncoding(NSUTF8StringEncoding, allowLossyConversion: true))
26
+ end
27
+
28
+ it 'creates a hash representing the document' do
29
+ hash = nil
30
+ @parser.parse { |hsh| hash = hsh }
31
+ hash.should == { element: { data: 'value' } }
32
+ end
33
+ end
34
+
35
+ describe 'with a simple single element and an attribute' do
36
+ before do
37
+ document = "<element attribute=\"attribute one\"></element>"
38
+ @parser = Sox::XML::Parser.new(document.dataUsingEncoding(NSUTF8StringEncoding, allowLossyConversion: true))
39
+ end
40
+
41
+ it 'creates a hash representing the document' do
42
+ hash = nil
43
+ @parser.parse { |hsh| hash = hsh }
44
+ hash.should == { element: { attribute: 'attribute one' } }
45
+ end
46
+ end
47
+
48
+ describe 'with a simple single element and multiple attribute' do
49
+ before do
50
+ document = "<element attribute_one=\"attribute one\" attribute_two=\"attribute two\"></element>"
51
+ @parser = Sox::XML::Parser.new(document.dataUsingEncoding(NSUTF8StringEncoding, allowLossyConversion: true))
52
+ end
53
+
54
+ it 'creates a hash representing the document' do
55
+ hash = nil
56
+ @parser.parse { |hsh| hash = hsh }
57
+ hash.should == { element: { attribute_one: 'attribute one', attribute_two: 'attribute two' } }
58
+ end
59
+ end
60
+
61
+ describe 'with a simple single element, multiple attribute, and a value' do
62
+ before do
63
+ document = "<element attribute_one=\"attribute one\" attribute_two=\"attribute two\">value</element>"
64
+ @parser = Sox::XML::Parser.new(document.dataUsingEncoding(NSUTF8StringEncoding, allowLossyConversion: true))
65
+ end
66
+
67
+ it 'creates a hash representing the document' do
68
+ hash = nil
69
+ @parser.parse { |hsh| hash = hsh }
70
+ hash.should == { element: { attribute_one: 'attribute one', attribute_two: 'attribute two', data: 'value' } }
71
+ end
72
+ end
73
+
74
+ describe 'with a simple embedded element' do
75
+ before do
76
+ document = "<element_one><element_two></element_two></element_one>"
77
+ @parser = Sox::XML::Parser.new(document.dataUsingEncoding(NSUTF8StringEncoding, allowLossyConversion: true))
78
+ end
79
+
80
+ it 'creates a hash representing the document' do
81
+ hash = nil
82
+ @parser.parse { |hsh| hash = hsh }
83
+ hash.should == { element_one: { element_two: {} } }
84
+ end
85
+ end
86
+
87
+ describe 'with a simple embedded element and a value' do
88
+ before do
89
+ document = "<element_one><element_two>value two</element_two></element_one>"
90
+ @parser = Sox::XML::Parser.new(document.dataUsingEncoding(NSUTF8StringEncoding, allowLossyConversion: true))
91
+ end
92
+
93
+ it 'creates a hash representing the document' do
94
+ hash = nil
95
+ @parser.parse { |hsh| hash = hsh }
96
+ hash.should == { element_one: { element_two: { data: 'value two' } } }
97
+ end
98
+ end
99
+
100
+ describe 'with a simple embedded element, a value, and some attributes' do
101
+ before do
102
+ document = "<element_one attribute_one=\"attribute one\"><element_two attribute_two=\"attribute two\">value two</element_two></element_one>"
103
+ @parser = Sox::XML::Parser.new(document.dataUsingEncoding(NSUTF8StringEncoding, allowLossyConversion: true))
104
+ end
105
+
106
+ it 'creates a hash representing the document' do
107
+ hash = nil
108
+ @parser.parse { |hsh| hash = hsh }
109
+ hash.should == { element_one: { attribute_one: 'attribute one', element_two: { attribute_two: 'attribute two', data: 'value two' } } }
110
+ end
111
+ end
112
+
113
+ describe 'with multiple child elements' do
114
+ before do
115
+ document = "<element_one><element_two>value two</element_two><element_three>value three</element_three></element_one>"
116
+ @parser = Sox::XML::Parser.new(document.dataUsingEncoding(NSUTF8StringEncoding, allowLossyConversion: true))
117
+ end
118
+
119
+ it 'creates a hash representing the document' do
120
+ hash = nil
121
+ @parser.parse { |hsh| hash = hsh }
122
+ hash.should == { element_one: { element_two: { data: 'value two' }, element_three: { data: 'value three' } } }
123
+ end
124
+ end
125
+
126
+ describe 'with an array of child elements' do
127
+ before do
128
+ document = "<parent><children><child></child><child></child></children></parent>"
129
+ @parser = Sox::XML::Parser.new(document.dataUsingEncoding(NSUTF8StringEncoding, allowLossyConversion: true))
130
+ end
131
+
132
+ it 'creates a hash representing the document' do
133
+ hash = nil
134
+ @parser.parse { |hsh| hash = hsh }
135
+ hash.should == { parent: { children: { child: [{}, {}]} } }
136
+ end
137
+ end
138
+
139
+ describe 'with an array with arrays of child elements' do
140
+ before do
141
+ document = "<parent><children><child></child><child><toys><toy></toy></toys></child></children></parent>"
142
+ @parser = Sox::XML::Parser.new(document.dataUsingEncoding(NSUTF8StringEncoding, allowLossyConversion: true))
143
+ end
144
+
145
+ it 'creates a hash representing the document' do
146
+ hash = nil
147
+ @parser.parse { |hsh| hash = hsh }
148
+ hash.should == { parent: { children: { child: [{}, { toys: { toy: {} } } ] } } }
149
+ end
150
+ end
151
+
152
+ describe 'with an invalid xml document' do
153
+ before do
154
+ document = '<element value></element>'
155
+ @parser = Sox::XML::Parser.new(document.dataUsingEncoding(NSUTF8StringEncoding, allowLossyConversion: true))
156
+ end
157
+
158
+ it 'raises a parser error' do
159
+ should.raise(Sox::XML::ParserError) { @parser.parse }
160
+ end
161
+ end
162
+ end
163
+ end
metadata ADDED
@@ -0,0 +1,147 @@
1
+ --- !ruby/object:Gem::Specification
2
+ name: sox
3
+ version: !ruby/object:Gem::Version
4
+ version: 0.0.1
5
+ prerelease:
6
+ platform: ruby
7
+ authors:
8
+ - Jamie Wright
9
+ - Keith Thompson
10
+ autorequire:
11
+ bindir: bin
12
+ cert_chain: []
13
+ date: 2013-07-26 00:00:00.000000000 Z
14
+ dependencies:
15
+ - !ruby/object:Gem::Dependency
16
+ name: bubble-wrap
17
+ requirement: !ruby/object:Gem::Requirement
18
+ none: false
19
+ requirements:
20
+ - - ~>
21
+ - !ruby/object:Gem::Version
22
+ version: 1.3.0
23
+ type: :runtime
24
+ prerelease: false
25
+ version_requirements: !ruby/object:Gem::Requirement
26
+ none: false
27
+ requirements:
28
+ - - ~>
29
+ - !ruby/object:Gem::Version
30
+ version: 1.3.0
31
+ - !ruby/object:Gem::Dependency
32
+ name: webstub
33
+ requirement: !ruby/object:Gem::Requirement
34
+ none: false
35
+ requirements:
36
+ - - ! '>='
37
+ - !ruby/object:Gem::Version
38
+ version: '0'
39
+ type: :development
40
+ prerelease: false
41
+ version_requirements: !ruby/object:Gem::Requirement
42
+ none: false
43
+ requirements:
44
+ - - ! '>='
45
+ - !ruby/object:Gem::Version
46
+ version: '0'
47
+ - !ruby/object:Gem::Dependency
48
+ name: bacon
49
+ requirement: !ruby/object:Gem::Requirement
50
+ none: false
51
+ requirements:
52
+ - - ! '>='
53
+ - !ruby/object:Gem::Version
54
+ version: '0'
55
+ type: :development
56
+ prerelease: false
57
+ version_requirements: !ruby/object:Gem::Requirement
58
+ none: false
59
+ requirements:
60
+ - - ! '>='
61
+ - !ruby/object:Gem::Version
62
+ version: '0'
63
+ - !ruby/object:Gem::Dependency
64
+ name: rake
65
+ requirement: !ruby/object:Gem::Requirement
66
+ none: false
67
+ requirements:
68
+ - - ! '>='
69
+ - !ruby/object:Gem::Version
70
+ version: '0'
71
+ type: :development
72
+ prerelease: false
73
+ version_requirements: !ruby/object:Gem::Requirement
74
+ none: false
75
+ requirements:
76
+ - - ! '>='
77
+ - !ruby/object:Gem::Version
78
+ version: '0'
79
+ description: A RubyMotion wrapper for the Freshbooks API
80
+ email:
81
+ - jamie@brilliantfantastic.com
82
+ - keith@brilliantfantastic.com
83
+ executables: []
84
+ extensions: []
85
+ extra_rdoc_files: []
86
+ files:
87
+ - .gitignore
88
+ - Gemfile
89
+ - Gemfile.lock
90
+ - LICENSE
91
+ - README.md
92
+ - Rakefile
93
+ - lib/sox.rb
94
+ - lib/sox/client.rb
95
+ - lib/sox/proxy.rb
96
+ - lib/sox/request_options.rb
97
+ - lib/sox/restful_operation.rb
98
+ - lib/sox/version.rb
99
+ - lib/sox/xml_parser.rb
100
+ - sox.gemspec
101
+ - spec/_spec_helper.rb
102
+ - spec/fixtures/all_clients_successful_response.xml
103
+ - spec/fixtures/all_projects_successful_response.xml
104
+ - spec/lib/sox/client_spec.rb
105
+ - spec/lib/sox/clients_operations_spec.rb
106
+ - spec/lib/sox/projects_operations_spec.rb
107
+ - spec/lib/sox/request_options_spec.rb
108
+ - spec/lib/sox/xml_parser_spec.rb
109
+ homepage:
110
+ licenses: []
111
+ post_install_message:
112
+ rdoc_options: []
113
+ require_paths:
114
+ - lib
115
+ required_ruby_version: !ruby/object:Gem::Requirement
116
+ none: false
117
+ requirements:
118
+ - - ! '>='
119
+ - !ruby/object:Gem::Version
120
+ version: '0'
121
+ segments:
122
+ - 0
123
+ hash: 2815329898089102049
124
+ required_rubygems_version: !ruby/object:Gem::Requirement
125
+ none: false
126
+ requirements:
127
+ - - ! '>='
128
+ - !ruby/object:Gem::Version
129
+ version: '0'
130
+ segments:
131
+ - 0
132
+ hash: 2815329898089102049
133
+ requirements: []
134
+ rubyforge_project:
135
+ rubygems_version: 1.8.23
136
+ signing_key:
137
+ specification_version: 3
138
+ summary: A RubyMotion wrapper for the Freshbooks API
139
+ test_files:
140
+ - spec/_spec_helper.rb
141
+ - spec/fixtures/all_clients_successful_response.xml
142
+ - spec/fixtures/all_projects_successful_response.xml
143
+ - spec/lib/sox/client_spec.rb
144
+ - spec/lib/sox/clients_operations_spec.rb
145
+ - spec/lib/sox/projects_operations_spec.rb
146
+ - spec/lib/sox/request_options_spec.rb
147
+ - spec/lib/sox/xml_parser_spec.rb