Class: RVGP::Pta

Inherits:
Object
  • Object
show all
Defined in:
lib/rvgp/pta.rb,
lib/rvgp/pta/ledger.rb,
lib/rvgp/pta/hledger.rb

Overview

A base class, which offers functionality to plain text accounting adapters. At the moment, that means either ‘ledger’, or ‘hledger’. This class contains abstractions and code shared by the HLedger and Ledger classes.

In addition, this class contains the AvailabilityHelper, which can be included by any class, in order to offer shorthand access to this entire suite of functions.

Direct Known Subclasses

HLedger, Ledger

Defined Under Namespace

Modules: AvailabilityHelper Classes: AssertionError, BalanceAccount, HLedger, Ledger, RegisterPosting, RegisterTransaction

Class Method Summary collapse

Instance Method Summary collapse

Class Method Details

.hledgerRVGP::Pta::HLedger

Return a new instance of RVGP::Pta::HLedger

Returns:



278
279
280
# File 'lib/rvgp/pta.rb', line 278

def hledger
  HLedger.new
end

.ledgerRVGP::Pta::Ledger

Return a new instance of RVGP::Pta::Ledger

Returns:



272
273
274
# File 'lib/rvgp/pta.rb', line 272

def ledger
  Ledger.new
end

.ptaRVGP::Pta::Ledger, RVGP::Pta::HLedger

Depending on what’s installed and configured, a pta adapter is returned. The rules that govern what adapter is choosen, works like this:

  1. If pta_adapter= has been set, then, this adapter will be returned.

  2. If ledger is installed on the system, then ledger is returned

  3. If hledger is installed on the system, then hledger is returned

If no pta adapters are available, an error is raised.



289
290
291
292
293
294
295
296
297
298
299
# File 'lib/rvgp/pta.rb', line 289

def pta
  @pta ||= if @pta_adapter
             send @pta_adapter
           elsif ledger.present?
             ledger
           elsif hledger.present?
             hledger
           else
             raise StandardError, 'No pta adapter specified, or detected, on system'
           end
end

.pta_adapter=(driver) ⇒ void

This method returns an undefined value.

Override the default adapter, used by pta. This can be set to one of: nil, :hledger, or :ledger.

Parameters:

  • driver (Symbol)

    The adapter name, in a shorthand form. Downcased, and symbolized.



305
306
307
308
# File 'lib/rvgp/pta.rb', line 305

def pta_adapter=(driver)
  @pta = nil
  @pta_adapter = driver.to_sym
end

Instance Method Details

#adapter_nameSymbol

The name of this adapter, either :ledger or :hledger

Returns:

  • (Symbol)

    The adapter name, in a shorthand form. Downcased, and symbolized.



253
254
255
# File 'lib/rvgp/pta.rb', line 253

def adapter_name
  self.class.name.split(':').last.downcase.to_sym
end

#args_and_opts(*args) ⇒ Array<Object>

Given a splatted array, groups and returns arguments into an array of commands, and a hash of options. Options, are expected to be provided as a Hash, as the last element of the splat.

