gnucash 1.0.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- data/.gitignore +20 -0
 - data/.rspec +2 -0
 - data/Gemfile +4 -0
 - data/LICENSE.txt +22 -0
 - data/README.md +29 -0
 - data/Rakefile +23 -0
 - data/gnucash.gemspec +27 -0
 - data/lib/gnucash.rb +17 -0
 - data/lib/gnucash/account.rb +91 -0
 - data/lib/gnucash/account_transaction.rb +23 -0
 - data/lib/gnucash/book.rb +84 -0
 - data/lib/gnucash/transaction.rb +41 -0
 - data/lib/gnucash/value.rb +97 -0
 - data/lib/gnucash/version.rb +4 -0
 - data/spec/books/sample-text.gnucash +30246 -0
 - data/spec/books/sample.gnucash +0 -0
 - data/spec/gnucash/account_spec.rb +40 -0
 - data/spec/gnucash/book_spec.rb +39 -0
 - data/spec/gnucash/transaction_spec.rb +38 -0
 - data/spec/gnucash/value_spec.rb +96 -0
 - data/spec/gnucash_spec.rb +12 -0
 - data/spec/spec_helper.rb +5 -0
 - metadata +172 -0
 
    
        data/.gitignore
    ADDED
    
    | 
         @@ -0,0 +1,20 @@ 
     | 
|
| 
      
 1 
     | 
    
         
            +
            *.gem
         
     | 
| 
      
 2 
     | 
    
         
            +
            *.rbc
         
     | 
| 
      
 3 
     | 
    
         
            +
            .bundle
         
     | 
| 
      
 4 
     | 
    
         
            +
            .config
         
     | 
| 
      
 5 
     | 
    
         
            +
            .yardoc
         
     | 
| 
      
 6 
     | 
    
         
            +
            Gemfile.lock
         
     | 
| 
      
 7 
     | 
    
         
            +
            InstalledFiles
         
     | 
| 
      
 8 
     | 
    
         
            +
            _yardoc
         
     | 
| 
      
 9 
     | 
    
         
            +
            coverage
         
     | 
| 
      
 10 
     | 
    
         
            +
            doc/
         
     | 
| 
      
 11 
     | 
    
         
            +
            lib/bundler/man
         
     | 
| 
      
 12 
     | 
    
         
            +
            pkg
         
     | 
| 
      
 13 
     | 
    
         
            +
            rdoc
         
     | 
| 
      
 14 
     | 
    
         
            +
            spec/reports
         
     | 
| 
      
 15 
     | 
    
         
            +
            test/tmp
         
     | 
| 
      
 16 
     | 
    
         
            +
            test/version_tmp
         
     | 
| 
      
 17 
     | 
    
         
            +
            tmp
         
     | 
| 
      
 18 
     | 
    
         
            +
            test.rb
         
     | 
