class RockBooks::CommandLineInterface

Constants

HELP_TEXT

Help text to be used when requested by 'h' command, in case of unrecognized or nonexistent command, etc.

PROJECT_URL

For conveniently finding the project on Github from the shell

Attributes

book_set[R]
interactive_mode[R]
run_options[R]
verbose_mode[R]

Public Class Methods

new(run_options) click to toggle source
# File lib/rock_books/cmd_line/command_line_interface.rb, line 71
def initialize(run_options)
  @run_options = run_options
  @interactive_mode = !!(run_options.interactive_mode)
  @verbose_mode = run_options.verbose

  validate_run_options(run_options)
  # book_set is set with a lazy initializer
end

Public Instance Methods

attempt_command_action(command, *args, &error_handler_block) click to toggle source

Look up the command name and, if found, run it. If not, execute the passed block.

# File lib/rock_books/cmd_line/command_line_interface.rb, line 193
def attempt_command_action(command, *args, &error_handler_block)
  no_command_specified = command.nil?
  command = 'help' if no_command_specified

  action = find_command_action(command)
  result = nil

  if action
    result = action.(*args)
  else
    error_handler_block.call
    nil
  end

  if no_command_specified
    puts enclose_in_hyphen_lines('! No operations specified !')
  end
  result
end
call() click to toggle source
# File lib/rock_books/cmd_line/command_line_interface.rb, line 410
def call
  begin
    # By this time, the Main class has removed the command line options, and all that is left
    # in ARGV is the commands and their options.
    if @interactive_mode
      run_shell
    else
      process_command_line
    end

  rescue BadCommandError => error
    separator_line = "! #{'-' * 75} !\n"
    puts '' << separator_line << error.to_s << "\n" << separator_line
    exit(-1)
  end
end
cmd_c() click to toggle source
# File lib/rock_books/cmd_line/command_line_interface.rb, line 248
def cmd_c
  puts chart_of_accounts.report_string
end
cmd_h() click to toggle source
# File lib/rock_books/cmd_line/command_line_interface.rb, line 253
def cmd_h
  print_help
end
cmd_j() click to toggle source
# File lib/rock_books/cmd_line/command_line_interface.rb, line 258
def cmd_j
  journal_names = book_set.journals.map(&:short_name)
  interactive_mode ? journal_names : ap(journal_names)
end
cmd_proj() click to toggle source
# File lib/rock_books/cmd_line/command_line_interface.rb, line 300
def cmd_proj
  puts 'https://github.com/keithrbennett/rock_books'
end
cmd_rec(options) click to toggle source
# File lib/rock_books/cmd_line/command_line_interface.rb, line 305
def cmd_rec(options)
  unless run_options.do_receipts
    raise Error.new("Receipt processing was requested but has been disabled with --no-receipts.")
  end

  data = ReceiptsReportData.new(all_entries, run_options.receipt_dir).fetch

  missing, existing, unused = data[:missing], data[:existing], data[:unused]

  print_missing  = -> { puts "\n\nMissing Receipts:";  ap missing }
  print_existing = -> { puts "\n\nExisting Receipts:"; ap existing }
  print_unused   = -> { puts "\n\nUnused Receipts:";   ap unused }

  case options.first.to_s
    when 'a'  # all
      if run_options.interactive_mode
        data
      else
        print_missing.()
        print_existing.()
        print_unused.()
      end

    when 'm'
      run_options.interactive_mode ? missing : print_missing.()

    when 'e'
      run_options.interactive_mode ? existing : print_existing.()

    when 'u'
      run_options.interactive_mode ? unused : print_unused.()

    when 'x'
      run_options.interactive_mode ? missing : print_missing.()
      run_options.interactive_mode ? unused : print_unused.()

  else
    message = "Invalid option for receipts." + \
        " Must be 'a' for all, 'm' for missing, 'e' for existing, 'u' for unused, or 'x' for errors (missing/unused)."
    if run_options.interactive_mode
      puts message
    else
      raise Error.new(message)
    end
  end