Most of the public methods in a Pta adapter, have largely-undefined arguments. This is because these methods mostly just pass their method arguments, straight to ledger and hledger for handling. Therefore, the ‘best’ place to find documentation on what arguments are supported, is the man page for hledger/ledger. The reason most of the methods exist (#balance, #register, etc), in a pta adapter, are to control how the output of the command is parsed.

Here are some examples of how arguments are sent straight to a pta command:

  • ledger.balance(‘Personal:Expenses’, file: ‘/tmp/test.journal’) becomes: /usr/bin/ledger xml Personal:Expenses –file /tmp/test.journal

  • pta.register(‘Income’, monthly: true) becomes: /usr/bin/ledger xml Income –sort date –monthly

That being said - there are some options that don’t get passed directly to the pta command. Most of these options are documented below.

This method also supports the following options, for additional handling:

  • :hledger_args - If this is a ledger adapter, this option is removed. Otherwise, the values of this Array will be returned in the first element of the return array.

  • :hledger_opts - If this is a ledger adapter, this option is removed. Otherwise, the values of this Hash will be merged with the second element of the return array.

  • :ledger_args - If this is an hledger adapter, this option is removed. Otherwise, the values of this Array will be returned in the first element of the return array.

  • :ledger_opts - If this is an hledger adapter, this option is removed. Otherwise, the values of this Hash will be merged with the second element of the return array.

Returns:

  • (Array<Object>)

    A two element array. The first element of this array is an Array<String> containing the string arguments that were provided to this method, and/or which should be passed directly to a pta shell command function. The second element of this array is a Hash<Symbol, Object> containing the options that were provided to this method, and which should be passed directly to a pta shell command function.



218
219
220
221
222
223
224
225
226
227
228
229
230
231
232
233
234
235
236
# File 'lib/rvgp/pta.rb', line 218

def args_and_opts(*args)
  opts = args.last.is_a?(Hash) ? args.pop : {}

  if ledger?
    opts.delete :hledger_opts
    opts.delete :hledger_args

    args += opts.delete(:ledger_args) if opts.key? :ledger_args
    opts.merge! opts.delete(:ledger_opts) if opts.key? :ledger_opts
  elsif hledger?
    opts.delete :ledger_opts
    opts.delete :ledger_args

    args += opts.delete(:hledger_args) if opts.key? :hledger_args
    opts.merge! opts.delete(:hledger_opts) if opts.key? :hledger_opts
  end

  [args, opts]
end

#bin_pathString

The path to this adapter’s binary

Returns:

  • (String)

    The path in the filesystem, where this pta bin is located



246
247
248
249
# File 'lib/rvgp/pta.rb', line 246

def bin_path
  # Maybe we should support more than just /usr/bin...
  self.class::BIN_PATH
end

#command(*args) ⇒ String

Returns the output of arguments to a pta adapter.

NOTE: If the RVGP_LOG_COMMANDS environment variable is set. (say, to “1”) this command will output diagnostic information to the console. This information will include the fully expanded command being run, alongside its execution time.

While args and options are largely fed straight to the pta command, for processing, we support the following options, which, are removed from the arguments, and handled in this method.

  • :from_s (String)- If a string is provided here, it’s fed to the STDIN of the pta adapter. And “-f -” is added to the program’s arguments. This instructs the command to treat STDIN as a journal.

Parameters:

  • args (Array<Object>)

    Arguments and options, passed to the pta command. See #args_and_opts for details

Returns:

  • (String)

    The output of a pta executable



147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
# File 'lib/rvgp/pta.rb', line 147

def command(*args)
  opts = args.pop if args.last.is_a? Hash
  open3_opts = {}
  if opts
    args += opts.map do |k, v|
      if k.to_sym == :from_s
        open3_opts[:stdin_data] = v
        %w[-f -]
      else
        [format('--%s', k.to_s), v == true ? nil : v] unless v == false
      end
    end.flatten.compact
  end

  is_logging = ENV.key?('RVGP_LOG_COMMANDS') && !ENV['RVGP_LOG_COMMANDS'].empty?

  cmd = ([bin_path] + args.collect { |a| Shellwords.escape a }).join(' ')

  time_start = Time.now if is_logging
  output, error, status = Open3.capture3 cmd, open3_opts
  time_end = Time.now if is_logging

  # Maybe We should send this to a RVGP.logger.trace...
  if is_logging
    pretty_cmd = ([bin_path] + args).join(' ')

    puts format('(%.2<time>f elapsed) %<cmd>s', time: time_end - time_start, cmd: pretty_cmd)
  end

  unless status.success?
    raise StandardError, format('%<adapter_name>s exited non-zero (%<exitstatus>d): %<msg>s',
                                adapter_name: adapter_name,
                                exitstatus: status.exitstatus,
                                msg: error)
  end

  output
end

#hledger?TrueClass, FalseClass

Indicates whether or not this is a hledger pta adapter

Returns:

  • (TrueClass, FalseClass)

    True if this is an instance of HLedger



265
266
267
# File 'lib/rvgp/pta.rb', line 265

def hledger?
  adapter_name == :hledger
end

#ledger?TrueClass, FalseClass

Indicates whether or not this is a ledger pta adapter

Returns:

  • (TrueClass, FalseClass)

    True if this is an instance of Ledger



259
260
261
# File 'lib/rvgp/pta.rb', line 259

def ledger?
  adapter_name == :ledger
end

#present?TrueClass, FalseClass

Determines whether this adapter is found in the expected path, and is executable

Returns:

  • (TrueClass, FalseClass)

    True if we can expect this adapter to execute



240
241
242
# File 'lib/rvgp/pta.rb', line 240

def present?
  File.executable? bin_path
end

#stats(*args) ⇒ Hash<String, <String,Array<String>>>

Returns the output of the ‘stats’ command, parsed into key/value pairs.

Parameters:

  • args (Array<Object>)

    Arguments and options, passed to the pta command. See #args_and_opts for details

Returns:

  • (Hash<String, <String,Array<String>>>)

    The journal statistics. Values are either a string, or an array of strings, depending on what was output.

Raises:

  • (StandardError)


115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
# File 'lib/rvgp/pta.rb', line 115

def stats(*args)
  # Somehow, it turned out that both hledger and ledger were similar enough, that I could abstract
  # this here....
  args, opts = args_and_opts(*args)
  # TODO: This should get its own error class...
  raise StandardError, "Unexpected argument(s) : #{args.inspect}" unless args.empty?

  command('stats', opts).scan(/^\n? *(?:([^:]+?)|(?:([^:]+?) *: *(.*?))) *$/).each_with_object([]) do |match, sum|
    if match[0]
      sum.last[1] = [sum.last[1]] unless sum.last[1].is_a?(Array)
      sum.last[1] << match[0]
    else
      sum << [match[1], match[2].empty? ? [] : match[2]]
    end
    sum
  end.to_h
end