| 
      
 19 
     | 
    
         
            +
            spec/books/*.gnucash.*.*
         
     | 
| 
      
 20 
     | 
    
         
            +
            spec/books/*.LCK
         
     | 
    
        data/.rspec
    ADDED
    
    
    
        data/Gemfile
    ADDED
    
    
    
        data/LICENSE.txt
    ADDED
    
    | 
         @@ -0,0 +1,22 @@ 
     | 
|
| 
      
 1 
     | 
    
         
            +
            Copyright (c) 2013 Josh Holtrop
         
     | 
| 
      
 2 
     | 
    
         
            +
             
     | 
| 
      
 3 
     | 
    
         
            +
            MIT License
         
     | 
| 
      
 4 
     | 
    
         
            +
             
     | 
| 
      
 5 
     | 
    
         
            +
            Permission is hereby granted, free of charge, to any person obtaining
         
     | 
| 
      
 6 
     | 
    
         
            +
            a copy of this software and associated documentation files (the
         
     | 
| 
      
 7 
     | 
    
         
            +
            "Software"), to deal in the Software without restriction, including
         
     | 
| 
      
 8 
     | 
    
         
            +
            without limitation the rights to use, copy, modify, merge, publish,
         
     | 
| 
      
 9 
     | 
    
         
            +
            distribute, sublicense, and/or sell copies of the Software, and to
         
     | 
| 
      
 10 
     | 
    
         
            +
            permit persons to whom the Software is furnished to do so, subject to
         
     | 
| 
      
 11 
     | 
    
         
            +
            the following conditions:
         
     | 
| 
      
 12 
     | 
    
         
            +
             
     | 
| 
      
 13 
     | 
    
         
            +
            The above copyright notice and this permission notice shall be
         
     | 
| 
      
 14 
     | 
    
         
            +
            included in all copies or substantial portions of the Software.
         
     | 
| 
      
 15 
     | 
    
         
            +
             
     | 
| 
      
 16 
     | 
    
         
            +
            THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND,
         
     | 
| 
      
 17 
     | 
    
         
            +
            EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF
         
     | 
| 
      
 18 
     | 
    
         
            +
            MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND
         
     | 
| 
      
 19 
     | 
    
         
            +
            NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE
         
     | 
| 
      
 20 
     | 
    
         
            +
            LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION
         
     | 
| 
      
 21 
     | 
    
         
            +
            OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION
         
     | 
| 
      
 22 
     | 
    
         
            +
            WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.
         
     | 
    
        data/README.md
    ADDED
    
    | 
         @@ -0,0 +1,29 @@ 
     | 
|
| 
      
 1 
     | 
    
         
            +
            # Gnucash
         
     | 
| 
      
 2 
     | 
    
         
            +
             
     | 
| 
      
 3 
     | 
    
         
            +
            Ruby library for extracting data from GnuCash data files
         
     | 
| 
      
 4 
     | 
    
         
            +
             
     | 
| 
      
 5 
     | 
    
         
            +
            ## Installation
         
     | 
| 
      
 6 
     | 
    
         
            +
             
     | 
| 
      
 7 
     | 
    
         
            +
            Add this line to your application's Gemfile:
         
     | 
| 
      
 8 
     | 
    
         
            +
             
     | 
| 
      
 9 
     | 
    
         
            +
                gem 'gnucash'
         
     | 
| 
      
 10 
     | 
    
         
            +
             
     | 
| 
      
 11 
     | 
    
         
            +
            And then execute:
         
     | 
| 
      
 12 
     | 
    
         
            +
             
     | 
| 
      
 13 
     | 
    
         
            +
                $ bundle
         
     | 
| 
      
 14 
     | 
    
         
            +
             
     | 
| 
      
 15 
     | 
    
         
            +
            Or install it yourself as:
         
     | 
| 
      
 16 
     | 
    
         
            +
             
     | 
| 
      
 17 
     | 
    
         
            +
                $ gem install gnucash
         
     | 
| 
      
 18 
     | 
    
         
            +
             
     | 
| 
      
 19 
     | 
    
         
            +
            ## Usage
         
     | 
| 
      
 20 
     | 
    
         
            +
             
     | 
| 
      
 21 
     | 
    
         
            +
                book = Gnucash.open("MyBook.gnucash")
         
     | 
| 
      
 22 
     | 
    
         
            +
             
     | 
| 
      
 23 
     | 
    
         
            +
            ## Contributing
         
     | 
| 
      
 24 
     | 
    
         
            +
             
     | 
| 
      
 25 
     | 
    
         
            +
            1. Fork it
         
     | 
| 
      
 26 
     | 
    
         
            +
            2. Create your feature branch (`git checkout -b my-new-feature`)
         
     | 
| 
      
 27 
     | 
    
         
            +
            3. Commit your changes (`git commit -am 'Add some feature'`)
         
     | 
| 
      
 28 
     | 
    
         
            +
            4. Push to the branch (`git push origin my-new-feature`)
         
     | 
| 
      
 29 
     | 
    
         
            +
            5. Create new Pull Request
         
     | 
    
        data/Rakefile
    ADDED
    
    | 
         @@ -0,0 +1,23 @@ 
     | 
|
| 
      
 1 
     | 
    
         
            +
            require "bundler/gem_tasks"
         
     | 
| 
      
 2 
     | 
    
         
            +
            require "rake/clean"
         
     | 
| 
      
 3 
     | 
    
         
            +
            require "rspec/core/rake_task"
         
     | 
| 
      
 4 
     | 
    
         
            +
            require "rdoc/task"
         
     | 
| 
      
 5 
     | 
    
         
            +
            require "yard"
         
     | 
| 
      
 6 
     | 
    
         
            +
             
     | 
| 
      
 7 
     | 
    
         
            +
            CLEAN.include "rdoc"
         
     | 
| 
      
 8 
     | 
    
         
            +
            CLEAN.include "pkg"
         
     | 
| 
      
 9 
     | 
    
         
            +
            CLEAN.include "coverage"
         
     | 
| 
      
 10 
     | 
    
         
            +
             
     | 
| 
      
 11 
     | 
    
         
            +
            YARD::Rake::YardocTask.new do |yard|
         
     | 
| 
      
 12 
     | 
    
         
            +
              yard.files = ['lib/**/*.rb']
         
     | 
| 
      
 13 
     | 
    
         
            +
            end
         
     | 
| 
      
 14 
     | 
    
         
            +
             
     | 
| 
      
 15 
     | 
    
         
            +
            RSpec::Core::RakeTask.new("spec")
         
     | 
| 
      
 16 
     | 
    
         
            +
             
     | 
| 
      
 17 
     | 
    
         
            +
            Rake::RDocTask.new(:rdoc) do |rdoc|
         
     | 
| 
      
 18 
     | 
    
         
            +
              rdoc.rdoc_dir = 'rdoc'
         
     | 
| 
      
 19 
     | 
    
         
            +
              rdoc.title = "Ruby library for extracting data from GnuCash data files"
         
     | 
| 
      
 20 
     | 
    
         
            +
              rdoc.rdoc_files.include('lib/**/*.rb')
         
     | 
| 
      
 21 
     | 
    
         
            +
            end
         
     | 
| 
      
 22 
     | 
    
         
            +
             
     | 
| 
      
 23 
     | 
    
         
            +
            task :default => :spec
         
     | 
    
        data/gnucash.gemspec
    ADDED
    
    | 
         @@ -0,0 +1,27 @@ 
     | 
|
| 
      
 1 
     | 
    
         
            +
            # -*- encoding: utf-8 -*-
         
     | 
| 
      
 2 
     | 
    
         
            +
            lib = File.expand_path('../lib', __FILE__)
         
     | 
| 
      
 3 
     | 
    
         
            +
            $LOAD_PATH.unshift(lib) unless $LOAD_PATH.include?(lib)
         
     | 
| 
      
 4 
     | 
    
         
            +
            require 'gnucash/version'
         
     | 
| 
      
 5 
     | 
    
         
            +
             
     | 
| 
      
 6 
     | 
    
         
            +
            Gem::Specification.new do |gem|
         
     | 
| 
      
 7 
     | 
    
         
            +
              gem.name          = "gnucash"
         
     | 
| 
      
 8 
     | 
    
         
            +
              gem.version       = Gnucash::VERSION
         
     | 
| 
      
 9 
     | 
    
         
            +
              gem.authors       = ["Josh Holtrop"]
         
     | 
| 
      
 10 
     | 
    
         
            +
              gem.email         = ["jholtrop@gmail.com"]
         
     | 
| 
      
 11 
     | 
    
         
            +
              gem.description   = %q{Ruby library for extracting data from GnuCash data files}
         
     | 
| 
      
 12 
     | 
    
         
            +
              gem.summary       = %q{Extract data from GnuCash data files}
         
     | 
