Module: RbInvoice::Options

Defined in:
lib/rbinvoice/options.rb

Class Method Summary collapse

Class Method Details

.add_invoice_to_data(tasks, start_date, end_date, filename, opts) ⇒ Object



151
152
153
154
155
156
157
158
159
160
161
162
163
164
# File 'lib/rbinvoice/options.rb', line 151

def self.add_invoice_to_data(tasks, start_date, end_date, filename, opts)
  data = opts[:data]
  client = data_for_client(data, opts[:client]) || add_new_client_data(opts[:client], data)
  (client[:invoices] ||= []) << {
    'id' => opts[:invoice_number],
    'start_date' => start_date,
    'end_date' => end_date,
    'filename' => filename
  }
  if not data[:last_invoice] or opts[:invoice_number] > data[:last_invoice]
    data[:last_invoice] = opts[:invoice_number]
  end
  write_data_file(opts)
end

.add_new_client_data(client, data) ⇒ Object



141
142
143
144
145
146
147
148
149
# File 'lib/rbinvoice/options.rb', line 141

def self.add_new_client_data(client, data)
  h = {
    'name' => client,
    'key' => client,
    'invoices' => []
  }
  (data[:clients] ||= []) << h
  return h
end

.address_for_client(data, opts, client) ⇒ Object



211
212
213
# File 'lib/rbinvoice/options.rb', line 211

def self.address_for_client(data, opts, client)
  key_for_client(data, client, :address)
end

.all_clients(data) ⇒ Object



166
167
168
# File 'lib/rbinvoice/options.rb', line 166

def self.all_clients(data)
  data[:clients] || []
end

.data_for_client(data, client) ⇒ Object



174
175
176
# File 'lib/rbinvoice/options.rb', line 174

def self.data_for_client(data, client)
  all_clients(data).select{|x| x[:key] == RbInvoice::to_client_key(client)}.first
end

.dba_for_client(data, opts, client) ⇒ Object



199
200
201
# File 'lib/rbinvoice/options.rb', line 199

def self.dba_for_client(data, opts, client)
  key_for_client(data, client, :dba) || opts[:dba] || 'Illuminated Computing Inc.'
end

.default_data_fileObject

This is a method rather than a constant so that we don’t evaulate ENV until it’s called. That makes it possible for tests to set ENV before running the code.



21
22
23
# File 'lib/rbinvoice/options.rb', line 21

def self.default_data_file
  File.join(ENV['HOME'] || '.', '.rbinvoice')
end

.default_out_directory(opts) ⇒ Object

TODO: Allow per-client settings to override the global setting



73
74
75
# File 'lib/rbinvoice/options.rb', line 73

def self.default_out_directory(opts)
  opts[:out_directory] || '.'
end

.default_out_filename(opts) ⇒ Object

Looks in ~/.rbinvoice



78
79
80
81
82
83
84
# File 'lib/rbinvoice/options.rb', line 78

def self.default_out_filename(opts)
  if opts[:client] and opts[:invoice_number]
    File.join(default_out_directory(opts), "invoice-#{opts[:invoice_number]}-#{opts[:client]}.pdf")
  else
    nil
  end
end

.default_rc_fileObject

This is a method rather than a constant so that we don’t evaulate ENV until it’s called. That makes it possible for tests to set ENV before running the code.



13
14
15
# File 'lib/rbinvoice/options.rb', line 13

def self.default_rc_file
  File.join(ENV['HOME'] || '.', '.rbinvoicerc')
end

.default_template_filenameObject



25
26
27
# File 'lib/rbinvoice/options.rb', line 25

def self.default_template_filename
  File.join(File.dirname(__FILE__), '..', '..', 'templates', 'invoice.tex.liquid')
end

.description_for_client(data, opts, client) ⇒ Object



215
216
217
218
# File 'lib/rbinvoice/options.rb', line 215

def self.description_for_client(data, opts, client)
  desc = key_for_client(data, client, :description)
  "#{desc} from #{opts[:start_date].strftime("%B %-d")} to #{opts[:end_date].strftime("%B %-d, %Y")}."
end

.find_invoice_bounds(d, freq, second_half_of_biweek = false) ⇒ Object

