├── VERSION ├── bin └── .gitkeep ├── AUTHORS ├── Gemfile ├── .gitmodules ├── spec ├── spec_helper.rb └── models │ └── transaction_spec.rb ├── .gitignore ├── lib ├── uta │ ├── models.rb │ └── models │ │ ├── account.rb │ │ ├── entry.rb │ │ └── transaction.rb └── uta.rb ├── UNLICENSE ├── uptheasset.gemspec ├── Rakefile ├── doc └── ontology.ttl └── README.md /VERSION: -------------------------------------------------------------------------------- 1 | 0.0.0 -------------------------------------------------------------------------------- /bin/.gitkeep: -------------------------------------------------------------------------------- 1 | -------------------------------------------------------------------------------- /AUTHORS: -------------------------------------------------------------------------------- 1 | Zachary Voase 2 | -------------------------------------------------------------------------------- /Gemfile: -------------------------------------------------------------------------------- 1 | source "http://rubygems.org" 2 | 3 | # Specify your gem's dependencies in .gemspec 4 | gemspec 5 | -------------------------------------------------------------------------------- /.gitmodules: -------------------------------------------------------------------------------- 1 | [submodule "doc/wiki"] 2 | path = doc/wiki 3 | url = git@github.com:zacharyvoase/uptheasset.wiki.git 4 | -------------------------------------------------------------------------------- /spec/spec_helper.rb: -------------------------------------------------------------------------------- 1 | require 'uta' 2 | 3 | TEST_REPO = RDF::Repository.new 4 | Spira.add_repository! :default, TEST_REPO 5 | -------------------------------------------------------------------------------- /.gitignore: -------------------------------------------------------------------------------- 1 | .DS_Store 2 | .bundle 3 | .tmp 4 | .yardoc 5 | /doc/yard 6 | /doc/ontology.* 7 | !/doc/ontology.ttl 8 | pkg 9 | tmp 10 | -------------------------------------------------------------------------------- /lib/uta/models.rb: -------------------------------------------------------------------------------- 1 | require 'spira' 2 | 3 | # Contains all the UpTheAsset Spira models. 4 | module UTA::Models 5 | require 'uta/models/account' 6 | require 'uta/models/entry' 7 | require 'uta/models/transaction' 8 | end 9 | -------------------------------------------------------------------------------- /lib/uta.rb: -------------------------------------------------------------------------------- 1 | require 'rdf' 2 | 3 | module RDF 4 | # Vocabulary for the UpTheAsset ontology. 5 | UTA = RDF::Vocabulary.new("http://uptheasset.org/ontology#") 6 | 7 | # RDF vocab for UUID URNs. 8 | # 9 | # @example Generate a UUID URN 10 | # RDF::UUID[UUID.new.generate] # => 11 | UUID = RDF::Vocabulary.new("urn:uuid:") 12 | end 13 | 14 | # The root UpTheAsset module. 15 | module UTA 16 | require 'uta/models' 17 | end 18 | -------------------------------------------------------------------------------- /spec/models/transaction_spec.rb: -------------------------------------------------------------------------------- 1 | include UTA::Models 2 | 3 | describe Transaction do 4 | describe ".generate" do 5 | it "should produce a new Transaction" do 6 | Transaction.generate.should be_a(Transaction) 7 | end 8 | 9 | it "should generate a UUID" do 10 | tr = Transaction.generate 11 | tr.subject.fragment.should =~ /^\w{8}-\w{4}-\w{4}-\w{4}-\w{12}$/ 12 | tr.id.should be_a(RDF::URI) 13 | tr.id.to_s.should =~ /^urn:uuid:/ 14 | end 15 | 16 | it "should set a default date" do 17 | Date.should_receive(:today) { Date.civil(2010, 12, 15) } 18 | tr = Transaction.generate 19 | tr.date.should == Date.civil(2010, 12, 15) 20 | end 21 | end 22 | end 23 | -------------------------------------------------------------------------------- /UNLICENSE: -------------------------------------------------------------------------------- 1 | This is free and unencumbered software released into the public domain. 2 | 3 | Anyone is free to copy, modify, publish, use, compile, sell, or 4 | distribute this software, either in source code form or as a compiled 5 | binary, for any purpose, commercial or non-commercial, and by any 6 | means. 7 | 8 | In jurisdictions that recognize copyright laws, the author or authors 9 | of this software dedicate any and all copyright interest in the 10 | software to the public domain. We make this dedication for the benefit 11 | of the public at large and to the detriment of our heirs and 12 | successors. We intend this dedication to be an overt act of 13 | relinquishment in perpetuity of all present and future rights to this 14 | software under copyright law. 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 NONINFRINGEMENT. 19 | IN NO EVENT SHALL THE AUTHORS BE LIABLE FOR ANY CLAIM, DAMAGES OR 20 | OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, 21 | ARISING FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR 22 | OTHER DEALINGS IN THE SOFTWARE. 23 | 24 | For more information, please refer to 25 | -------------------------------------------------------------------------------- /lib/uta/models/account.rb: -------------------------------------------------------------------------------- 1 | module UTA::Models 2 | # An instance of . 3 | # 4 | # An account represents a category of resource or obligation against which 5 | # debits and credits are recorded. 6 | # 7 | # @example Adding metadata to an account 8 | # account = Account["assets/current/cash"] 9 | # account.label = "Cash" 10 | # account.comment = "Cash held in the till or the company bank account." 11 | # account.owner = RDF::URI.new("http://zacharyvoase.com/") 12 | # account.save! 13 | # 14 | # @example Referencing accounts from transactions 15 | # transaction.credit 30, Account["assets/current/cash"] 16 | # transaction.debit 30, Account["revenue/services"] 17 | # transaction.credit 30, Account["expenses/stationery"] 18 | # 19 | # @see http://uptheasset.org/ontology#Account 20 | # Ontology docs for Account 21 | class Account 22 | include Spira::Resource 23 | 24 | base_uri "this:accounts#" 25 | type RDF::UTA.Account 26 | 27 | # The name of this account. 28 | property :label, :predicate => RDF::RDFS.label 29 | 30 | # Additional information about this account. 31 | property :comment, :predicate => RDF::RDFS.comment 32 | 33 | # The owner of this account. 34 | property :owner, :predicate => RDF::UTA.owner 35 | end 36 | end 37 | -------------------------------------------------------------------------------- /uptheasset.gemspec: -------------------------------------------------------------------------------- 1 | # -*- encoding: utf-8 -*- 2 | 3 | require 'rubygems' 4 | require 'rake' 5 | 6 | GEMSPEC = Gem::Specification.new do |gem| 7 | gem.version = File.read("VERSION").chomp 8 | gem.date = File.mtime("VERSION").strftime("%Y-%m-%d") 9 | 10 | gem.name = "uptheasset" 11 | gem.summary = "RDF-enhanced bookkeeping and accounting." 12 | gem.description = "A suite of bookkeeping and accounting utilities, using RDF." 13 | gem.homepage = "http://github.com/zacharyvoase/uptheasset" 14 | gem.rubyforge_project = "nowarning" 15 | gem.license = "Public Domain" if gem.respond_to?(:license=) 16 | 17 | gem.authors = ["Zachary Voase"] 18 | gem.email = "z@zacharyvoase.com" 19 | 20 | gem.platform = Gem::Platform::RUBY 21 | gem.files = FileList["{bin,lib,spec}/**/*", "AUTHORS", "README.md", "UNLICENSE", "VERSION"] 22 | gem.bindir = "bin" 23 | gem.executables = FileList["bin/*"].pathmap("%f") 24 | gem.require_paths = ["lib"] 25 | gem.extensions = [] 26 | gem.test_files = FileList["{test,spec,features}/**/*"] 27 | gem.has_rdoc = false 28 | 29 | gem.required_ruby_version = ">= 1.8.6" 30 | 31 | gem.add_dependency "rdf", "~> 0.3.0" 32 | gem.add_dependency "spira", "~> 0.0.11" 33 | gem.add_dependency "uuid", "~> 2.3.1" 34 | 35 | gem.add_development_dependency "rake", "~> 0.8.7" 36 | gem.add_development_dependency "rspec", ">= 1.3.0" 37 | gem.add_development_dependency "yard", "~> 0.5" 38 | gem.add_development_dependency "bluecloth" # For Markdown formatting in YARD. 39 | end 40 | -------------------------------------------------------------------------------- /lib/uta/models/entry.rb: -------------------------------------------------------------------------------- 1 | module UTA::Models 2 | # An instance of . 3 | # 4 | # An entry is a single credit or debit against an {Account}, which makes up 5 | # part of a transaction. 6 | # 7 | # @see http://uptheasset.org/ontology#Entry 8 | # Ontology docs for Entry 9 | class Entry 10 | include Spira::Resource 11 | 12 | # The type of this entry. 13 | # 14 | # Should be set to either `RDF::UTA.Credit` or `RDF::UTA.Debit`. 15 | property :type, :predicate => RDF.type 16 | 17 | # The amount debited/credited with this entry. 18 | # 19 | # This is deliberately left open-ended so you can specify integers, custom 20 | # currency objects, etc. 21 | property :amount, :predicate => RDF::UTA.amount 22 | 23 | # The account to debit/credit. 24 | # 25 | # This should be an `RDF::URI` or an {Account}. 26 | property :account, :predicate => RDF::UTA.account, :type => :Account 27 | 28 | # Create and save a credit against a given account. 29 | # 30 | # @param [RDF::Value] amount 31 | # The amount by which to credit. 32 | # @param [Account, RDF::URI] account 33 | # The account to credit. 34 | # 35 | # @return [Entry] the created credit. 36 | def self.credit(amount, account) 37 | self.new(:type => RDF::UTA.Credit, 38 | :amount => amount, 39 | :account => account).save! 40 | end 41 | 42 | # Create and save a debit against a given account. 43 | # 44 | # @param [RDF::Value] amount 45 | # The amount by which to debit. 46 | # @param [Account, RDF::URI] account 47 | # The account to debit. 48 | # 49 | # @return [Entry] the created debit. 50 | def self.debit(amount, account) 51 | self.new(:type => RDF::UTA.Debit, 52 | :amount => amount, 53 | :account => account).save! 54 | end 55 | end 56 | end 57 | -------------------------------------------------------------------------------- /Rakefile: -------------------------------------------------------------------------------- 1 | $:.unshift(File.expand_path(File.join(File.dirname(__FILE__), 'lib'))) 2 | 3 | require 'bundler' 4 | require 'shellwords' 5 | 6 | # Adds `build`, `install` and `release` tasks. 7 | Bundler::GemHelper.install_tasks 8 | 9 | # ---- Ontology ---- 10 | # 11 | # Helpers for building out various versions of the ontology. 12 | 13 | ONTOLOGY_FORMATS = [ 14 | ["ntriples", ".nt"], 15 | ["rdfxml-abbrev", ".rdf"], 16 | ] 17 | 18 | desc "Build other formats of the ontology from the Turtle source" 19 | task :onto do 20 | ONTOLOGY_FORMATS.each do |format, fileext| 21 | sh "rapper -i turtle -o #{format} doc/ontology.ttl > doc/ontology#{fileext}" 22 | end 23 | end 24 | 25 | # ---- Versioning ---- 26 | 27 | # Tasks for bumping the value in VERSION: 28 | # 29 | # rake bump:bug # 1.2.3 -> 1.2.4 30 | # rake bump:min # 1.2.3 -> 1.3.0 31 | # rake bump:maj # 1.2.3 -> 2.0.0 32 | # 33 | # These tasks will also do a `git commit` for the version bump: 34 | # 35 | # git commit -m "Bumped to v1.2.4" VERSION 36 | # 37 | namespace :bump do 38 | def get_current_version 39 | File.read("VERSION").split(".").map(&:to_i) 40 | end 41 | 42 | def write_current_version(ver) 43 | File.open("VERSION", "w") { |f| f << ver.join(".") } 44 | FileUtils.touch("VERSION") 45 | sh "git", "commit", "-m", "Bumped to v#{ver.join(".")}", "VERSION" 46 | end 47 | 48 | desc "Increment this project's bugfix version (1.2.x)" 49 | task :bug do 50 | ver = get_current_version 51 | ver[2] += 1 52 | write_current_version(ver) 53 | end 54 | 55 | desc "Increment this project's minor version (1.x.3)" 56 | task :min do 57 | ver = get_current_version 58 | ver[1] += 1 59 | ver[2] = 0 60 | write_current_version(ver) 61 | end 62 | 63 | desc "Increment this project's major version (x.2.3)" 64 | task :maj do 65 | ver = get_current_version 66 | ver[0] += 1 67 | ver[1] = 0 68 | ver[2] = 0 69 | write_current_version(ver) 70 | end 71 | end 72 | 73 | # ---- RSpec ---- 74 | 75 | require 'rspec/core/rake_task' 76 | 77 | task :default => :spec 78 | spec = RSpec::Core::RakeTask.new do |t| 79 | t.verbose = false 80 | t.pattern = "spec/**/*_spec.rb" 81 | t.rspec_opts = (ENV["RSPEC_OPTS"] || %{ 82 | --color 83 | --format nested 84 | -I #{File.expand_path(File.join(__FILE__, "..", "lib"))} 85 | -r #{File.expand_path(File.join(__FILE__, "..", "spec", "spec_helper.rb"))} 86 | }).shellsplit 87 | end 88 | 89 | # ---- YARD ---- 90 | 91 | require 'yard' 92 | 93 | task :doc => :yard 94 | YARD::Rake::YardocTask.new do |yard| 95 | yard.files = ['lib/**/*.rb', '-', 'AUTHORS', 'UNLICENSE', 'VERSION'] 96 | yard.options = (ENV["YARD_OPTS"] || %{ 97 | --title "#{GEMSPEC.name} v#{GEMSPEC.version}" 98 | --output-dir doc/yard 99 | --protected 100 | --no-private 101 | --hide-void-return 102 | --markup markdown 103 | --readme README.md 104 | }).shellsplit 105 | end 106 | 107 | namespace :deploy do 108 | desc "Sync the built HTML docs and ontology to uptheasset.org" 109 | task :docs => [:onto, :yard] do 110 | sh "rsync -avz doc/ontology.* doc/yard/ -e ssh uptheasset.org:" 111 | end 112 | end 113 | -------------------------------------------------------------------------------- /lib/uta/models/transaction.rb: -------------------------------------------------------------------------------- 1 | require 'uuid' 2 | 3 | module UTA::Models 4 | # An instance of . 5 | # 6 | # A transaction is the record of an exchange between two or more accounts in 7 | # a journal. Every transaction should have at least two entries (one credit, 8 | # one debit). 9 | # 10 | # @see http://uptheasset.org/ontology#Transaction 11 | # Ontology docs for Transaction 12 | class Transaction 13 | include Spira::Resource 14 | 15 | base_uri "this:transactions#" 16 | type RDF::UTA.Transaction 17 | 18 | # The identifier of this transaction. 19 | # 20 | # When using {Transaction.generate}, this will be set to a `urn:uuid:` URI. 21 | property :id, :predicate => RDF::DC.identifier 22 | 23 | # The date of this transaction. 24 | # 25 | # Defaults to `Date.today` when using {Transaction.generate}. 26 | property :date, :predicate => RDF::DC.date 27 | 28 | # The label for this transaction. 29 | # 30 | # @example 31 | # tr.label = "Sale of goods" 32 | # tr.label = "Depreciation of car" 33 | # tr.label = "Stationery purchase" 34 | property :label, :predicate => RDF::RDFS.label 35 | 36 | # Additional information about a transaction. 37 | # 38 | # @example 39 | # tr.comment = "Sold some nearly-expired stock at a slight discount." 40 | property :comment, :predicate => RDF::RDFS.comment 41 | 42 | # The individual components of a transaction. 43 | # 44 | # @example 45 | # tr.entries << Entry.credit(30, Account["cash"]) 46 | # tr.entries << Entry.debit(30, Account["expenses"]) 47 | has_many :entries, :predicate => RDF::UTA.entry, :type => :Entry 48 | 49 | # Generate and save a new transaction (with its own UUID). 50 | # 51 | # @param [Hash] attributes 52 | # An optional hash of attributes to set on the generated transaction. 53 | # 54 | # @yield [transaction] The generated transaction (not yet saved). 55 | # 56 | # @return [Transaction] The saved transaction. 57 | # 58 | # @example Record a sale 59 | # Transaction.generate do |t| 60 | # t.credit 30, RDF::URI.new("accounts#revenue") 61 | # t.debit 30, RDF::URI.new("accounts#cash") 62 | # end # => 63 | def self.generate(attributes = {}) 64 | uuid = ::UUID.new.generate 65 | transaction = self.for(uuid) 66 | transaction.id = RDF::UUID[uuid] 67 | transaction.date = Date.today 68 | transaction.update(attributes) 69 | yield transaction if block_given? 70 | transaction.save! 71 | end 72 | 73 | # Add a (saved) credit {Entry} to this transaction. 74 | # 75 | # @param [RDF::Value] amount 76 | # The amount by which to credit. 77 | # @param [Account, RDF::URI] account 78 | # The account to credit. 79 | # @return [Entry] the created entry. 80 | def credit(amount, account) 81 | self.entries << Entry.credit(amount, account) 82 | end 83 | 84 | # Add a (saved) debit {Entry} to this transaction. 85 | # 86 | # @param [RDF::Value] amount 87 | # The amount by which to debit. 88 | # @param [Account, RDF::URI] account 89 | # The account to debit. 90 | # @return [Entry] the created entry. 91 | def debit(amount, account) 92 | self.entries << Entry.debit(amount, account) 93 | end 94 | end 95 | end 96 | -------------------------------------------------------------------------------- /doc/ontology.ttl: -------------------------------------------------------------------------------- 1 | @base . 2 | @prefix : . 3 | @prefix dc: . 4 | @prefix foaf: . 5 | @prefix owl: . 6 | @prefix rdf: . 7 | @prefix rdfs: . 8 | @prefix rel: . 9 | @prefix xsd: . 10 | 11 | <> a owl:Ontology ; 12 | rdfs:label "UpTheAsset" ; 13 | rdfs:comment "An ontology for double-entry bookkeeping and accounting."@en ; 14 | rdfs:seeAlso ; 15 | foaf:maker ; 16 | dc:creator "UpTheAsset.org" ; 17 | dc:created "2010-12-30"^^xsd:date ; 18 | dc:modified "2010-12-30"^^xsd:date ; 19 | rel:license ; 20 | owl:versionInfo "0.0.1" . 21 | 22 | ############# 23 | ## Classes ## 24 | ############# 25 | 26 | :Account a owl:Class, rdfs:Class ; 27 | rdfs:isDefinedBy <> ; 28 | rdfs:label "Account" ; 29 | rdfs:comment "A specific class of resources or obligations for which debits and credits are recorded."@en . 30 | 31 | :Credit a owl:Class, rdfs:Class ; 32 | rdfs:isDefinedBy <> ; 33 | rdfs:subClassOf :Entry ; 34 | rdfs:label "Credit" ; 35 | rdfs:comment "An increase in the balance of a liability, revenue or equity account, or a decrease in the balance of an asset or expense account."@en ; 36 | owl:disjointWith :Debit . 37 | 38 | :Debit a owl:Class, rdfs:Class ; 39 | rdfs:isDefinedBy <> ; 40 | rdfs:subClassOf :Entry ; 41 | rdfs:label "Debit" ; 42 | rdfs:comment "An increase in the balance of an asset or expense account, or a decrease in the balance of a liability, revenue or equity account."@en . 43 | 44 | :Entry a owl:Class, rdfs:Class ; 45 | rdfs:isDefinedBy <> ; 46 | rdfs:label "Entry" ; 47 | rdfs:comment "An atomic part of a transaction which involves the debiting or crediting of a single account by a value."@en . 48 | 49 | :Journal a owl:Class, rdfs:Class ; 50 | rdfs:isDefinedBy <> ; 51 | rdfs:subClassOf rdf:Seq ; 52 | rdfs:label "Journal" ; 53 | rdfs:comment "A chronological record of debits and credits against one or more accounts for an entity."@en . 54 | 55 | :Transaction a owl:Class, rdfs:Class ; 56 | rdfs:isDefinedBy <> ; 57 | rdfs:label "Transaction" ; 58 | rdfs:comment "A single event in a journal, composed of at least one debit and at least one credit, involving the movement of value between at least two accounts."@en . 59 | 60 | ####################### 61 | ## Object Properties ## 62 | ####################### 63 | 64 | :account a owl:ObjectProperty, rdfs:Property ; 65 | rdfs:isDefinedBy <> ; 66 | rdfs:label "account" ; 67 | rdfs:comment "The account against which this credit or debit is incurred."@en ; 68 | rdfs:domain :Entry ; 69 | rdfs:range :Account . 70 | 71 | :amount a owl:ObjectProperty, rdfs:Property ; 72 | rdfs:isDefinedBy <> ; 73 | rdfs:label "amount" ; 74 | rdfs:comment "The value of a debit or credit."@en ; 75 | rdfs:domain :Entry . 76 | 77 | :entry a owl:InverseFunctionalProperty, owl:ObjectProperty, rdfs:Property ; 78 | rdfs:isDefinedBy <> ; 79 | rdfs:label "entry" ; 80 | rdfs:comment "An entry in a transaction."@en ; 81 | rdfs:domain :Transaction ; 82 | rdfs:range :Entry ; 83 | owl:inverseOf :transaction . 84 | 85 | :owner a owl:ObjectProperty, rdfs:Property ; 86 | rdfs:isDefinedBy <> ; 87 | rdfs:label "owner" ; 88 | rdfs:comment "The owner of this resource."@en ; 89 | owl:inverseOf :owns . 90 | 91 | :owns a owl:ObjectProperty, rdfs:Property ; 92 | rdfs:isDefinedBy <> ; 93 | rdfs:label "owns" ; 94 | rdfs:comment "A resource which this entity owns."@en . 95 | 96 | :transaction a owl:FunctionalProperty, owl:ObjectProperty ; 97 | rdfs:isDefinedBy <> ; 98 | rdfs:label "transaction" ; 99 | rdfs:comment "The transaction which this entry belongs to."@en ; 100 | rdfs:domain :Entry ; 101 | rdfs:range :Transaction . 102 | 103 | [] a owl:AllDisjointClasses ; 104 | owl:members ( 105 | :Account 106 | :Entry 107 | :Journal 108 | :Transaction 109 | ) . 110 | -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | # Up The Asset 2 | 3 | UTA is a proof-of-concept system for [double-entry bookkeeping][deb] and 4 | accounting, powered by standard UNIX and [RDF.rb][]. 5 | 6 | [deb]: http://en.wikipedia.org/wiki/Double-entry_bookkeeping_system 7 | [rdf.rb]: http://rdf.rubyforge.org/ 8 | 9 | It is also a work-in-progress Web Ontology for accounting and bookkeeping. 10 | 11 | **N.B.:** Many of the examples shown here don’t work because they haven’t been 12 | implemented yet. Much of this document is still speculative. 13 | 14 | 15 | ## Background 16 | 17 | Accounting and bookkeeping are essential aspects of running any business. 18 | Usually considered dull and tedious by laymen, the books of a company provide a 19 | unique insight into its operation. Using the information contained therein, 20 | perhaps supplemented by data from other sources, novel ways of minimizing costs 21 | and maximizing revenues may be revealed. 22 | 23 | Until now, most professional accounting software has been either closed-source 24 | or GPL. I can find no examples which observe the [UNIX philosophy][]. 25 | Extensibility is difficult even with open-source software, since most examples 26 | are usually written in compiled languages and/or store their data in 27 | proprietary or obscure formats. 28 | 29 | [unix philosophy]: http://en.wikipedia.org/wiki/Unix_philosophy 30 | 31 | **Up The Asset** is a reimagination of accounting software for the modern age. 32 | Based on the proven, centuries-old principle of double-entry bookkeeping, yet 33 | using nothing but [open web standards and formats][rdf] stored in plain-text 34 | files, it aims to bring to accounting the same breath of fresh air which 35 | [git][] brought to version control. 36 | 37 | [rdf]: http://en.wikipedia.org/wiki/Resource_Description_Framework 38 | [git]: http://git-scm.com/ 39 | 40 | Because the system is nothing but text and cross-platform executables which 41 | operate on that text, it is fully extensible. The use of [RDF][] means you can 42 | supplement the core UTA vocabulary with your own domain-specific (or even 43 | organization-specific) ontology. Scripting or extending the application itself 44 | is easy thanks to the power of [Ruby][]. The excellent [RDF.rb][] and [Spira][] 45 | libraries allow you to operate on the RDF data at the highest or lowest levels. 46 | 47 | [ruby]: http://www.ruby-lang.org/ 48 | [spira]: http://spira.rubyforge.org/ 49 | 50 | 51 | ## Architecture 52 | 53 | ### Storage 54 | 55 | Your books are kept in a single directory, which looks like this: 56 | 57 | books/ 58 | |-- attachments/ 59 | | |-- index 60 | | `-- ... # More files (BitCache repo) 61 | |-- accounts # Chart of accounts 62 | `-- transactions # Journal 63 | 64 | `attachments/index`, `accounts` and `transactions` all contain N3-formatted RDF 65 | statements representing the bulk of the information held. All UTA commands read 66 | from and/or write to these files. `attachments/` is also a 67 | [BitCache](http://bitcache.org/) repository holding any extra files needed 68 | (e.g. source documents like invoices and receipts). 69 | 70 | 71 | ### Interaction 72 | 73 | #### Example: Record a transaction 74 | 75 | This shell command: 76 | 77 | $ uta record 30 assets/current/cash revenue/service "Consulting" \ 78 | --with-comment --on 2010-12-28 79 | Gave John Doe my services for 1 hour in exchange for $30. 80 | ^D 81 | 82 | Evaluates to the following Ruby code: 83 | 84 | Transaction.generate do |tr| 85 | tr.label = "Consulting" 86 | tr.comment = "Gave John Doe my services for 1 hour in exchange for $30." 87 | tr.date = Date.civil(2010, 12, 28) 88 | tr.debit 30, Account["assets/current/cash"] 89 | tr.credit 30, Account["revenue/service"] 90 | end.save! 91 | 92 | Generates this RDF (shown in Turtle): 93 | 94 | # Prefixes shown here for illustrative purposes. 95 | @base . 96 | @prefix rdf: . 97 | @prefix this: . 98 | @prefix dc: . 99 | @prefix uta: . 100 | @prefix xsd: . 101 | 102 | a uta:Transaction ; 103 | dc:date "2010-12-28Z"^^xsd:date ; 104 | dc:identifier ; 105 | rdfs:label "Consulting"@en ; 106 | rdfs:comment "Gave John Doe my services for 1 hour in exchange for $30."@en ; 107 | uta:entry 108 | [ a uta:Debit ; 109 | uta:account ; 110 | uta:amount 30 ] , 111 | [ a uta:Credit ; 112 | uta:account ; 113 | uta:amount 30 ] . 114 | 115 | 116 | ## Unlicense 117 | 118 | This is free and unencumbered software released into the public domain. 119 | 120 | Anyone is free to copy, modify, publish, use, compile, sell, or distribute this 121 | software, either in source code form or as a compiled binary, for any purpose, 122 | commercial or non-commercial, and by any means. 123 | 124 | In jurisdictions that recognize copyright laws, the author or authors of this 125 | software dedicate any and all copyright interest in the software to the public 126 | domain. We make this dedication for the benefit of the public at large and to 127 | the detriment of our heirs and successors. We intend this dedication to be an 128 | overt act of relinquishment in perpetuity of all present and future rights to 129 | this software under copyright law. 130 | 131 | THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR 132 | IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, 133 | FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE 134 | AUTHORS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN 135 | ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION 136 | WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. 137 | 138 | For more information, please refer to 139 | --------------------------------------------------------------------------------