| 
      
 13 
     | 
    
         
            +
              gem.homepage      = ""
         
     | 
| 
      
 14 
     | 
    
         
            +
             
     | 
| 
      
 15 
     | 
    
         
            +
              gem.files         = `git ls-files`.split($/)
         
     | 
| 
      
 16 
     | 
    
         
            +
              gem.executables   = gem.files.grep(%r{^bin/}).map{ |f| File.basename(f) }
         
     | 
| 
      
 17 
     | 
    
         
            +
              gem.test_files    = gem.files.grep(%r{^(test|spec|features)/})
         
     | 
| 
      
 18 
     | 
    
         
            +
              gem.require_paths = ["lib"]
         
     | 
| 
      
 19 
     | 
    
         
            +
             
     | 
| 
      
 20 
     | 
    
         
            +
              gem.add_dependency "nokogiri"
         
     | 
| 
      
 21 
     | 
    
         
            +
             
     | 
| 
      
 22 
     | 
    
         
            +
              gem.add_development_dependency "simplecov"
         
     | 
| 
      
 23 
     | 
    
         
            +
              gem.add_development_dependency "rspec"
         
     | 
| 
      
 24 
     | 
    
         
            +
              gem.add_development_dependency "rspec-core"
         
     | 
| 
      
 25 
     | 
    
         
            +
              gem.add_development_dependency "rspec-expectations"
         
     | 
| 
      
 26 
     | 
    
         
            +
              gem.add_development_dependency "rspec-mocks"
         
     | 
| 
      
 27 
     | 
    
         
            +
            end
         
     | 
    
        data/lib/gnucash.rb
    ADDED
    
    | 
         @@ -0,0 +1,17 @@ 
     | 
|
| 
      
 1 
     | 
    
         
            +
            require "gnucash/account"
         
     | 
| 
      
 2 
     | 
    
         
            +
            require "gnucash/account_transaction"
         
     | 
| 
      
 3 
     | 
    
         
            +
            require "gnucash/book"
         
     | 
| 
      
 4 
     | 
    
         
            +
            require "gnucash/transaction"
         
     | 
| 
      
 5 
     | 
    
         
            +
            require "gnucash/value"
         
     | 
| 
      
 6 
     | 
    
         
            +
            require "gnucash/version"
         
     | 
| 
      
 7 
     | 
    
         
            +
             
     | 
| 
      
 8 
     | 
    
         
            +
            # Namespace module for gnucash gem functionality
         
     | 
| 
      
 9 
     | 
    
         
            +
            module Gnucash
         
     | 
| 
      
 10 
     | 
    
         
            +
              # Open a GnuCash book from file.
         
     | 
| 
      
 11 
     | 
    
         
            +
              # The file can be either a plain-text XML file or a gzipped XML file.
         
     | 
| 
      
 12 
     | 
    
         
            +
              # === Arguments
         
     | 
| 
      
 13 
     | 
    
         
            +
              # +fname+ _String_:: Name of the file to open.
         
     | 
| 
      
 14 
     | 
    
         
            +
              def self.open(fname)
         
     | 
| 
      
 15 
     | 
    
         
            +
                Book.new(fname)
         
     | 
| 
      
 16 
     | 
    
         
            +
              end
         
     | 
| 
      
 17 
     | 
    
         
            +
            end
         
     | 
| 
         @@ -0,0 +1,91 @@ 
     | 
|
| 
      
 1 
     | 
    
         
            +
            module Gnucash
         
     | 
| 
      
 2 
     | 
    
         
            +
              # Represent a GnuCash account object
         
     | 
| 
      
 3 
     | 
    
         
            +
              class Account
         
     | 
| 
      
 4 
     | 
    
         
            +
                # _String_: The name of the account (unqualified)
         
     | 
| 
      
 5 
     | 
    
         
            +
                attr_accessor :name
         
     | 
| 
      
 6 
     | 
    
         
            +
             
     | 
| 
      
 7 
     | 
    
         
            +
                # _String_: The account type (such as "EXPENSE")
         
     | 
| 
      
 8 
     | 
    
         
            +
                attr_accessor :type
         
     | 
| 
      
 9 
     | 
    
         
            +
             
     | 
| 
      
 10 
     | 
    
         
            +
                # _String_: The GUID of the account
         
     | 
| 
      
 11 
     | 
    
         
            +
                attr_accessor :id
         
     | 
| 
      
 12 
     | 
    
         
            +
             
     | 
| 
      
 13 
     | 
    
         
            +
                # _Array_: List of _AccountTransaction_ transactions associated with this
         
     | 
| 
      
 14 
     | 
    
         
            +
                # account.
         
     | 
| 
      
 15 
     | 
    
         
            +
                attr_accessor :transactions
         
     | 
| 
      
 16 
     | 
    
         
            +
             
     | 
| 
      
 17 
     | 
    
         
            +
                # Create an Account object.
         
     | 
| 
      
 18 
     | 
    
         
            +
                # === Arguments
         
     | 
| 
      
 19 
     | 
    
         
            +
                # +book+ _Book_:: The Gnucash::Book containing the account
         
     | 
| 
      
 20 
     | 
    
         
            +
                # +node+ _Nokogiri::XML::Node_:: Nokogiri XML node
         
     | 
| 
      
 21 
     | 
    
         
            +
                def initialize(book, node)
         
     | 
| 
      
 22 
     | 
    
         
            +
                  @book = book
         
     | 
| 
      
 23 
     | 
    
         
            +
                  @node = node
         
     | 
| 
      
 24 
     | 
    
         
            +
                  @name = node.xpath('act:name').text
         
     | 
| 
      
 25 
     | 
    
         
            +
                  @type = node.xpath('act:type').text
         
     | 
| 
      
 26 
     | 
    
         
            +
                  @id = node.xpath('act:id').text
         
     | 
