moneybook 0.1.6 → 0.1.7
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/VERSION +1 -1
- data/bin/moneybook +177 -134
- metadata +4 -4
    
        data/VERSION
    CHANGED
    
    | @@ -1 +1 @@ | |
| 1 | 
            -
            0.1. | 
| 1 | 
            +
            0.1.7
         | 
    
        data/bin/moneybook
    CHANGED
    
    | @@ -1,25 +1,48 @@ | |
| 1 1 | 
             
            #!/usr/bin/env ruby
         | 
| 2 2 | 
             
            require 'rubygems'
         | 
| 3 | 
            -
             | 
| 3 | 
            +
            ###
         | 
| 4 4 | 
             
            require 'terminal-table/import'
         | 
| 5 | 
            -
            require 'commander/import'
         | 
| 6 | 
            -
            require 'date'
         | 
| 7 5 | 
             
            require 'andand'
         | 
| 6 | 
            +
            require 'highline/import'
         | 
| 7 | 
            +
            ###
         | 
| 8 | 
            +
            require 'iconv'
         | 
| 9 | 
            +
            require 'optparse'
         | 
| 10 | 
            +
            require 'date'
         | 
| 8 11 | 
             
            require 'abbrev'
         | 
| 9 12 | 
             
            require 'pp'
         | 
| 10 13 |  | 
| 11 | 
            -
             | 
| 12 | 
            -
             | 
| 13 | 
            -
             | 
| 14 | 
            -
             | 
| 14 | 
            +
            class Array
         | 
| 15 | 
            +
              def normalize_length
         | 
| 16 | 
            +
                max = self.inject{|longest, current| longest.to_s.length > current.to_s.length ? longest : current }.to_s.length
         | 
| 17 | 
            +
                self.map{|x| x = "%-#{max}s" % x.to_s}
         | 
| 18 | 
            +
              end
         | 
| 19 | 
            +
            end
         | 
| 20 | 
            +
             | 
| 21 | 
            +
            def process_command(cmd)
         | 
| 22 | 
            +
              args = Array(cmd)
         | 
| 23 | 
            +
              command = args.shift
         | 
| 24 | 
            +
              case(command)
         | 
| 25 | 
            +
              when "new"
         | 
| 26 | 
            +
                command_new(args[0])
         | 
| 27 | 
            +
              when "parse"
         | 
| 28 | 
            +
                options = {}
         | 
| 29 | 
            +
                OptionParser.new do |opts|
         | 
| 30 | 
            +
                  opts.banner = "Usage: moneybook parse [options] FILENAME"
         | 
| 31 | 
            +
                  opts.on("-i", "--[no-]intermediate", "Show intermediate computations") do |i|
         | 
| 32 | 
            +
                    options[:intermediate] = i
         | 
| 33 | 
            +
                  end
         | 
| 34 | 
            +
                end.parse!(args)
         | 
| 35 | 
            +
                command_parse(options, args.last)
         | 
| 36 | 
            +
              else
         | 
| 37 | 
            +
                puts "unknown command: #{command}"
         | 
| 38 | 
            +
              end
         | 
| 39 | 
            +
            end
         | 
| 15 40 |  | 
| 16 | 
            -
             | 
| 17 | 
            -
               | 
| 18 | 
            -
               | 
| 19 | 
            -
               | 
| 20 | 
            -
             | 
| 21 | 
            -
                people = ask('People (separated by spaces): ', lambda{|x|x.split(/\s+/)})
         | 
| 22 | 
            -
                text = "### #{title} ###
         | 
| 41 | 
            +
            def command_new(title)
         | 
| 42 | 
            +
              title = ask("Title: "){|q| q.readline = true} unless title.to_s != ''
         | 
| 43 | 
            +
              people = ask('People (separated by spaces): ', lambda{|x|x.split(/\s+/)}){|q| q.readline = true}
         | 
| 44 | 
            +
              change= ask("Currency change? (leave blank if not needed)  ", Float) { |q| q.default = 0; q.readline = true }
         | 
| 45 | 
            +
              text = "### #{title} ###
         | 
| 23 46 | 
             
            # #{Date.today.to_s}
         | 
| 24 47 | 
             
            #######
         | 
| 25 48 | 
             
            # - everyone is included:
         | 
