sox 0.0.1

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.
@@ -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