| 
      
 27 
     | 
    
         
            +
                  @parent_id = node.xpath('act:parent').text
         
     | 
| 
      
 28 
     | 
    
         
            +
                  @parent_id = nil if @parent_id == ""
         
     | 
| 
      
 29 
     | 
    
         
            +
                  @transactions = []
         
     | 
| 
      
 30 
     | 
    
         
            +
                  @balances = []
         
     | 
| 
      
 31 
     | 
    
         
            +
                end
         
     | 
| 
      
 32 
     | 
    
         
            +
             
     | 
| 
      
 33 
     | 
    
         
            +
                # Return the fully qualified account name
         
     | 
| 
      
 34 
     | 
    
         
            +
                def full_name
         
     | 
| 
      
 35 
     | 
    
         
            +
                  prefix = ""
         
     | 
| 
      
 36 
     | 
    
         
            +
                  if @parent_id
         
     | 
| 
      
 37 
     | 
    
         
            +
                    parent = @book.find_account_by_id(@parent_id)
         
     | 
| 
      
 38 
     | 
    
         
            +
                    if parent and parent.type != 'ROOT'
         
     | 
| 
      
 39 
     | 
    
         
            +
                      prefix = parent.full_name + "::"
         
     | 
| 
      
 40 
     | 
    
         
            +
                    end
         
     | 
| 
      
 41 
     | 
    
         
            +
                  end
         
     | 
| 
      
 42 
     | 
    
         
            +
                  prefix + name
         
     | 
| 
      
 43 
     | 
    
         
            +
                end
         
     | 
| 
      
 44 
     | 
    
         
            +
             
     | 
| 
      
 45 
     | 
    
         
            +
                # Internal method used to associate a transaction with the account
         
     | 
| 
      
 46 
     | 
    
         
            +
                def add_transaction(act_txn)
         
     | 
| 
      
 47 
     | 
    
         
            +
                  @transactions << act_txn
         
     | 
| 
      
 48 
     | 
    
         
            +
                end
         
     | 
| 
      
 49 
     | 
    
         
            +
             
     | 
| 
      
 50 
     | 
    
         
            +
                # Internal method used to complete initialization of the Account after
         
     | 
| 
      
 51 
     | 
    
         
            +
                # all transactions have been associated with it.
         
     | 
| 
      
 52 
     | 
    
         
            +
                def finalize
         
     | 
| 
      
 53 
     | 
    
         
            +
                  @transactions.sort! { |a, b| a.date <=> b.date }
         
     | 
| 
      
 54 
     | 
    
         
            +
                  balance = Value.new(0)
         
     | 
| 
      
 55 
     | 
    
         
            +
                  @balances = @transactions.map do |act_txn|
         
     | 
| 
      
 56 
     | 
    
         
            +
                    balance += act_txn.value
         
     | 
| 
      
 57 
     | 
    
         
            +
                    {
         
     | 
| 
      
 58 
     | 
    
         
            +
                      date: act_txn.date,
         
     | 
| 
      
 59 
     | 
    
         
            +
                      value: balance,
         
     | 
| 
      
 60 
     | 
    
         
            +
                    }
         
     | 
| 
      
 61 
     | 
    
         
            +
                  end
         
     | 
| 
      
 62 
     | 
    
         
            +
                end
         
     | 
| 
      
 63 
     | 
    
         
            +
             
     | 
| 
      
 64 
     | 
    
         
            +
                # Return the final balance of the account as a _Gnucash::Value_
         
     | 
| 
      
 65 
     | 
    
         
            +
                def final_balance
         
     | 
| 
      
 66 
     | 
    
         
            +
                  return Value.new(0) unless @balances.size > 0
         
     | 
| 
      
 67 
     | 
    
         
            +
                  @balances.last[:value]
         
     | 
| 
      
 68 
     | 
    
         
            +
                end
         
     | 
| 
      
 69 
     | 
    
         
            +
             
     | 
| 
      
 70 
     | 
    
         
            +
                # Return the balance of the account as of the date given as a
         
     | 
| 
      
 71 
     | 
    
         
            +
                # _Gnucash::Value_. Transactions that occur on the given date are included
         
     | 
| 
      
 72 
     | 
    
         
            +
                # in the returned balance.
         
     | 
| 
      
 73 
     | 
    
         
            +
                def balance_on(date)
         
     | 
| 
      
 74 
     | 
    
         
            +
                  return Value.new(0) unless @balances.size > 0
         
     | 
| 
      
 75 
     | 
    
         
            +
                  return Value.new(0) if @balances.first[:date] > date
         
     | 
| 
      
 76 
     | 
    
         
            +
                  return @balances.last[:value] if date >= @balances.last[:date]
         
     | 
| 
      
 77 
     | 
    
         
            +
                  imin = 0
         
     | 
| 
      
 78 
     | 
    
         
            +
                  imax = @balances.size - 2
         
     | 
| 
      
 79 
     | 
    
         
            +
                  idx = imax / 2
         
     | 
| 
      
 80 
     | 
    
         
            +
                  until @balances[idx][:date] <= date and @balances[idx + 1][:date] > date
         
     | 
| 
      
 81 
     | 
    
         
            +
                    if @balances[idx][:date] <= date
         
     | 
| 
      
 82 
     | 
    
         
            +
                      imin = idx + 1
         
     | 
| 
      
 83 
     | 
    
         
            +
                    else
         
     | 
| 
      
 84 
     | 
    
         
            +
                      imax = idx
         
     | 
| 
      
 85 
     | 
    
         
            +
                    end
         
     | 
| 
      
 86 
     | 
    
         
            +
                    idx = (imin + imax) / 2
         
     | 
| 
      
 87 
     | 
    
         
            +
                  end
         
     | 
| 
      
 88 
     | 
    
         
            +
                  @balances[idx][:value]
         
     | 