| @@ -32,152 +55,172 @@ command :new do |c| | |
| 32 55 | 
             
            # dinner (50Fra 16Tom) +5Tom -2Jack -Mary #Tom spends 5 more than the others, Jack 2 less
         | 
| 33 56 | 
             
            # - synctactic sugar, instead of ...
         | 
| 34 57 | 
             
            # pay back (20L) T
         | 
| 35 | 
            -
            # - ... you can write
         | 
| 58 | 
            +
            # - ... you can write ...
         | 
| 36 59 | 
             
            # 20L -> T
         | 
| 60 | 
            +
            # - ... and since it's a payback it won't count on the spent amount
         | 
| 37 61 | 
             
            #######
         | 
| 38 62 |  | 
| 39 63 | 
             
            PEOPLE: #{people.join(' ')}"
         | 
| 40 | 
            -
             | 
| 41 | 
            -
             | 
| 42 | 
            -
             | 
| 43 | 
            -
             | 
| 44 | 
            -
             | 
| 45 | 
            -
             | 
| 46 | 
            -
                end
         | 
| 64 | 
            +
              text += "\nCHANGE: #{change}" if change > 0
         | 
| 65 | 
            +
              filename = "moneybook_#{title.downcase.gsub(' ','_')}.txt"
         | 
| 66 | 
            +
              if File.open(filename,'w'){|f|f.write text} then
         | 
| 67 | 
            +
                puts "File #{filename} created!"
         | 
| 68 | 
            +
              else
         | 
| 69 | 
            +
                puts "Could not create file #{filename}.."
         | 
| 47 70 | 
             
              end
         | 
| 48 71 | 
             
            end
         | 
| 49 72 |  | 
| 50 73 | 
             
            def get_final(sq, people)
         | 
| 51 74 | 
             
              final = {}
         | 
| 52 | 
            -
               | 
| 75 | 
            +
              spent = {}
         | 
| 76 | 
            +
              given = {}
         | 
| 77 | 
            +
              people.each{|person| final[person]=0;spent[person]=0;given[person]=0}
         | 
| 53 78 | 
             
              sq.each {|c|
         | 
| 54 79 | 
             
                c[:balance].each_pair{|person, value|
         | 
| 55 80 | 
             
                  final[person] += value
         | 
| 56 81 | 
             
                }
         | 
| 82 | 
            +
                c[:virtual].each_pair{|person, value|
         | 
| 83 | 
            +
                  spent[person] += value unless c[:name] == 'PAYBACK'
         | 
| 84 | 
            +
                }
         | 
| 85 | 
            +
                c[:true].each_pair{|person, value|
         | 
| 86 | 
            +
                  given[person] += value
         | 
| 87 | 
            +
                }
         | 
| 57 88 | 
             
              }
         | 
| 58 | 
            -
              final
         | 
| 89 | 
            +
              [final, spent, given]
         | 
| 59 90 | 
             
            end
         | 
| 60 91 |  | 
| 61 | 
            -
             | 
| 62 | 
            -
               | 
| 63 | 
            -
               | 
| 64 | 
            -
               | 
| 65 | 
            -
               | 
| 66 | 
            -
             | 
| 67 | 
            -
             | 
| 68 | 
            -
             | 
| 69 | 
            -
                f= | 
| 70 | 
            -
                 | 
| 71 | 
            -
             | 
| 72 | 
            -
             | 
| 73 | 
            -
                 | 
| 74 | 
            -
             | 
| 75 | 
            -
                   | 
| 76 | 
            -
             | 
| 77 | 
            -
                   | 
| 78 | 
            -
             | 
| 79 | 
            -
             | 
| 80 | 
            -
                   | 
| 81 | 
            -
             | 
| 82 | 
            -
             | 
| 83 | 
            -
             | 
| 84 | 
            -
             | 
| 85 | 
            -
                     | 
| 86 | 
            -
                     | 
| 87 | 
            -
             | 
| 88 | 
            -
             | 
| 89 | 
            -
             | 
| 90 | 
            -
             | 
| 91 | 
            -
             | 
| 92 | 
            -
             | 