second_half_of_biweek - indicates that ‘d` is from the second half of the biweek. If false (the default), we assume `d` is from the first half.



122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
# File 'lib/rbinvoice/options.rb', line 122

def self.find_invoice_bounds(d, freq, second_half_of_biweek=false)
  case freq.to_sym
  when :weekly
    return week_start(d), week_end(d)
  when :biweekly
    if second_half_of_biweek
      return week_start(d) - 7, week_end(d)
    else
      return week_start(d), week_end(d) + 7
    end
  when :semimonthly
    return semimonth_start(d), semimonth_end(d)
  when :monthly
    return first_day_of_the_month(d), last_day_of_the_month(d)
  else
    raise "Unknown frequency: #{freq}"
  end
end

.first_day_of_the_month(d) ⇒ Object



86
87
88
# File 'lib/rbinvoice/options.rb', line 86

def self.first_day_of_the_month(d)
  Date.new(d.year, d.month, 1)
end

.frequency_for_client(data, client) ⇒ Object



195
196
197
# File 'lib/rbinvoice/options.rb', line 195

def self.frequency_for_client(data, client)
  key_for_client(data, client, :frequency)
end

.frequncy_for_client(data, client) ⇒ Object



183
184
185
# File 'lib/rbinvoice/options.rb', line 183

def self.frequncy_for_client(data, client)
  key_for_client(data, client, :frequency)
end

.full_name_for_client(data, opts, client) ⇒ Object



207
208
209
# File 'lib/rbinvoice/options.rb', line 207

def self.full_name_for_client(data, opts, client)
  key_for_client(data, client, :full_name)
end

.invoices_for_client(data, client) ⇒ Object



187
188
189
# File 'lib/rbinvoice/options.rb', line 187

def self.invoices_for_client(data, client)
  key_for_client(data, client, :invoices) || []
end

.key_for_client(data, client, key) ⇒ Object



178
179
180
181
# File 'lib/rbinvoice/options.rb', line 178

def self.key_for_client(data, client, key)
  d = data_for_client(data, client)
  d = d ? d[key] : nil
end

.last_day_of_the_month(d) ⇒ Object



90
91
92
93
# File 'lib/rbinvoice/options.rb', line 90

def self.last_day_of_the_month(d)
  n = d.next_month
  Date.new(d.year, d.month, (Date.new(n.year, n.month, 1) - 1).day)
end

.last_invoice_for_client(data, client) ⇒ Object



191
192
193
# File 'lib/rbinvoice/options.rb', line 191

def self.last_invoice_for_client(data, client)
  invoices_for_client(data, client).sort_by{|x| x[:end_date]}.last
end

.last_invoice_number(data) ⇒ Object



170
171
172
# File 'lib/rbinvoice/options.rb', line 170

def self.last_invoice_number(data)
  data[:last_invoice]
end

.parse_command_line(argv) ⇒ Object



220
221
222
223
224
225
226
227
228
229
230
231
232
233
234
235
236
237
238
239
240
241
242
243
244
245
246
247
248
249
250
251
252
253
254
255
256
257
258
259
260
261
262
263
264
265
266
267
268
269
270
271
272
273
274
275
276
277
278
279
280
281
282
283
284
285
286
287
288
289
290
291
292
293
# File 'lib/rbinvoice/options.rb', line 220

def self.parse_command_line(argv)
  opts = Trollop::options(argv) do
    version "rbinvoice #{::RbInvoice::VERSION} (c) 2012 Paul A. Jungwirth"
    banner "      USAGE: rbinvoice [options] <client> [filename]\n    EOH\n    opt :help, \"Show a help message\"\n\n    opt :rcfile, \"Use an rc file other than ~/.rbinvoicerc\", :short => '-r'\n    opt :no_rcfile, \"Don't read an rc file\", :default => false, :short => '-R'\n\n    opt :data_file, \"Use a data file other than ${out_directory}/rbinvoice\", :default => nil, :short => '-d'\n    opt :no_data_file, \"Don't update the data file\", :default => false, :short => '-D'\n\n    opt :invoice_number, \"Use a specific invoice number\", :type => :int, :short => '-n'\n    opt :no_write_invoice_number, \"Record the invoice number\", :default => false, :short => '-N'\n\n    opt :spreadsheet, \"Read the given spreadsheet URL\", :type => :string, :short => '-u'\n    opt :start_date, \"Date to begin the invoice (yyyy-mm-dd)\", :type => :string\n    opt :end_date, \"Date to end the invoice (yyyy-mm-dd)\", :type => :string\n\n    opt :template, \"Use the given liquid template\", :type => :string, :default => RbInvoice::Options::default_template_filename\n  end\n  Trollop::die \"client must be given\" unless argv.size > 0\n  opts[:client] = argv.shift\n\n  read_rc_file(opts) unless opts[:no_rcfile]\n  opts[:data_file] ||= \"\#{default_out_directory(opts)}/rbinvoice\"\n  opts[:data] = read_data_file(opts)\n\n  if not opts[:invoice_number] and not last_invoice_number(opts[:data])\n    Trollop::die \"Can't determine invoice number\"\n  end\n  opts[:invoice_number] ||= last_invoice_number(opts[:data]) + 1\n\n  Trollop::die \"can't determine hourly spreadsheet\" unless opts[:spreadsheet]\n\n  opts[:out_filename] = argv.shift\n  if not opts[:out_filename]\n    opts[:out_filename] = default_out_filename(opts)\n    opts[:used_default_out_filename] = true     # TODO if this is set and not quiet, then print the name of the file we wrote to: \"Wrote invoice to \#{out_filename}\"\n  end\n  Trollop::die \"can't infer output filename; please provide one\" unless opts[:out_filename]\n\n  # opts[:start_date] = '2012-07-15'\n  # opts[:end_date]   = '2012-07-31'\n  opts[:start_date] = Date.strptime(opts[:start_date], \"%Y-%m-%d\")  if opts[:start_date]\n  opts[:end_date]   = Date.strptime(opts[:end_date], \"%Y-%m-%d\")    if opts[:end_date]\n\n  opts[:dba] = dba_for_client(opts[:data], opts, opts[:client])\n  opts[:payment_due] = payment_due_for_client(opts[:data], opts, opts[:client])\n  # Read the list of past invoices.\n  # If there are none, assume there is only one invoice to do.\n\n  jobs = []\n\n  last_invoice = last_invoice_for_client(opts[:data], opts[:client])\n  if opts[:end_date] and opts[:start_date]\n    # just do it, regardless of frequency.\n  elsif opts[:end_date] or opts[:start_date]\n    freq = frequency_for_client(opts[:data], opts[:client])\n    if freq\n      opts[:start_date], opts[:end_date] = find_invoice_bounds(opts[:start_date] || opts[:end_date], freq, !!opts[:end_date])\n    else\n      Trollop::die \"can't determine invoice range without frequency\"\n    end\n  else\n    # Do all pending invoices (leave start_date and end_date nil).\n  end\n\n  # return jobs\n\n  return opts[:client], opts[:start_date], opts[:end_date], opts[:out_filename], opts\nend\n"

.parse_data_file(text, opts) ⇒ Object



45
46
47
# File 'lib/rbinvoice/options.rb', line 45

def self.parse_data_file(text, opts)
  text ? RbInvoice::Util::read_with_yaml(text) : {}
end

.parse_rc_file(text, opts) ⇒ Object



63
64
65
66
67
68
69
70
# File 'lib/rbinvoice/options.rb', line 63

def self.parse_rc_file(text, opts)
  rc = (text ? RbInvoice::Util::read_with_yaml(text) : {})
  %w{spreadsheet spreadsheet_user spreadsheet_password out_directory}.each do |key|
    key = key.to_sym
    opts[key] ||= rc[key]
  end
  return opts
end

.payment_due_for_client(data, opts, client) ⇒ Object



203
204
205
# File 'lib/rbinvoice/options.rb', line 203

def self.payment_due_for_client(data, opts, client)
  key_for_client(data, client, :payment_due) || opts[:payment_due] || 'upon receipt'
end

.read_data_file(opts) ⇒ Object



34
35
36
37
38
39
40
41
42
43
# File 'lib/rbinvoice/options.rb', line 34

def self.read_data_file(opts)
  if File.exist?(opts[:data_file])
    ret = parse_data_file(File.read(opts[:data_file]), opts)
    client = data_for_client(ret, opts[:client])
    opts[:rate] = client[:rate] if client
    return ret
  else
    return {}
  end
end

.read_rc_file(opts) ⇒ Object



49
50
51
52
53
54
55
56
57
58
59
60
61
# File 'lib/rbinvoice/options.rb', line 49

def self.read_rc_file(opts)
  Trollop::die :rcfile, "doesn't exist" if opts[:rcfile] and not File.exist?(opts[:rcfile])

  # Don't apply the default until now
  # so we know if the user requested one specifically or not:
  opts[:rcfile] ||= default_rc_file

  if File.exist?(opts[:rcfile])
    return parse_rc_file(File.read(opts[:rcfile]), opts)
  else
    return opts
  end
end

.semimonth_end(d) ⇒ Object



95
96
97
98
99
100
101
# File 'lib/rbinvoice/options.rb', line 95

def self.semimonth_end(d)
  if d.day <= 15
    Date.new(d.year, d.month, 15)
  else
    Date.new(d.year, d.month, last_day_of_the_month(d).day)
  end
end

.semimonth_start(d) ⇒ Object



103
104
105
106
107
108
109
# File 'lib/rbinvoice/options.rb', line 103

def self.semimonth_start(d)
  if d.day <= 15
    first_day_of_the_month(d)
  else
    Date.new(d.year, d.month, 16)
  end
end

.week_end(d) ⇒ Object



116
117
118
# File 'lib/rbinvoice/options.rb', line 116

def self.week_end(d)
  week_start(d) + 7
end

.week_start(d) ⇒ Object



111
112
113
114
# File 'lib/rbinvoice/options.rb', line 111

def self.week_start(d)
  # Assumes the week starts on a Sunday:
  d - d.wday
end

.write_data_file(opts) ⇒ Object



29
30
31
32
# File 'lib/rbinvoice/options.rb', line 29

def self.write_data_file(opts)
  # Add the new invoice to the list of client invoices.
  File.open(opts[:data_file], 'w') { |f| f.puts YAML::dump(RbInvoice::Util::stringify_hash(opts[:data])) }
end