| 
      
 89 
     | 
    
         
            +
                end
         
     | 
| 
      
 90 
     | 
    
         
            +
              end
         
     | 
| 
      
 91 
     | 
    
         
            +
            end
         
     | 
| 
         @@ -0,0 +1,23 @@ 
     | 
|
| 
      
 1 
     | 
    
         
            +
            module Gnucash
         
     | 
| 
      
 2 
     | 
    
         
            +
              # Class to link a transaction object to an Account.
         
     | 
| 
      
 3 
     | 
    
         
            +
              class AccountTransaction
         
     | 
| 
      
 4 
     | 
    
         
            +
                # _Gnucash::Value_: The transaction value for the linked account
         
     | 
| 
      
 5 
     | 
    
         
            +
                attr_accessor :value
         
     | 
| 
      
 6 
     | 
    
         
            +
             
     | 
| 
      
 7 
     | 
    
         
            +
                # Construct an AccountTransaction object.
         
     | 
| 
      
 8 
     | 
    
         
            +
                # This method is used internally when building a Transaction object.
         
     | 
| 
      
 9 
     | 
    
         
            +
                # === Arguments
         
     | 
| 
      
 10 
     | 
    
         
            +
                # +real_txn+ _Gnucash::Transaction_:: The linked Transaction object
         
     | 
| 
      
 11 
     | 
    
         
            +
                # +value+ _Gnucash::Value_::
         
     | 
| 
      
 12 
     | 
    
         
            +
                #   The value of the Transaction split for this account
         
     | 
| 
      
 13 
     | 
    
         
            +
                def initialize(real_txn, value)
         
     | 
| 
      
 14 
     | 
    
         
            +
                  @real_txn = real_txn
         
     | 
| 
      
 15 
     | 
    
         
            +
                  @value = value
         
     | 
| 
      
 16 
     | 
    
         
            +
                end
         
     | 
| 
      
 17 
     | 
    
         
            +
             
     | 
| 
      
 18 
     | 
    
         
            +
                # Pass through any missing method calls to the linked Transaction object
         
     | 
| 
      
 19 
     | 
    
         
            +
                def method_missing(*args)
         
     | 
| 
      
 20 
     | 
    
         
            +
                  @real_txn.send(*args)
         
     | 
| 
      
 21 
     | 
    
         
            +
                end
         
     | 
| 
      
 22 
     | 
    
         
            +
              end
         
     | 
| 
      
 23 
     | 
    
         
            +
            end
         
     | 
    
        data/lib/gnucash/book.rb
    ADDED
    
    | 
         @@ -0,0 +1,84 @@ 
     | 
|
| 
      
 1 
     | 
    
         
            +
            require "zlib"
         
     | 
| 
      
 2 
     | 
    
         
            +
            require "nokogiri"
         
     | 
| 
      
 3 
     | 
    
         
            +
             
     | 
| 
      
 4 
     | 
    
         
            +
            module Gnucash
         
     | 
| 
      
 5 
     | 
    
         
            +
              # Represent a GnuCash Book
         
     | 
| 
      
 6 
     | 
    
         
            +
              class Book
         
     | 
| 
      
 7 
     | 
    
         
            +
                # _Array_ of _Gnucash::Account_ objects in the book
         
     | 
| 
      
 8 
     | 
    
         
            +
                attr_accessor :accounts
         
     | 
| 
      
 9 
     | 
    
         
            +
             
     | 
| 
      
 10 
     | 
    
         
            +
                # _Array_ of _Gnucash::Transaction_ objects in the book
         
     | 
| 
      
 11 
     | 
    
         
            +
                attr_accessor :transactions
         
     | 
| 
      
 12 
     | 
    
         
            +
             
     | 
| 
      
 13 
     | 
    
         
            +
                # _String_ in "YYYY-MM-DD" format of the first transaction in the book
         
     | 
| 
      
 14 
     | 
    
         
            +
                attr_accessor :start_date
         
     | 
| 
      
 15 
     | 
    
         
            +
             
     | 
| 
      
 16 
     | 
    
         
            +
                # _String_ in "YYYY-MM-DD" format of the last transaction in the book
         
     | 
| 
      
 17 
     | 
    
         
            +
                attr_accessor :end_date
         
     | 
| 
      
 18 
     | 
    
         
            +
             
     | 
| 
      
 19 
     | 
    
         
            +
                # Construct a Book object.
         
     | 
| 
      
 20 
     | 
    
         
            +
                # Normally called internally by Gnucash.open()
         
     | 
| 
      
 21 
     | 
    
         
            +
                # === Arguments
         
     | 
| 
      
 22 
     | 
    
         
            +
                # +fname+ _String_:: The file name of the GnuCash file to open.
         
     | 
| 
      
 23 
     | 
    
         
            +
                def initialize(fname)
         
     | 
| 
      
 24 
     | 
    
         
            +
                  begin
         
     | 
| 
      
 25 
     | 
    
         
            +
                    @ng = Nokogiri.XML(Zlib::GzipReader.open(fname).read)
         
     | 
| 
      
 26 
     | 
    
         
            +
                  rescue Zlib::GzipFile::Error
         
     | 
| 
      
 27 
     | 
    
         
            +
                    @ng = Nokogiri.XML(File.read(fname))
         
     | 
| 
      
 28 
     | 
    
         
            +
                  end
         
     | 
| 
      
 29 
     | 
    
         
            +
                  book_nodes = @ng.xpath('/gnc-v2/gnc:book')
         
     | 
| 
      
 30 
     | 
    
         
            +
                  if book_nodes.count != 1
         
     | 
| 
      
 31 
     | 
    
         
            +
                    raise "Error: Expected to find one gnc:book entry"
         
     | 
| 
      
 32 
     | 
    
         
            +
                  end
         
     | 
| 
      
 33 
     | 
    
         
            +
                  @book_node = book_nodes.first
         
     | 