| 93 | 
            -
                    c[:true_str].split(/\s+/).each{|y|
         | 
| 94 | 
            -
                      temp = y.match(/([\d\.]+)([A-Za-z]+)/)
         | 
| 95 | 
            -
                      if (person=people_abbreviations[temp[2].to_s.downcase]) != nil
         | 
| 96 | 
            -
                        c[:true][person] += temp[1].to_f
         | 
| 97 | 
            -
                        c[:true_total] += temp[1].to_f
         | 
| 98 | 
            -
                      else
         | 
| 99 | 
            -
                        puts "error: ambiguos or inexistent name: #{temp[2].to_s}"
         | 
| 100 | 
            -
                        exit
         | 
| 101 | 
            -
                      end
         | 
| 102 | 
            -
                    }
         | 
| 92 | 
            +
            def command_parse(options, filename)
         | 
| 93 | 
            +
              intermediate = options[:intermediate]
         | 
| 94 | 
            +
              people = []
         | 
| 95 | 
            +
              currency_change = 0
         | 
| 96 | 
            +
              people_abbreviations = []
         | 
| 97 | 
            +
              sq = [] #big table to make the computations..
         | 
| 98 | 
            +
              f=File.open(filename,'r').read
         | 
| 99 | 
            +
              if f[0]==255 && f[1]==254 then
         | 
| 100 | 
            +
                f=Iconv.iconv('UTF-8', 'UTF-16LE', f)[0][3..-1]
         | 
| 101 | 
            +
                puts "converting from UTF-16LE..."
         | 
| 102 | 
            +
              end
         | 
