badbill 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.
data/CONTRIBUTING.md ADDED
@@ -0,0 +1,34 @@
1
+ # How to contribute
2
+
3
+ This API client is implemented for my own use, so I will only work on the parts I need for myself.
4
+ Additional improvements, comments on code and implementation and bug fixes are very welcome.
5
+
6
+ ## Report bugs, improvements or feature requests
7
+
8
+ * Submit a ticket for your issue, assuming one does not already exist.
9
+ * Clearly describe the issue including steps to reproduce when it is a bug.
10
+ * Make sure you fill in the earliest version that you know has the issue.
11
+
12
+ ## Making Changes
13
+
14
+ * Fork the repository on GitHub.
15
+ * Create a topic branch from where you want to base your work.
16
+ * Make commits of logical units.
17
+ * Check for unnecessary whitespace with `git diff --check` before committing.
18
+ * Make sure your commit messages are in the proper format.
19
+ * Run _all_ the tests to assure nothing else was accidentally broken.
20
+ * Include tests for new features and bug fixes (if you don't know how to do that, feel free to ask, I will help).
21
+
22
+
23
+ ## Submitting Changes
24
+
25
+ * Push your changes to a topic branch in your fork of the repository.
26
+ * Submit a pull request to the repository.
27
+ * Wait for my response.
28
+
29
+ # Contact
30
+
31
+ Feel free to contact me with any issues or questions.
32
+
33
+ * Mail: badboy@archlinux.us
34
+ * Twitter: [@badboy_](https://twitter.com/badboy_)
data/Gemfile ADDED
@@ -0,0 +1,9 @@
1
+ source 'https://rubygems.org'
2
+
3
+ group :development do
4
+ gem 'yard'
5
+ gem 'redcarpet'
6
+ gem 'simplecov'
7
+ end
8
+
9
+ gemspec
data/Gemfile.lock ADDED
@@ -0,0 +1,51 @@
1
+ PATH
2
+ remote: .
3
+ specs:
4
+ badbill (0.0.1)
5
+ faraday
6
+ faraday_middleware
7
+ hashie
8
+ yajl-ruby
9
+
10
+ GEM
11
+ remote: https://rubygems.org/
12
+ specs:
13
+ addressable (2.3.2)
14
+ crack (0.3.1)
15
+ diff-lcs (1.1.3)
16
+ faraday (0.8.4)
17
+ multipart-post (~> 1.1)
18
+ faraday_middleware (0.8.8)
19
+ faraday (>= 0.7.4, < 0.9)
20
+ hashie (1.2.0)
21
+ multi_json (1.3.6)
22
+ multipart-post (1.1.5)
23
+ redcarpet (2.1.1)
24
+ rspec (2.11.0)
25
+ rspec-core (~> 2.11.0)
26
+ rspec-expectations (~> 2.11.0)
27
+ rspec-mocks (~> 2.11.0)
28
+ rspec-core (2.11.1)
29
+ rspec-expectations (2.11.3)
30
+ diff-lcs (~> 1.1.3)
31
+ rspec-mocks (2.11.3)
32
+ simplecov (0.6.4)
33
+ multi_json (~> 1.0)
34
+ simplecov-html (~> 0.5.3)
35
+ simplecov-html (0.5.3)
36
+ webmock (1.8.10)
37
+ addressable (>= 2.2.7)
38
+ crack (>= 0.1.7)
39
+ yajl-ruby (1.1.0)
40
+ yard (0.8.2.1)
41
+
42
+ PLATFORMS
43
+ ruby
44
+
45
+ DEPENDENCIES
46
+ badbill!
47
+ redcarpet
48
+ rspec
49
+ simplecov
50
+ webmock
51
+ yard
data/LICENSE ADDED
@@ -0,0 +1,21 @@
1
+ Copyright (c) 2012 Jan-Erik Rediger <http://fnordig.de/about/>
2
+
3
+ Permission is hereby granted, free of charge, to any person ob-
4
+ taining a copy of this software and associated documentation
5
+ files (the "Software"), to deal in the Software without restric-
6
+ tion, including without limitation the rights to use, copy, modi-
7
+ fy, merge, publish, distribute, sublicense, and/or sell copies of
8
+ the Software, and to permit persons to whom the Software is fur-
9
+ nished to do so, subject to the following conditions:
10
+
11
+ The above copyright notice and this permission notice shall be
12
+ included in all copies or substantial portions of the Software.
13
+
14
+ THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND,
15
+ EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES
16
+ OF MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONIN-
17
+ FRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE
18
+ LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN
19
+ ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN
20
+ CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
21
+ SOFTWARE.
data/README.md ADDED
@@ -0,0 +1,92 @@
1
+ BadBill - Billomat API Client
2
+ ===================
3
+
4
+ Simple but working API client for the [Billomat API][apidocu].
5
+
6
+ See the [API documentation][apidocu] for full documentation of all resources.
7
+
8
+ ## Features
9
+
10
+ * Fast and easy access to all resources the API provides
11
+ (not all resources are Ruby classes, yet)
12
+ * Full documentation.
13
+ * Test coverage as best as I can.
14
+ * Production-ready (it's for a job project).
15
+
16
+ ## What's working right now
17
+
18
+ The basic BadBill class allows access to all resources. It includes no syntactic sugar to work with, instead it just returns the data as a hash. This works for basic usage.
19
+
20
+ The following resources are currently implemented as its own class:
21
+
22
+ * [clients](http://www.billomat.com/en/api/invoices/) (`BadBill::Client`)
23
+ * [invoices](http://www.billomat.com/en/api/invoices/) (`BadBill::Invoices`)
24
+
25
+ These are the two I need right now.
26
+ Implementing new resources is easy. Pull Requests for others are welcome.
27
+
28
+ ## Requirements
29
+
30
+ * [yajl-ruby](https://github.com/brianmario/yajl-ruby)
31
+ * [faraday](https://github.com/technoweenie/faraday)
32
+ * [faraday_middleware](https://github.com/pengwynn/faraday_middleware)
33
+ * [hashie](https://github.com/intridea/hashie)
34
+
35
+ ## Installation
36
+
37
+ BadBill is just a `gem install badbill` away. Get an API key for the Billomat API on your profile page.
38
+
39
+ ## Examples
40
+
41
+ ### Basic Usage
42
+
43
+ bill = BadBill.new "billo", "18e40e14"
44
+ # => #<BadBill:0x00000001319d30 ...>
45
+ bill.get 'settings'
46
+ # => {"settings"=>
47
+ # {"invoice_intro"=>"",
48
+ # "invoice_note"=>"",
49
+ # ...}}
50
+ bill.put 'settings', settings: { invoice_intro: "Intro" }
51
+ # => {"settings"=>
52
+ # {"invoice_intro"=>"Intro",
53
+ # "invoice_note"=>"",
54
+ # ...}}
55
+
56
+ ### Using defined resource classes
57
+
58
+ BadBill.new "billo", "18e40e14"
59
+
60
+ BadBill::Invoice.all
61
+ # => [#<BadBill::Invoice:0x000000024caf98 @id="1" @data={...}>], ...]
62
+
63
+ invoice = BadBill::Invoice.find(1)
64
+ invoice.pdf
65
+ # => {"id"=>"1",
66
+ # "created"=>"2012-09-17T13:58:42+02:00",
67
+ # "invoice_id"=>"322791",
68
+ # "filename"=>"Invoice 322791.pdf",
69
+ # "mimetype"=>"application/pdf",
70
+ # "filesize"=>"90811",
71
+ # "base64file"=>"JVBERi0xLjM..."}
72
+ invoice.delete
73
+ # => true
74
+
75
+ BadBill::Invoice.create client_id: 1, date: "2012-09-18", note: "Thank you for your order", ...
76
+
77
+ ## Documentation
78
+
79
+ Documentation is online at [rubydoc.info](http://rubydoc.info/github/badboy/badbill/master/frames).
80
+
81
+ Generate locale documentation with `rake doc` ([yard](http://yardoc.org/) required).
82
+ Required Parameters and possible values won't be documentated here. See the [API documentation][apidocu] for that.
83
+
84
+ ## Contribute
85
+
86
+ See [CONTRIBUTING.md](/badboy/badbill/blob/master/CONTRIBUTING.md) for info.
87
+
88
+ ## License
89
+
90
+ See [LICENSE](/badboy/badbill/blob/master/LICENSE) for info.
91
+
92
+ [apidocu]: http://www.billomat.com/en/api/
data/Rakefile ADDED
@@ -0,0 +1,85 @@
1
+ require 'date'
2
+ require 'fileutils'
3
+ require 'rspec/core/rake_task'
4
+ RSpec::Core::RakeTask.new(:spec)
5
+
6
+
7
+ require 'yard'
8
+ YARD::Rake::YardocTask.new do |t|
9
+ t.files = ['lib/**/*.rb']
10
+ end
11
+
12
+ task :default => :spec
13
+ task :doc => :yard
14
+
15
+ ## helper functions
16
+
17
+ def name
18
+ @name ||= Dir['*.gemspec'].first.split('.').first
19
+ end
20
+
21
+ def version
22
+ line = File.read("lib/#{name}.rb")[/^\s*VERSION\s*=\s*.*/]
23
+ line.match(/.*VERSION\s*=\s*['"](.*)['"]/)[1]
24
+ end
25
+
26
+ def gemspec_file
27
+ "#{name}.gemspec"
28
+ end
29
+
30
+ def gem_file
31
+ "#{name}-#{version}.gem"
32
+ end
33
+
34
+ def replace_header(head, header_name)
35
+ head.sub!(/(\.#{header_name}\s*= ').*'/) { "#{$1}#{send(header_name)}'"}
36
+ end
37
+
38
+ desc "Open an irb session preloaded with this library"
39
+ task :console do
40
+ sh "irb -rubygems -r ./lib/#{name}.rb"
41
+ end
42
+
43
+ ## release management tasks
44
+
45
+ desc "Commit, create tag v#{version} and build and push #{gem_file} to Rubygems"
46
+ task :release => :build do
47
+ sh "git commit --allow-empty -a -m 'Release #{version}'"
48
+ sh "git tag v#{version}"
49
+ sh "git push origin"
50
+ sh "git push origin v#{version}"
51
+ sh "gem push pkg/#{gem_file}"
52
+ end
53
+
54
+ desc "Build #{gem_file} into the pkg directory"
55
+ task :build => :gemspec do
56
+ FileUtils.mkdir_p 'pkg'
57
+ sh "gem build #{gemspec_file}"
58
+ sh "mv #{gem_file} pkg"
59
+ end
60
+
61
+ desc "Generate #{gemspec_file}"
62
+ task :gemspec do
63
+ # read spec file and split out manifest section
64
+ spec = File.read(gemspec_file)
65
+ head, manifest, tail = spec.split(" # = MANIFEST =\n")
66
+
67
+ # replace name version and date
68
+ replace_header(head, :name)
69
+ replace_header(head, :version)
70
+
71
+ # determine file list from git ls-files
72
+ files = `git ls-files`.
73
+ split("\n").
74
+ sort.
75
+ reject { |file| file =~ /^\./ }.
76
+ reject { |file| file =~ /^(doc|coverage|pkg)/ }.
77
+ map { |file| " ./#{file}" }.
78
+ join("\n")
79
+
80
+ # piece file back together and write
81
+ manifest = " s.files = %w[\n#{files}\n ]\n"
82
+ spec = [head, manifest, tail].join(" # = MANIFEST =\n")
83
+ File.open(gemspec_file, 'w') { |io| io.write(spec) }
84
+ puts "Updated #{gemspec_file}"
85
+ end
@@ -0,0 +1,118 @@
1
+ # encoding: utf-8
2
+
3
+ class BadBill
4
+ class BaseResource
5
+ include BadBill::Resource
6
+ extend BadBill::Resource
7
+
8
+ include BadBill::ForwardMethods
9
+
10
+ # ID of the resource.
11
+ attr_reader :id
12
+ # All data in a Hash.
13
+ attr_accessor :data
14
+
15
+ # Public: Create new resource object.
16
+ #
17
+ # @param [String,Integer] id The ID of the resource.
18
+ # @param [Hashie::Mash] data Resource data.
19
+ #
20
+ # All resources have an ID and data. Methods are proxied to the data object.
21
+ #
22
+ # @return [Resource] A new resource.
23
+ def initialize id, data
24
+ @id = id
25
+ @data = data
26
+ end
27
+
28
+ # Get all resources of this type.
29
+ #
30
+ # @param [Hash] filter An Hash of filter parameters,
31
+ # see the online documentation for allowed values.
32
+ #
33
+ # @return [Array<Resource>] All found resources.
34
+ def self.all filter={}
35
+ all = get resource_name, filter
36
+ all.__send__(resource_name).__send__(resource_name_singular).map do |res|
37
+ new res.id, res
38
+ end
39
+ end
40
+
41
+ # Get the resource with the given ID.
42
+ #
43
+ # @param [String,Integer] id ID of the resource.
44
+ #
45
+ # @return [Resource] New resource with id and data set.
46
+ def self.find id
47
+ c = get resource_name, id
48
+ new id, c.__send__(resource_name_singular)
49
+ end
50
+
51
+ # Create a new resource with the given parameters.
52
+ #
53
+ # @param [Hash] params A Hash-like object of data.
54
+ # See the online documentation for allowed values.
55
+ #
56
+ # @return [Resource] New resource with id and data set.
57
+ def self.create params
58
+ res = post(resource_name, {resource_name_singular => params})
59
+ res_data = res.__send__(resource_name_singular)
60
+ new res_data.id, res_data
61
+ end
62
+
63
+ # Save any changed data.
64
+ #
65
+ # @return [Boolean] True if successfull, false otherwise.
66
+ def save
67
+ @data.id = id
68
+ resp = put resource_name, id, {resource_name_singular => @data}
69
+ !resp
70
+ end
71
+
72
+ # Delete this resource.
73
+ #
74
+ # @return [Boolean] True if successfull, false otherwise.
75
+ def delete
76
+ # Hack: We can't call #delete here, because we ARE #delete
77
+ resp = call resource_name, id, nil, :delete
78
+ !resp
79
+ end
80
+
81
+ # Protect overriding of the ID.
82
+ #
83
+ # @raise [NoMethodError] because the ID may not be overwritten.
84
+ def id= *args
85
+ raise NoMethodError, "undefined method `id=' for '#{self.inspect.sub(/ .+/, '>')}`"
86
+ end
87
+
88
+ private
89
+
90
+ def resource_name_singular
91
+ self.class.resource_name_singular
92
+ end
93
+
94
+ def resource_name
95
+ self.class.resource_name
96
+ end
97
+
98
+ # The singular resource name for use with the API.
99
+ #
100
+ # By default this is just the Class name, but it may be overridden.
101
+ # Make sure to overwrite resource_name, too.
102
+ #
103
+ # @return [String] The singular resource name.
104
+ def self.resource_name_singular
105
+ @resource_name_singular ||= self.name.split(/::/).last.downcase
106
+ end
107
+
108
+ # The resource name (plural) for use with the API.
109
+ #
110
+ # By default this is just the `resource_name_singular` with an appended 's'
111
+ # See #resource_name_singular
112
+ #
113
+ # @return [String] The resource name.
114
+ def self.resource_name
115
+ @resource_name ||= "#{resource_name_singular}s"
116
+ end
117
+ end
118
+ end
@@ -0,0 +1,32 @@
1
+ # encoding: utf-8
2
+
3
+ class BadBill
4
+ # The clients resource handles all clients.
5
+ #
6
+ # See http://www.billomat.com/en/api/clients/
7
+ class Client < BaseResource
8
+ attr_writer :myself
9
+
10
+ def initialize id, data
11
+ super
12
+ @myself = false
13
+ end
14
+
15
+ # Fetch information about yourself.
16
+ #
17
+ # @return [Client] A new resource.
18
+ def self.myself
19
+ c = get 'clients', 'myself'
20
+ client = new c.client.id, c.client
21
+ client.myself = true
22
+ client
23
+ end
24
+
25
+ # Indicates wether this resource is yourself or not.
26
+ #
27
+ # @return [Boolean] Wether or not this resource is yourself.
28
+ def myself?
29
+ @myself
30
+ end
31
+ end
32
+ end
@@ -0,0 +1,30 @@
1
+ # encoding: utf-8
2
+
3
+ class BadBill
4
+ # Forward all methods to an underlying object called data.
5
+ #
6
+ # This acts like a proxy object.
7
+ module ForwardMethods
8
+ # Respond to method_missing to send down the line.
9
+ #
10
+ # As Hashie::Mash#method_missing does not respond with true to an
11
+ # assignment request, we only check for the method name without the equal sign.
12
+ def method_missing(method_name, *arguments, &block)
13
+ if data.respond_to?(method_name.to_s.sub(/=$/, ''))
14
+ data.send(method_name, *arguments, &block)
15
+ else
16
+ super
17
+ end
18
+ end
19
+
20
+ # Proxy respond_to? to the underlying object if needed.
21
+ #
22
+ # If playing with method_missing define respond_to? too.
23
+ # See http://robots.thoughtbot.com/post/28335346416/always-define-respond-to-missing-when-overriding
24
+ #
25
+ # @return [Boolean] True if the class itself or the proxied object responds to the given method.
26
+ def respond_to?(method_name, include_private = false)
27
+ super || data.respond_to?(method_name, include_private)
28
+ end
29
+ end
30
+ end
@@ -0,0 +1,106 @@
1
+ # encoding: utf-8
2
+
3
+ require 'base64'
4
+
5
+ class BadBill
6
+ # The resource handles all invoices.
7
+ #
8
+ # See http://www.billomat.com/en/api/invoices
9
+ class Invoice < BaseResource
10
+ # Get the PDF invoice.
11
+ #
12
+ #
13
+ # @return [Hashie::Mash] Hash containing the ID, filesize, base64file and
14
+ # other parameters.
15
+ # See http://www.billomat.com/en/api/invoices for
16
+ # all parameters.
17
+ def pdf
18
+ resp = get resource_name, "#{id}/pdf"
19
+ resp.pdf
20
+ end
21
+
22
+ # Closes a statement in the draft status (DRAFT). Here, the
23
+ # status of open (OPEN) or overdue (OVERDUE) is set and a PDF is
24
+ # generated and stored in the file system.
25
+ #
26
+ # @param [String,Integer] template_id ID of the Template used to create pdf,
27
+ # If not set, default template is used.
28
+ #
29
+ # @return [Boolean] Wether or not the call was successfull.
30
+ def complete template_id=nil
31
+ data = { complete: {} }
32
+ data[:complete] = { template_id: template_id } if template_id
33
+ resp = put resource_name, "#{id}/complete", data
34
+
35
+ !resp
36
+ end
37
+
38
+ # Cancel an invoice.
39
+ #
40
+ # @return [Boolean] Wether or not the call was successfull.
41
+ def cancel
42
+ resp = put resource_name, "#{id}/cancel"
43
+ !resp
44
+ end
45
+
46
+ # Sends an invoice by email.
47
+ #
48
+ # @param [String] to Recipient of the email.
49
+ # cc and bcc can be set in the `more` Hash.
50
+ # @param [String] from Sender email address.
51
+ # @param [String] subject Subject for the email.
52
+ # @param [String] body Body for the email.
53
+ # @param [Hash] more Hash-like object including more options.
54
+ # See online documentation for all allowed parameters.
55
+ #
56
+ # from, subject or body can be replaced by more.
57
+ #
58
+ # @return [Boolean] Wether or not the call was successfull.
59
+ def email to, from=nil, subject=nil, body=nil, more={}
60
+ data = { recipients: {} }
61
+
62
+ if more.empty? && from.kind_of?(Hash)
63
+ more = from
64
+ from = nil
65
+ end
66
+
67
+ if more.empty? && subject.kind_of?(Hash)
68
+ more = subject
69
+ subject = nil
70
+ end
71
+
72
+ if more.empty? && body.kind_of?(Hash)
73
+ more = body
74
+ body = nil
75
+ end
76
+
77
+ data[:from] = from if from
78
+ data[:subject] = subject if subject
79
+ data[:body] = body if body
80
+
81
+ data.merge! more
82
+ data[:recipients][:to] = to
83
+
84
+ resp = post resource_name, "#{id}/email", email: data
85
+ !resp
86
+ end
87
+
88
+ # Uploads a digital signature for a given invoice.
89
+ #
90
+ # The status of the invoice may not be DRAFT.
91
+ #
92
+ # @param [String,#read] file file name or a object responding to #read.
93
+ # This will be base64-encoded before send.
94
+ #
95
+ # @return [Boolean] false if status is DRAFT or another error occured. true if everything was successfull.
96
+ def upload_signature file
97
+ return false if data.status == 'DRAFT'
98
+
99
+ file = File.open(file, 'r') unless file.respond_to?(:read)
100
+
101
+ base64 = Base64.encode64 file.read
102
+ put resource_name, "#{id}/upload-signature", { signature: { base64file: base64 } }
103
+ true
104
+ end
105
+ end
106
+ end
@@ -0,0 +1,34 @@
1
+ # encoding: utf-8
2
+
3
+ class BadBill
4
+ # Forward requests to the underlying connection object.
5
+ #
6
+ # This module is included in BadBill::BaseResource.
7
+ module Resource
8
+ # @param (see BadBill#get)
9
+ def get resource, id='', options=nil
10
+ call resource, id, options, :get
11
+ end
12
+
13
+ # @param (see BadBill#post)
14
+ def post resource, id='', options=nil
15
+ call resource, id, options, :post
16
+ end
17
+
18
+ # @param (see BadBill#put)
19
+ def put resource, id='', options=nil
20
+ call resource, id, options, :put
21
+ end
22
+
23
+ # @param (see BadBill#delete)
24
+ def delete resource, id='', options=nil
25
+ call resource, id, options, :delete
26
+ end
27
+
28
+ # @param (see BadBill#call)
29
+ def call resource, id='', options=nil, method=:get
30
+ raise BadBill::NoConnection, "No connection. Use BadBill.new first." if BadBill.connection.nil?
31
+ BadBill.connection.call resource, id, options, method
32
+ end
33
+ end
34
+ end