| 
      
 34 
     | 
    
         
            +
                  build_accounts
         
     | 
| 
      
 35 
     | 
    
         
            +
                  build_transactions
         
     | 
| 
      
 36 
     | 
    
         
            +
                  finalize
         
     | 
| 
      
 37 
     | 
    
         
            +
                end
         
     | 
| 
      
 38 
     | 
    
         
            +
             
     | 
| 
      
 39 
     | 
    
         
            +
                # Return a handle to the Account object that has the given GUID.
         
     | 
| 
      
 40 
     | 
    
         
            +
                # === Arguments
         
     | 
| 
      
 41 
     | 
    
         
            +
                # +id+ _String_:: GUID
         
     | 
| 
      
 42 
     | 
    
         
            +
                # === Return
         
     | 
| 
      
 43 
     | 
    
         
            +
                # _Gnucash::Account_ or +nil+
         
     | 
| 
      
 44 
     | 
    
         
            +
                def find_account_by_id(id)
         
     | 
| 
      
 45 
     | 
    
         
            +
                  @accounts.find { |a| a.id == id }
         
     | 
| 
      
 46 
     | 
    
         
            +
                end
         
     | 
| 
      
 47 
     | 
    
         
            +
             
     | 
| 
      
 48 
     | 
    
         
            +
                # Return a handle to the Account object that has the given fully-qualified
         
     | 
| 
      
 49 
     | 
    
         
            +
                # name.
         
     | 
| 
      
 50 
     | 
    
         
            +
                # === Arguments
         
     | 
| 
      
 51 
     | 
    
         
            +
                # +full_name+ _String_::
         
     | 
| 
      
 52 
     | 
    
         
            +
                #   Fully-qualified account name (ex: "Expenses::Auto::Gas")
         
     | 
| 
      
 53 
     | 
    
         
            +
                # === Return
         
     | 
| 
      
 54 
     | 
    
         
            +
                # _Gnucash::Account_ or +nil+
         
     | 
| 
      
 55 
     | 
    
         
            +
                def find_account_by_full_name(full_name)
         
     | 
| 
      
 56 
     | 
    
         
            +
                  @accounts.find { |a| a.full_name == full_name }
         
     | 
| 
      
 57 
     | 
    
         
            +
                end
         
     | 
| 
      
 58 
     | 
    
         
            +
             
     | 
| 
      
 59 
     | 
    
         
            +
                private
         
     | 
| 
      
 60 
     | 
    
         
            +
             
     | 
| 
      
 61 
     | 
    
         
            +
                def build_accounts
         
     | 
| 
      
 62 
     | 
    
         
            +
                  @accounts = @book_node.xpath('gnc:account').map do |act_node|
         
     | 
| 
      
 63 
     | 
    
         
            +
                    Account.new(self, act_node)
         
     | 
| 
      
 64 
     | 
    
         
            +
                  end
         
     | 
| 
      
 65 
     | 
    
         
            +
                end
         
     | 
| 
      
 66 
     | 
    
         
            +
             
     | 
| 
      
 67 
     | 
    
         
            +
                def build_transactions
         
     | 
| 
      
 68 
     | 
    
         
            +
                  @start_date = nil
         
     | 
| 
      
 69 
     | 
    
         
            +
                  @end_date = nil
         
     | 
| 
      
 70 
     | 
    
         
            +
                  @transactions = @book_node.xpath('gnc:transaction').map do |txn_node|
         
     | 
| 
      
 71 
     | 
    
         
            +
                    Transaction.new(self, txn_node).tap do |txn|
         
     | 
| 
      
 72 
     | 
    
         
            +
                      @start_date = txn.date if @start_date.nil? or txn.date < @start_date
         
     | 
| 
      
 73 
     | 
    
         
            +
                      @end_date = txn.date if @end_date.nil? or txn.date > @end_date
         
     | 
| 
      
 74 
     | 
    
         
            +
                    end
         
     | 
| 
      
 75 
     | 
    
         
            +
                  end
         
     | 
| 
      
 76 
     | 
    
         
            +
                end
         
     | 
| 
      
 77 
     | 
    
         
            +
             
     | 
| 
      
 78 
     | 
    
         
            +
                def finalize
         
     | 
| 
      
 79 
     | 
    
         
            +
                  @accounts.each do |account|
         
     | 
| 
      
 80 
     | 
    
         
            +
                    account.finalize
         
     | 
| 
      
 81 
     | 
    
         
            +
                  end
         
     | 
| 
      
 82 
     | 
    
         
            +
                end
         
     | 
| 
      
 83 
     | 
    
         
            +
              end
         
     | 
| 
      
 84 
     | 
    
         
            +
            end
         
     | 
| 
         @@ -0,0 +1,41 @@ 
     | 
|
| 
      
 1 
     | 
    
         
            +
            module Gnucash
         
     | 
| 
      
 2 
     | 
    
         
            +
              # Represent a GnuCash transaction.
         
     | 
| 
      
 3 
     | 
    
         
            +
              # Transactions have multiple splits with individual values.
         
     | 
| 
      
 4 
     | 
    
         
            +
              # Splits are created as AccountTransaction objects which are associated
         
     | 
| 
      
 5 
     | 
    
         
            +
              # with an individual account.
         
     | 
| 
      
 6 
     | 
    
         
            +
              class Transaction
         
     | 
| 
      
 7 
     | 
    
         
            +
                # _String_: The date of the transaction, in ISO format ("YYYY-MM-DD")
         
     | 
| 
      
 8 
     | 
    
         
            +
                attr_accessor :date
         
     | 
| 
      
 9 
     | 
    
         
            +
             
     | 
| 
      
 10 
     | 
    
         
            +
                # _String_: The GUID of the transaction
         
     | 
| 
      
 11 
     | 
    
         
            +
                attr_accessor :id
         
     | 