end
cmd_rel() click to toggle source
# File lib/rock_books/cmd_line/command_line_interface.rb, line 275
def cmd_rel
  reload_data
  nil
end
cmd_rep() click to toggle source

All reports as Ruby objects; only makes sense in shell mode.

# File lib/rock_books/cmd_line/command_line_interface.rb, line 282
def cmd_rep
  unless run_options.interactive_mode
    raise Error.new("Option 'all_reports' is only available in shell mode. Try 'write_reports'.")
  end

  os = OpenStruct.new(book_set.all_reports($filter))

  # add hash methods for convenience
  def os.keys; to_h.keys; end
  def os.values; to_h.values; end

  # to access as array, e.g. `a.at(1)`
  def os.at(index); self.public_send(keys[index]); end

  os
end
cmd_w() click to toggle source
# File lib/rock_books/cmd_line/command_line_interface.rb, line 352
def cmd_w
  BookSetReporter.new(book_set, run_options.output_dir, $filter).generate
  nil
end
cmd_x() click to toggle source
# File lib/rock_books/cmd_line/command_line_interface.rb, line 358
def cmd_x
  quit
end
commands() click to toggle source
# File lib/rock_books/cmd_line/command_line_interface.rb, line 363
def commands
  @commands_ ||= [
      Command.new('rec', 'receipts',          -> (*options)  { cmd_rec(options)  }),
      Command.new('rep', 'reports',           -> (*_options) { cmd_rep           }),
      Command.new('w',   'write_reports',     -> (*_options) { cmd_w             }),
      Command.new('c',   'chart_of_accounts', -> (*_options) { cmd_c             }),
      Command.new('jo',  'journals',          -> (*_options) { cmd_j             }),
      Command.new('h',   'help',              -> (*_options) { cmd_h             }),
      Command.new('proj','project_page',      -> (*_options) { cmd_proj          }),
      Command.new('q',   'quit',              -> (*_options) { cmd_x             }),
      Command.new('rel', 'reload_data',       -> (*_options) { cmd_rel           }),
      Command.new('x',   'xit',               -> (*_options) { cmd_x             })
  ]
end
enclose_in_hyphen_lines(string) click to toggle source
# File lib/rock_books/cmd_line/command_line_interface.rb, line 148
def enclose_in_hyphen_lines(string)
  hyphen_line = "#{'-' * 80}\n"
  hyphen_line + string + "\n" + hyphen_line
end
find_command_action(command_string) click to toggle source
# File lib/rock_books/cmd_line/command_line_interface.rb, line 379
def find_command_action(command_string)
  return nil if command_string.nil?

  result = commands.detect do |cmd|
    cmd.max_string.start_with?(command_string) \
  && \
  command_string.length >= cmd.min_string.length  # e.g. 'c' by itself should not work
  end
  result ? result.action : nil
end
load_data() click to toggle source
# File lib/rock_books/cmd_line/command_line_interface.rb, line 269
def load_data
  @book_set = BookSetLoader.load(run_options)
end
Also aliased as: reload_data
method_missing(method_name, *method_args) click to toggle source

For use by the shell when the user types the DSL commands

# File lib/rock_books/cmd_line/command_line_interface.rb, line 215
def method_missing(method_name, *method_args)
  attempt_command_action(method_name.to_s, *method_args) do
    puts(%Q{"#{method_name}" is not a valid command or option. } \
      << 'If you intend for this to be a string literal, ' \
      << 'use quotes or %q{}/%Q{}.')
  end
end
post_process(object) click to toggle source

If a post-processor has been configured (e.g. YAML or JSON), use it.

# File lib/rock_books/cmd_line/command_line_interface.rb, line 392
def post_process(object)
  post_processor ? post_processor.(object) : object
end
post_processor() click to toggle source
# File lib/rock_books/cmd_line/command_line_interface.rb, line 397
def post_processor
  run_options.post_processor
end
print_help() click to toggle source
process_command_line() click to toggle source

Processes the command (ARGV) and any relevant options (ARGV).

