sox 0.0.1
Sign up to get free protection for your applications and to get access to all the features.
- data/.gitignore +4 -0
- data/Gemfile +3 -0
- data/Gemfile.lock +22 -0
- data/LICENSE +22 -0
- data/README.md +91 -0
- data/Rakefile +26 -0
- data/lib/sox.rb +9 -0
- data/lib/sox/client.rb +24 -0
- data/lib/sox/proxy.rb +11 -0
- data/lib/sox/request_options.rb +40 -0
- data/lib/sox/restful_operation.rb +21 -0
- data/lib/sox/version.rb +4 -0
- data/lib/sox/xml_parser.rb +64 -0
- data/sox.gemspec +21 -0
- data/spec/_spec_helper.rb +6 -0
- data/spec/fixtures/all_clients_successful_response.xml +14 -0
- data/spec/fixtures/all_projects_successful_response.xml +45 -0
- data/spec/lib/sox/client_spec.rb +36 -0
- data/spec/lib/sox/clients_operations_spec.rb +28 -0
- data/spec/lib/sox/projects_operations_spec.rb +27 -0
- data/spec/lib/sox/request_options_spec.rb +21 -0
- data/spec/lib/sox/xml_parser_spec.rb +163 -0
- metadata +147 -0
data/.gitignore
ADDED
data/Gemfile
ADDED
data/Gemfile.lock
ADDED
@@ -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.
|
data/README.md
ADDED
@@ -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)
|
data/Rakefile
ADDED
@@ -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
|
data/lib/sox.rb
ADDED
@@ -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
|
data/lib/sox/client.rb
ADDED
@@ -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
|
data/lib/sox/proxy.rb
ADDED
@@ -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
|
+
|
data/lib/sox/version.rb
ADDED
@@ -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
|
data/sox.gemspec
ADDED
@@ -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,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
|