| 
      
 12 
     | 
    
         
            +
             
     | 
| 
      
 13 
     | 
    
         
            +
                # _String_: The description of the transaction
         
     | 
| 
      
 14 
     | 
    
         
            +
                attr_accessor :description
         
     | 
| 
      
 15 
     | 
    
         
            +
             
     | 
| 
      
 16 
     | 
    
         
            +
                # Create a new Transaction object
         
     | 
| 
      
 17 
     | 
    
         
            +
                # === Arguments
         
     | 
| 
      
 18 
     | 
    
         
            +
                # +book+ _Book_:: The Gnucash::Book containing the transaction
         
     | 
| 
      
 19 
     | 
    
         
            +
                # +node+ _Nokogiri::XML::Node_:: Nokogiri XML node
         
     | 
| 
      
 20 
     | 
    
         
            +
                def initialize(book, node)
         
     | 
| 
      
 21 
     | 
    
         
            +
                  @book = book
         
     | 
| 
      
 22 
     | 
    
         
            +
                  @node = node
         
     | 
| 
      
 23 
     | 
    
         
            +
                  @id = node.xpath('trn:id').text
         
     | 
| 
      
 24 
     | 
    
         
            +
                  @date = node.xpath('trn:date-posted/ts:date').text.split(' ').first
         
     | 
| 
      
 25 
     | 
    
         
            +
                  @description = node.xpath('trn:description').text
         
     | 
| 
      
 26 
     | 
    
         
            +
                  @splits = node.xpath('trn:splits/trn:split').map do |split_node|
         
     | 
| 
      
 27 
     | 
    
         
            +
                    value = Value.new(split_node.xpath('split:value').text)
         
     | 
| 
      
 28 
     | 
    
         
            +
                    account_id = split_node.xpath('split:account').text
         
     | 
| 
      
 29 
     | 
    
         
            +
                    account = @book.find_account_by_id(account_id)
         
     | 
| 
      
 30 
     | 
    
         
            +
                    unless account
         
     | 
| 
      
 31 
     | 
    
         
            +
                      raise "Could not find account with ID #{account_id} for transaction #{@id}"
         
     | 
| 
      
 32 
     | 
    
         
            +
                    end
         
     | 
| 
      
 33 
     | 
    
         
            +
                    account.add_transaction(AccountTransaction.new(self, value))
         
     | 
| 
      
 34 
     | 
    
         
            +
                    {
         
     | 
| 
      
 35 
     | 
    
         
            +
                      account_id: account_id,
         
     | 
| 
      
 36 
     | 
    
         
            +
                      value: value,
         
     | 
| 
      
 37 
     | 
    
         
            +
                    }
         
     | 
| 
      
 38 
     | 
    
         
            +
                  end
         
     | 
| 
      
 39 
     | 
    
         
            +
                end
         
     | 
| 
      
 40 
     | 
    
         
            +
              end
         
     | 
| 
      
 41 
     | 
    
         
            +
            end
         
     | 
| 
         @@ -0,0 +1,97 @@ 
     | 
|
| 
      
 1 
     | 
    
         
            +
            module Gnucash
         
     | 
| 
      
 2 
     | 
    
         
            +
              # Represent a currency value as an integer so that integer math can be used
         
     | 
| 
      
 3 
     | 
    
         
            +
              # for accuracy in computations.
         
     | 
| 
      
 4 
     | 
    
         
            +
              class Value
         
     | 
| 
      
 5 
     | 
    
         
            +
                include Comparable
         
     | 
| 
      
 6 
     | 
    
         
            +
             
     | 
| 
      
 7 
     | 
    
         
            +
                # _Fixnum_:: The raw, undivided integer value
         
     | 
| 
      
 8 
     | 
    
         
            +
                attr_accessor :val
         
     | 
| 
      
 9 
     | 
    
         
            +
             
     | 
| 
      
 10 
     | 
    
         
            +
                # Create a new Value object with value 0
         
     | 
| 
      
 11 
     | 
    
         
            +
                def self.zero
         
     | 
| 
      
 12 
     | 
    
         
            +
                  Value.new(0)
         
     | 
| 
      
 13 
     | 
    
         
            +
                end
         
     | 
| 
      
 14 
     | 
    
         
            +
             
     | 
| 
      
 15 
     | 
    
         
            +
                # Construct a Value object
         
     | 
| 
      
 16 
     | 
    
         
            +
                # === Arguments
         
     | 
| 
      
 17 
     | 
    
         
            +
                # +val+ _String_ or _Fixnum_::
         
     | 
| 
      
 18 
     | 
    
         
            +
                #   Either a _String_ in the form "1234/100" or an integer containing the
         
     | 
| 
      
 19 
     | 
    
         
            +
                #   raw value
         
     | 
| 
      
 20 
     | 
    
         
            +
                # +div+ _Fixnum_::
         
     | 
| 
      
 21 
     | 
    
         
            +
                #   The divisor value to use (when +val+ is given as a _Fixnum_)
         
     | 
| 
      
 22 
     | 
    
         
            +
                def initialize(val, div = 100)
         
     | 
| 
      
 23 
     | 
    
         
            +
                  if val.is_a?(String)
         
     | 
| 
      
 24 
     | 
    
         
            +
                    if val =~ /^(-?\d+)\/(\d+)$/
         
     | 
| 
      
 25 
     | 
    
         
            +
                      @val = $1.to_i
         
     | 
| 
      
 26 
     | 
    
         
            +
                      @div = $2.to_i
         
     | 
| 
      
 27 
     | 
    
         
            +
                    else
         
     | 
| 
      
 28 
     | 
    
         
            +
                      raise "Unexpected value string: #{val.inspect}"
         
     | 
| 
      
 29 
     | 
    
         
            +
                    end
         
     | 
| 
      
 30 
     | 
    
         
            +
                  elsif val.is_a?(Fixnum)
         
     | 