CAUTION! In interactive mode, any strings entered (e.g. a network name) MUST be in a form that the Ruby interpreter will recognize as a string, i.e. single or double quotes, %q, %Q, etc. Otherwise it will assume it's a method name and pass it to method_missing!

# File lib/rock_books/cmd_line/command_line_interface.rb, line 230
def process_command_line
  attempt_command_action(ARGV[0], *ARGV[1..-1]) do
    print_help
    raise BadCommandError.new(
        %Q{! Unrecognized command. Command was #{ARGV.first.inspect} and options were #{ARGV[1..-1].inspect}.})
  end
end
quit() click to toggle source
# File lib/rock_books/cmd_line/command_line_interface.rb, line 239
def quit
  if interactive_mode
    exit(0)
  else
    puts "This command can only be run in shell mode."
  end
end
reload_data()
Alias for: load_data
run_pry() click to toggle source

Pry will output the content of the method from which it was called. This small method exists solely to reduce the amount of pry's output that is not needed here.

# File lib/rock_books/cmd_line/command_line_interface.rb, line 157
def run_pry
  binding.pry

  # the seemingly useless line below is needed to avoid pry's exiting
  # (see https://github.com/deivid-rodriguez/pry-byebug/issues/45)
  _a = nil
end
run_shell() click to toggle source

Runs a pry session in the context of this object. Commands and options specified on the command line can also be specified in the shell.

# File lib/rock_books/cmd_line/command_line_interface.rb, line 168
def run_shell
  begin
    require 'pry'
  rescue LoadError
    message = "The 'pry' gem and/or one of its prerequisites, required for running the shell, was not found." +
        " Please `gem install pry` or, if necessary, `sudo gem install pry`."
    raise Error.new(message)
  end

  print_help

  # Enable the line below if you have any problems with pry configuration being loaded
  # that is messing up this runtime use of pry:
  # Pry.config.should_load_rc = false

  # Strangely, this is the only thing I have found that successfully suppresses the
  # code context output, which is not useful here. Anyway, this will differentiate
  # a pry command from a DSL command, which _is_ useful here.
  Pry.config.command_prefix = '%'

  run_pry
end
td(date_string) click to toggle source

Easier than remembering and typing Date.iso8601.

# File lib/rock_books/cmd_line/command_line_interface.rb, line 405
def td(date_string)
  Date.iso8601(date_string)
end
validate_run_options(options) click to toggle source
# File lib/rock_books/cmd_line/command_line_interface.rb, line 81
  def validate_run_options(options)

    if [
        # the command requested was to show the project page
        find_command_action(ARGV[0]) == find_command_action('proj'),

        options.suppress_command_line_validation,
    ].any?
      return  # do not validate
    end

    validate_input_dir = -> do
      File.directory?(options.input_dir) ? nil : "Input directory '#{options.input_dir}' does not exist. "
    end

    validate_output_dir = -> do

      # We need to create the reports directory if it does not already exist.
      # mkdir_p silently returns if the directory already exists.
      begin
        FileUtils.mkdir_p(options.output_dir)
        nil
      rescue Errno::EACCES => error
        "Output directory '#{options.output_dir}' does not exist and could not be created. "
      end
    end

    validate_receipt_dir = -> do
      File.directory?(options.receipt_dir) ? nil : \
          "Receipts directory '#{options.receipt_dir}' does not exist. "
    end

    output = []
    output << validate_input_dir.()
    output << validate_output_dir.()
    if run_options.do_receipts
      output << validate_receipt_dir.()
    end

    output.compact!

    unless output.empty?
      message = <<~HEREDOC
      #{output.compact.join("\n")}

      Running this program assumes that you you have:

      * an input directory containing documents with your accounting data. 
        The default directory for this is #{DEFAULT_INPUT_DIR} and can be overridden
        with the -i/--input_dir option.

      * Unless receipt handling is disabled with the --no-receipts option,
        a directory where receipts can or will be stored.
        The default directory for this is #{DEFAULT_RECEIPT_DIR} and can be overridden
        with the -r/--receipt_dir option.
      
      HEREDOC
      raise Error.new(message)
    end
  end