| 103 | 
            +
              f.each_line{|line|
         | 
| 104 | 
            +
                if line =~ /^\s*\#/ || line =~ /\A\s*\Z/
         | 
| 105 | 
            +
                  # this is interpreted as a comment line
         | 
| 106 | 
            +
                  puts line if intermediate
         | 
| 107 | 
            +
                elsif (m = line.match(/\A\s*PEOPLE\s*\:?\s*((\s+\w+)+)\s*\Z/).andand[1].andand.split(/\s+/).andand.delete_if{|x|x==""}) != nil then
         | 
| 108 | 
            +
                  m.each{|z|people<<z.to_s.downcase}
         | 
| 109 | 
            +
                  people_abbreviations = people.abbrev
         | 
| 110 | 
            +
                elsif (currency_change == 0) && ((m = line.match(/\A\s*CHANGE\s*\:\s*([\.\d]+)\s*\Z/).andand[1].to_f) != 0) then
         | 
| 111 | 
            +
                  currency_change = m
         | 
| 112 | 
            +
                  puts "currency change is #{currency_change}"
         | 
| 113 | 
            +
                else
         | 
| 114 | 
            +
                  original_str = false
         | 
| 115 | 
            +
                  if temp=line.match(/\A\s*(.+)\s*\-+\>\s*(.+)\Z/) then
         | 
| 116 | 
            +
                    original_str = line.strip
         | 
| 117 | 
            +
                    line = "PAYBACK (#{temp[1].to_s}) #{temp[2].to_s}"
         | 
| 118 | 
            +
                  end
         | 
| 119 | 
            +
                  x = line.strip.match(/^\s*([\w\ \']+?)\s*\((.+)\)\s*(.+)?/).to_a.map{|z| z.to_s}
         | 
| 120 | 
            +
                  sq << {:name => x[1].to_s, :original_str => original_str || line.strip, :true_str => x[2].to_s, :virtual_str => x[3].to_s, :true => {}, :virtual => {}, :balance => {},
         | 
| 121 | 
            +
                  :true_total => 0, :virtual_total => 0}
         | 
| 122 | 
            +
                  c=sq.last
         | 
| 123 | 
            +
                  people.each{|person| c[:true][person]=0;c[:virtual][person]=0;c[:balance][person]=0}
         | 
| 103 124 |  | 
| 104 | 
            -
             | 
| 105 | 
            -
             | 
| 106 | 
            -
             | 
| 107 | 
            -
             | 
| 125 | 
            +
                  # parsing of true_str
         | 
| 126 | 
            +
                  c[:true_str].split(/\s+/).each{|y|
         | 
| 127 | 
            +
                    temp = y.match(/([\d\.]+)([A-Za-z]+)/)
         | 
| 128 | 
            +
                    if (person=people_abbreviations[temp[2].to_s.downcase]) != nil
         | 
| 129 | 
            +
                      c[:true][person] += temp[1].to_f
         | 
| 130 | 
            +
                      c[:true_total] += temp[1].to_f
         | 
| 108 131 | 
             
                    else
         | 
| 109 | 
            -
                       | 
| 110 | 
            -
                       | 
| 111 | 
            -
             | 
| 112 | 
            -
             | 
| 113 | 
            -
             | 
| 114 | 
            -
             | 
| 115 | 
            -
             | 
| 116 | 
            -
             | 
| 117 | 
            -
             | 
| 132 | 
            +
                      puts "error: ambiguos or inexistent name: #{temp[2].to_s}"
         | 
| 133 | 
            +
                      exit
         | 
| 134 | 
            +
                    end
         | 
| 135 | 
            +
                  }
         | 
| 136 | 
            +
             | 
| 137 | 
            +
                  # parsing of virtual_str
         | 
| 138 | 
            +
                  if c[:virtual_str] =~ /\A\s*\Z/ then
         | 
| 139 | 
            +
                    c[:virtual].each_key{|person|c[:virtual][person] = c[:true_total]/people.length}
         | 
| 140 | 
            +
                    c[:virtual_total] = c[:true_total]
         | 
| 141 | 
            +
                  else
         | 
| 142 | 
            +
                    avoid = []
         | 
| 143 | 
            +
                    people_to_complete = []
         | 
| 144 | 
            +
                    complete_automatically = true
         | 
| 145 | 
            +
                    c[:virtual_str].split(/\s+/).each{|y|
         | 
| 146 | 
            +
                      if (temp = y.match(/([\+\-\d\.]*)([A-Za-z]+)/)) then         
         | 
| 147 | 
            +
                        if (person=people_abbreviations[temp[2].to_s.downcase]) != nil
         | 
| 148 | 
            +
                          if temp[1].to_s == "" then
         | 
| 149 | 
            +
                            complete_automatically = false
         | 
| 150 | 
            +
                            people_to_complete << person
         | 
| 151 | 
            +
                            avoid << person
         | 
| 152 | 
            +
                          elsif temp[1].to_s[0,1] == '+'
         | 
| 153 | 
            +
                            c[:virtual][person] += temp[1].to_s[1..-1].to_f
         | 
| 154 | 
            +
                            c[:virtual_total] += temp[1].to_f
         | 
| 155 | 
            +
                          elsif temp[1].to_s[0,1] == '-'
         | 
| 156 | 
            +
                            if temp[1].to_s == '-' then
         | 
| 157 | 
            +
                              c[:virtual][person] = 0
         | 
| 118 158 | 
             
                              avoid << person
         | 
| 119 | 
            -
                            elsif temp[1].to_s[0,1] == '+'
         | 
| 120 | 
            -
                              c[:virtual][person] += temp[1].to_s[1..-1].to_f
         | 
| 121 | 
            -
                              c[:virtual_total] -= temp[1].to_f
         | 
| 122 | 
            -
                            elsif temp[1].to_s[0,1] == '-'
         | 
| 123 | 
            -
                              if temp[1].to_s == '-' then
         | 
| 124 | 
            -
                                c[:virtual][person] = 0
         | 
| 125 | 
            -
                                avoid << person
         | 
| 126 | 
            -
                              else
         | 
| 127 | 
            -
                                c[:virtual][person] -= temp[1].to_s[1..-1].to_f
         | 
| 128 | 
            -
                                c[:virtual_total] += temp[1].to_f
         | 
| 129 | 
            -
                              end
         | 
| 130 159 | 
             
                            else
         | 
| 131 | 
            -
                              c[:virtual][person]  | 
| 132 | 
            -
                              c[:virtual_total]  | 
| 133 | 
            -
                              avoid << person
         | 
| 160 | 
            +
                              c[:virtual][person] -= temp[1].to_s[1..-1].to_f
         | 
| 161 | 
            +
                              c[:virtual_total] -= temp[1].to_f
         | 
| 134 162 | 
             
                            end
         | 
| 135 163 | 
             
                          else
         | 
| 136 | 
            -
                             | 
| 137 | 
            -
                             | 
| 164 | 
            +
                            c[:virtual][person] += temp[1].to_f
         | 
| 165 | 
            +
                            c[:virtual_total] += temp[1].to_f
         | 
| 166 | 
            +
                            avoid << person
         | 
| 138 167 | 
             
                          end
         | 
| 168 | 
            +
                        else
         | 
| 169 | 
            +
                          puts "error: ambiguos or inexistent name: #{temp[2].to_s}"
         | 
| 170 | 
            +
                          exit
         | 
| 139 171 | 
             
                        end
         | 
| 140 | 
            -
                      }
         | 
| 141 | 
            -
                      if c[:virtual_total] > c[:true_total]
         | 
| 142 | 
            -
                        puts "error: Virtual total doesn't match.."
         | 
| 143 | 
            -
                        exit
         | 
| 144 172 | 
             
                      end
         | 
| 145 | 
            -
                      if complete_automatically then      
         | 
| 146 | 
            -
                        others = c[:virtual].map{|i,v|i}-avoid
         | 
| 147 | 
            -
                        others.each { |p|
         | 
| 148 | 
            -
                          c[:virtual][p] += (c[:true_total] - c[:virtual_total])/others.length
         | 
| 149 | 
            -
                        }
         | 
| 150 | 
            -
                      else
         | 
| 151 | 
            -
                        people_to_complete.each { |p|
         | 
| 152 | 
            -
                          c[:virtual][p] += (c[:true_total] - c[:virtual_total])/people_to_complete.length
         | 
| 153 | 
            -
                        }
         | 
| 154 | 
            -
                      end
         | 
| 155 | 
            -
                    end
         | 
| 156 | 
            -
                    c[:balance].each_key{|person|
         | 
| 157 | 
            -
                      c[:balance][person] = c[:true][person] - c[:virtual][person]
         | 
| 158 173 | 
             
                    }
         | 
| 159 | 
            -
                     | 
| 160 | 
            -
             | 
| 161 | 
            -
                       | 
| 162 | 
            -
             | 
| 163 | 
            -
             | 
| 164 | 
            -
             | 
| 165 | 
            -
             | 
| 166 | 
            -
                         | 
| 167 | 
            -
             | 
| 168 | 
            -
             | 
| 169 | 
            -
             | 
| 170 | 
            -
                         | 
| 171 | 
            -
             | 
| 172 | 
            -
                      end
         | 
| 173 | 
            -
                      puts mytable
         | 
| 174 | 
            +
                    if c[:virtual_total] > c[:true_total]
         | 
| 175 | 
            +
                      puts "error: Virtual total doesn't match.."
         | 
| 176 | 
            +
                      exit
         | 
| 177 | 
            +
                    end
         | 
| 178 | 
            +
                    if complete_automatically then      
         | 
| 179 | 
            +
                      others = c[:virtual].map{|i,v|i}-avoid
         | 
| 180 | 
            +
                      others.each { |p|
         | 
| 181 | 
            +
                        c[:virtual][p] += (c[:true_total] - c[:virtual_total])/others.length
         | 
| 182 | 
            +
                      }
         | 
| 183 | 
            +
                    else
         | 
| 184 | 
            +
                      people_to_complete.each { |p|
         | 
| 185 | 
            +
                        c[:virtual][p] += (c[:true_total] - c[:virtual_total])/people_to_complete.length
         | 
| 186 | 
            +
                      }
         | 
| 174 187 | 
             
                    end
         | 
| 175 | 
            -
                    ###
         | 
| 176 188 | 
             
                  end
         | 
| 177 | 
            -
             | 
| 178 | 
            -
             | 
| 179 | 
            -
             | 
| 180 | 
            -
                   | 
| 181 | 
            -
             | 
| 182 | 
            -
             | 
| 189 | 
            +
                  c[:balance].each_key{|person|
         | 
| 190 | 
            +
                    c[:balance][person] = c[:true][person] - c[:virtual][person]
         | 
| 191 | 
            +
                  }
         | 
| 192 | 
            +
                  ### PRINT INTERMEDIATE RESULTS ###
         | 
| 193 | 
            +
                  if intermediate then
         | 
| 194 | 
            +
                    puts "==== #{c[:original_str].to_s} ===="
         | 
| 195 | 
            +
                    puts "total: #{c[:true_total].to_s}"
         | 
| 196 | 
            +
                    mytable = table do |t|
         | 
| 197 | 
            +
                      t.headings = people.sort
         | 
| 198 | 
            +
                      t << c[:true].sort.map{|z|z[1] == 0 ? "" : "%.1f" % z[1]}
         | 
| 199 | 
            +
                      t << c[:virtual].sort.map{|z|z[1] == 0 ? "" : "%.1f" % z[1]}
         | 
| 200 | 
            +
                      t.add_separator
         | 
| 201 | 
            +
                      t << c[:balance].sort.map{|z|z[1] == 0 ? "" : "%.1f" % z[1]}
         | 
| 202 | 
            +
                      t.add_separator
         | 
| 203 | 
            +
                      final = get_final(sq, people)[0]
         | 
| 204 | 
            +
                      t << final.sort.map{|z|z[1] == 0 ? "" : "%.1f" % z[1]}
         | 
| 205 | 
            +
                    end
         | 
| 206 | 
            +
                    puts mytable
         | 
| 207 | 
            +
                  end
         | 
| 208 | 
            +
                  ###
         | 
| 209 | 
            +
                end
         | 
| 210 | 
            +
              }
         | 
| 211 | 
            +
              ### PRINT FINAL RESULTS ###
         | 
| 212 | 
            +
              final = get_final(sq, people)
         | 
| 213 | 
            +
              puts "in local currency:" if currency_change > 0
         | 
| 214 | 
            +
              people_to_print = people.normalize_length
         | 
| 215 | 
            +
              people.each_index{|i|
         | 
| 216 | 
            +
                person = people[i]
         | 
| 217 | 
            +
                puts "#{people_to_print[i]} \t #{final[0][person] > 0 ? 'receives' : 'gives   '}   #{"% 8.2f" % final[0][person]} spent #{"% 8.2f" % final[1][person]} given #{"% 8.2f" % final[2][person]}"
         | 
| 218 | 
            +
              }
         | 
| 219 | 
            +
              puts "in converted currency:" if currency_change > 0
         | 
| 220 | 
            +
              people.each_index{|i|
         | 
| 221 | 
            +
                person = people[i]
         | 
| 222 | 
            +
                puts "#{people_to_print[i]} \t #{final[0][person] > 0 ? 'receives' : 'gives   '}   #{"% 8.2f" % (final[0][person]*currency_change)} spent #{"% 8.2f" % (final[1][person]*currency_change)} given #{"% 8.2f" % (final[2][person]*currency_change)}" if currency_change > 0
         | 
| 223 | 
            +
              }
         | 
| 183 224 | 
             
            end
         | 
| 225 | 
            +
             | 
| 226 | 
            +
            process_command(ARGV)
         | 
    
        metadata
    CHANGED
    
    | @@ -1,13 +1,13 @@ | |
| 1 1 | 
             
            --- !ruby/object:Gem::Specification 
         | 
| 2 2 | 
             
            name: moneybook
         | 
| 3 3 | 
             
            version: !ruby/object:Gem::Version 
         | 
| 4 | 
            -
              hash:  | 
| 4 | 
            +
              hash: 21
         | 
| 5 5 | 
             
              prerelease: 
         | 
| 6 6 | 
             
              segments: 
         | 
| 7 7 | 
             
              - 0
         | 
| 8 8 | 
             
              - 1
         | 
| 9 | 
            -
              -  | 
| 10 | 
            -
              version: 0.1. | 
| 9 | 
            +
              - 7
         | 
| 10 | 
            +
              version: 0.1.7
         | 
| 11 11 | 
             
            platform: ruby
         | 
| 12 12 | 
             
            authors: 
         | 
| 13 13 | 
             
            - luca cioria
         | 
| @@ -15,7 +15,7 @@ autorequire: | |
| 15 15 | 
             
            bindir: bin
         | 
| 16 16 | 
             
            cert_chain: []
         | 
| 17 17 |  | 
| 18 | 
            -
            date: 2011-01- | 
| 18 | 
            +
            date: 2011-01-18 00:00:00 +01:00
         | 
| 19 19 | 
             
            default_executable: moneybook
         | 
| 20 20 | 
             
            dependencies: 
         | 
| 21 21 | 
             
            - !ruby/object:Gem::Dependency 
         |