| 
      
 31 
     | 
    
         
            +
                    @val = val
         
     | 
| 
      
 32 
     | 
    
         
            +
                    @div = div
         
     | 
| 
      
 33 
     | 
    
         
            +
                  else
         
     | 
| 
      
 34 
     | 
    
         
            +
                    raise "Unexpected value type: #{val.class}"
         
     | 
| 
      
 35 
     | 
    
         
            +
                  end
         
     | 
| 
      
 36 
     | 
    
         
            +
                end
         
     | 
| 
      
 37 
     | 
    
         
            +
             
     | 
| 
      
 38 
     | 
    
         
            +
                # Add to a Value object
         
     | 
| 
      
 39 
     | 
    
         
            +
                # +other+ can be another Value or a Numeric
         
     | 
| 
      
 40 
     | 
    
         
            +
                def +(other)
         
     | 
| 
      
 41 
     | 
    
         
            +
                  if other.is_a?(Value)
         
     | 
| 
      
 42 
     | 
    
         
            +
                    Value.new(@val + other.val)
         
     | 
| 
      
 43 
     | 
    
         
            +
                  elsif other.is_a?(Numeric)
         
     | 
| 
      
 44 
     | 
    
         
            +
                    (to_f + other).round(2)
         
     | 
| 
      
 45 
     | 
    
         
            +
                  else
         
     | 
| 
      
 46 
     | 
    
         
            +
                    raise "Unexpected argument"
         
     | 
| 
      
 47 
     | 
    
         
            +
                  end
         
     | 
| 
      
 48 
     | 
    
         
            +
                end
         
     | 
| 
      
 49 
     | 
    
         
            +
             
     | 
| 
      
 50 
     | 
    
         
            +
                # Subtract from a Value object
         
     | 
| 
      
 51 
     | 
    
         
            +
                # +other+ can be another Value or a Numeric
         
     | 
| 
      
 52 
     | 
    
         
            +
                def -(other)
         
     | 
| 
      
 53 
     | 
    
         
            +
                  if other.is_a?(Value)
         
     | 
| 
      
 54 
     | 
    
         
            +
                    Value.new(@val - other.val)
         
     | 
| 
      
 55 
     | 
    
         
            +
                  elsif other.is_a?(Numeric)
         
     | 
| 
      
 56 
     | 
    
         
            +
                    (to_f - other).round(2)
         
     | 
| 
      
 57 
     | 
    
         
            +
                  else
         
     | 
| 
      
 58 
     | 
    
         
            +
                    raise "Unexpected argument"
         
     | 
| 
      
 59 
     | 
    
         
            +
                  end
         
     | 
| 
      
 60 
     | 
    
         
            +
                end
         
     | 
| 
      
 61 
     | 
    
         
            +
             
     | 
| 
      
 62 
     | 
    
         
            +
                # Multiply a Value object
         
     | 
| 
      
 63 
     | 
    
         
            +
                # +other+ should be a Numeric
         
     | 
| 
      
 64 
     | 
    
         
            +
                def *(other)
         
     | 
| 
      
 65 
     | 
    
         
            +
                  if other.is_a?(Numeric)
         
     | 
| 
      
 66 
     | 
    
         
            +
                    (to_f * other).round(2)
         
     | 
| 
      
 67 
     | 
    
         
            +
                  else
         
     | 
| 
      
 68 
     | 
    
         
            +
                    raise "Unexpected argument"
         
     | 
| 
      
 69 
     | 
    
         
            +
                  end
         
     | 
| 
      
 70 
     | 
    
         
            +
                end
         
     | 
| 
      
 71 
     | 
    
         
            +
             
     | 
| 
      
 72 
     | 
    
         
            +
                # Divide a Value object
         
     | 
| 
      
 73 
     | 
    
         
            +
                # +other+ should be a Numeric
         
     | 
| 
      
 74 
     | 
    
         
            +
                def /(other)
         
     | 
| 
      
 75 
     | 
    
         
            +
                  if other.is_a?(Numeric)
         
     | 
| 
      
 76 
     | 
    
         
            +
                    (to_f / other).round(2)
         
     | 
| 
      
 77 
     | 
    
         
            +
                  else
         
     | 
| 
      
 78 
     | 
    
         
            +
                    raise "Unexpected argument"
         
     | 
| 
      
 79 
     | 
    
         
            +
                  end
         
     | 
| 
      
 80 
     | 
    
         
            +
                end
         
     | 
| 
      
 81 
     | 
    
         
            +
             
     | 
| 
      
 82 
     | 
    
         
            +
                # Represent the Value as a string (two decimal places)
         
     | 
| 
      
 83 
     | 
    
         
            +
                def to_s
         
     | 
| 
      
 84 
     | 
    
         
            +
                  sprintf("%.02f", to_f)
         
     | 
| 
      
 85 
     | 
    
         
            +
                end
         
     | 
| 
      
 86 
     | 
    
         
            +
             
     | 
| 
      
 87 
     | 
    
         
            +
                # Convert the Value to a Float
         
     | 
| 
      
 88 
     | 
    
         
            +
                def to_f
         
     | 
| 
      
 89 
     | 
    
         
            +
                  @val / @div.to_f
         
     | 
| 
      
 90 
     | 
    
         
            +
                end
         
     | 
| 
      
 91 
     | 
    
         
            +
             
     | 
| 
      
 92 
     | 
    
         
            +
                # Compare two Value objects
         
     | 
| 
      
 93 
     | 
    
         
            +
                def <=>(other)
         
     | 
| 
      
 94 
     | 
    
         
            +
                  @val <=> other.val
         
     | 
| 
      
 95 
     | 
    
         
            +
                end
         
     | 
| 
      
 96 
     | 
    
         
            +
              end
         
     | 
| 
      
 97 
     | 
    
         
            +
            end
         
     |