lib/ldgr/parser.rb in ldgr-0.1.8 vs lib/ldgr/parser.rb in ldgr-0.1.9
- old
+ new
@@ -8,23 +8,86 @@
require 'strscan'
require 'fileutils'
require 'yaml'
module Ldgr
+ # Parses configuration options.
+ #
+ #
+ # Examples
+ #
+ # Ldgr::Parser.parse
+ # # => some file action
+ #
+ # Returns nothing on success.
class Parser
FILEBASE = Dir.home + '/.config/ledger/'
- FILE = FILEBASE + 'transactions.dat'
VERSION = Ldgr::VERSION
PROGRAM_NAME = 'ldgr'
MATCH = /(?=(\n\d\d\d\d-\d\d-\d\d)(=\d\d\d\d-\d\d-\d\d)*)|\z/
OTHER_MATCH = /(?=(\d\d\d\d-\d\d-\d\d)(=\d\d\d\d-\d\d-\d\d)*)/
- COMMANDS = %w(add sort tag clear open)
- SETUP_FILES = %w(transactions.dat accounts.dat budgets.dat aliases.dat commodities.dat setup.dat ledger.dat ldgr.yaml)
- CONFIG_FILE = Pathname(FILEBASE + 'ldgr.yaml')
- LDGR_DEFAULTS = { currency: '$', equity: 'Cash' }.to_h
- def self.parse
+ attr_accessor :transactions_file, :config
+
+ # Public: Creates a new Parser object
+ #
+ # config - A hash of config options
+ #
+ # Examples
+ #
+ # new(config: {currency: '¥'})
+ # # => <ParserObject>
+ #
+ # Returns a Parser object.
+ def initialize(config: {})
+ @transactions_file = defaults.fetch(:transactions_file)
+ @config = defaults.merge(user_config).merge(config)
+ end
+
+ # Public: User-specified config options
+ #
+ # Examples
+ #
+ # user_config
+ # # => {all the config options from the user's YAML file}
+ #
+ # Returns a hash of user-specified config options.
+ def user_config
+ path = Pathname(FILEBASE + 'ldgr.yaml')
+ YAML.load_file(path).to_h
+ end
+
+ # Public: available commands
+ #
+ # Examples
+ #
+ # commands
+ #
+ # Returns an array of command names.
+ def commands
+ %w(add sort tag clear open)
+ end
+
+ # Public: expected setup files
+ #
+ # Examples
+ #
+ # setup_files
+ #
+ # Returns an array of file names.
+ def self.setup_files
+ %w(transactions.dat accounts.dat budgets.dat aliases.dat commodities.dat setup.dat ledger.dat ldgr.yaml)
+ end
+
+ # Public: Kicks off the CLI
+ #
+ # Examples
+ #
+ # parse
+ #
+ # Returns nothing.
+ def parse
cli = OptionParser.new do |o|
o.banner = "Usage #{PROGRAM_NAME} [add|sort|tag|clear|open]"
o.program_name = PROGRAM_NAME
o.version = VERSION
@@ -37,41 +100,54 @@
o.define '-f', '--file=FILE', String, 'a file of transactions'
o.define '-A', '--amount=AMOUNT', String, 'the amount of the transaction'
o.define '-p', '--payee=PAYEE', String, 'the payee of the transaction'
end
- config = defaults
command = String(cli.parse(ARGV, into: config)[0])
- binding.irb
- send(command, config) if COMMANDS.include? command
+ send(command) if commands.include? command
end
- def self.add(config)
+ # Public: Adds a transaction to the transactions_file.
+ #
+ # Examples
+ #
+ # add
+ #
+ # Returns nothing.
+ def add
error_policy = ->(key) { fail "You need to provide a value for #{key.to_s}." }
transaction = Transaction.new do |t|
- date = String(config.fetch(:date) { Date.today } )
- effective = String(config.fetch(:effective) { Date.today })
+ date = String(config.fetch(:date) { |key| error_policy.call(key) })
+ effective = String(config.fetch(:effective) { |key| error_policy.call(key) })
t.payee = config.fetch(:payee) { |key| error_policy.call(key) }
t.account = config.fetch(:account) { |key| error_policy.call(key) }
t.amount = config.fetch(:amount) { |key| error_policy.call(key) }
- t.currency = config.fetch(:currency) { defaults.fetch('currency') { '$' } }
- t.equity = config.fetch(:equity) { defaults.fetch('equity') { 'Cash' } }
+ t.currency = config.fetch(:currency) { config.fetch(:currency) }
+ t.equity = config.fetch(:equity) { config.fetch(:equity) }
t.cleared = config[:cleared] ? '* ' : ''
t.date = date == effective ? date : date << '=' << effective
end
- File.open(FILE, 'a') { |file| file.puts transaction }
+ File.open(transactions_file, 'a') { |file| file.puts transaction }
end
- def self.clear(config)
+ # Public: Runs through all uncleared transactions that are passed
+ # their effective date and offers to clear them.
+ #
+ # Examples
+ #
+ # clear
+ #
+ # Returns nothing.
+ def clear
output = ''
pattern = /((^\d{,4}-\d{,2}-\d{,2})(=\d{,4}-\d{,2}-\d{,2})?) ([^\*]+)/
count = 0
- File.open(FILE, 'r') do |transactions|
+ File.open(transactions_file, 'r') do |transactions|
transactions.each_line do |transaction|
match = pattern.match(transaction)
if match && match[3]
effective_date = Date.parse(match[3])
else
@@ -88,20 +164,27 @@
transaction.gsub!(pattern, "#{front} * #{back}") if question =~ /y/i
end
output << transaction
end
end
- IO.write(FILE, output)
+ IO.write(transactions_file, output)
end
- def self.tag(config)
+ # Public: Runs through all transactions with only Expenses set as the account and lets you enter an account name.
+ #
+ # Examples
+ #
+ # tag
+ #
+ # Returns nothing.
+ def tag
output = ''
pattern = /(^\s+Expenses[^:])\s*(¥.+)/
count = 0
previous = ''
- File.open(FILE, 'r') do |transactions|
+ File.open(transactions_file, 'r') do |transactions|
transactions.each_line do |transaction|
match = pattern.match(transaction)
if match
count += 1
puts "\n#{HighLine.color(previous, :blue)} #{HighLine.color(match[2], :red)}"
@@ -110,51 +193,87 @@
end
previous = transaction.chomp
output << transaction
end
end
- IO.write(FILE, output)
+ IO.write(transactions_file, output)
end
- def self.sort(config)
- text = File.read(FILE).gsub(/\n+|\r+/, "\n").squeeze("\n").strip
+ # Public: Sorts all transactions by date.
+ #
+ # Examples
+ #
+ # sort
+ #
+ # Returns nothing.
+ def sort
+ text = File.read(transactions_file).gsub(/\n+|\r+/, "\n").squeeze("\n").strip
scanner = StringScanner.new(text)
results = []
until scanner.eos?
results << scanner.scan_until(MATCH)
scanner.skip_until(OTHER_MATCH)
end
- File.open(FILE, 'w') do |file|
+ File.open(transactions_file, 'w') do |file|
file.puts results.sort
end
end
- def self.open(_)
- def self.open_file(file_to_open)
+ # Public: Opens a settings file from ~/.config/ledger
+ #
+ # Examples
+ #
+ # open accounts
+ # # => accounts file opens in $EDITOR
+ #
+ # Returns nothing.
+ def open
+ def open_file(file_to_open)
checked_file = "#{FILEBASE}#{file_to_open}.dat"
raise "#{checked_file} doesn't exist." unless Pathname(checked_file).exist?
system(ENV['EDITOR'], checked_file)
end
open_file(ARGV[1])
end
- def self.defaults(config_file=CONFIG_FILE)
- LDGR_DEFAULTS.merge(YAML.load_file(config_file).to_h)
+ # Public: ldgr's default configuration options
+ #
+ # Examples
+ #
+ # defaults
+ # # => {all the configuration options}
+ #
+ # Returns a hash of default configuration options.
+ def defaults
+ {
+ currency: '$',
+ equity: 'Cash',
+ effective: Date.today,
+ date: Date.today,
+ cleared: false,
+ transactions_file: FILEBASE + 'transactions.dat'
+ }
end
+ # Private: Prepares users' file system for ldgr.
+ #
+ # Returns nothing.
def self.setup
unless config_exist?
FileUtils.mkdir_p(FILEBASE)
- SETUP_FILES.each do |file|
+ setup_files.each do |file|
FileUtils.touch("#{FILEBASE}#{file}")
end
end
end
- def self.config_exist?(setup_files=SETUP_FILES)
+ # Private: Checks if user already has setup files created.
+ #
+ # Returns nothing.
+ def self.config_exist?
setup_files.each do |file|
return false unless Pathname("#{FILEBASE}#{file}").exist?
end
true
end