From 207b417c3daf8e0c7bcb9ab8c9bbf869fa673639 Mon Sep 17 00:00:00 2001 From: "Glenn Y. Rolland" Date: Sun, 4 Apr 2021 14:47:31 +0200 Subject: [PATCH] Add base for parsing config, wallets & trades --- src/actions.cr | 54 +++++++++++++ src/actions/report.cr | 10 +++ src/actions/tui.cr | 10 +++ src/actions/web.cr | 10 +++ src/cli.cr | 130 +++++++++++++++++++++++++------ src/main.cr | 4 +- src/models.cr | 21 +++++ src/models/config.cr | 13 ++++ src/models/deposit.cr | 12 +++ src/models/fee_trade.cr | 9 +++ src/models/portfolio.cr | 36 +++++++++ src/models/ref_wallet.cr | 10 +++ src/models/trade.cr | 12 +++ src/models/transaction_trade.cr | 11 +++ src/models/ui_config.cr | 11 +++ src/models/wallet.cr | 34 ++++++++ src/models/wallet_value_trade.cr | 10 +++ src/types.cr | 4 + src/version.cr | 1 + 19 files changed, 378 insertions(+), 24 deletions(-) create mode 100644 src/actions.cr create mode 100644 src/actions/report.cr create mode 100644 src/actions/tui.cr create mode 100644 src/actions/web.cr create mode 100644 src/models.cr create mode 100644 src/models/config.cr create mode 100644 src/models/deposit.cr create mode 100644 src/models/fee_trade.cr create mode 100644 src/models/portfolio.cr create mode 100644 src/models/ref_wallet.cr create mode 100644 src/models/trade.cr create mode 100644 src/models/transaction_trade.cr create mode 100644 src/models/ui_config.cr create mode 100644 src/models/wallet.cr create mode 100644 src/models/wallet_value_trade.cr create mode 100644 src/types.cr diff --git a/src/actions.cr b/src/actions.cr new file mode 100644 index 0000000..0a6d401 --- /dev/null +++ b/src/actions.cr @@ -0,0 +1,54 @@ + +module Hodler + class Action + enum Type + Report + TextUi + WebUi + end + + def self.match(action) + return false + end + + def initialize(config : ConfigModel) + @config = config + end + + def perform + wallets = {} of Tuple(String,String) => Float32 + @config.trades.each do |trade| + tr = trade.transaction + from_id = {tr.from.wallet, tr.from.sym} + to_id = {tr.to.wallet, tr.to.sym} + fee_id = {tr.from.wallet, tr.fee.sym} + + wallets[from_id] ||= 0 + wallets[fee_id] ||= 0 + wallets[to_id] ||= 0 + wallets[from_id] -= tr.from.amount + wallets[fee_id] -= tr.fee.amount + wallets[to_id] += tr.from.amount + + pp tr + pp wallets + end + + end + end + + class ActionFactory + def self.build(options, config) + action_class = Action + [ReportAction, WebUiAction, TextUiAction].each do |cls| + action_class = cls if cls.match(options[:action]) + end + action = action_class.new(config) + end + end +end + +require "./actions/report" +require "./actions/tui" +require "./actions/web" + diff --git a/src/actions/report.cr b/src/actions/report.cr new file mode 100644 index 0000000..118309f --- /dev/null +++ b/src/actions/report.cr @@ -0,0 +1,10 @@ + +require "../actions" + +module Hodler + class ReportAction < Action + def self.match(action) + return (action == Action::Type::Report) + end + end +end diff --git a/src/actions/tui.cr b/src/actions/tui.cr new file mode 100644 index 0000000..9e0bf3a --- /dev/null +++ b/src/actions/tui.cr @@ -0,0 +1,10 @@ + +require "../actions" + +module Hodler + class TextUiAction < Action + def self.match(action) + return (action == Action::Type::TextUi) + end + end +end diff --git a/src/actions/web.cr b/src/actions/web.cr new file mode 100644 index 0000000..43f5535 --- /dev/null +++ b/src/actions/web.cr @@ -0,0 +1,10 @@ + +require "../actions" + +module Hodler + class WebUiAction < Action + def self.match(action) + return (action == Action::Type::WebUi) + end + end +end diff --git a/src/cli.cr b/src/cli.cr index 7bb9cd9..94788f5 100644 --- a/src/cli.cr +++ b/src/cli.cr @@ -1,58 +1,144 @@ +require "./models" +require "./actions" +require "./models" + module Hodler class Cli alias Options = { - wallet_file: String, + action: Action::Type, + verbose_enable: Bool, + config_file: String } - # @config : Config? - @options : Options? + property config : ConfigModel? + property options : Options? def initialize - # @config = nil + @config = nil @options = nil end - def parse_options(args) : Options + def self.parse_options(args) : Options # default values - wallet_file = XDGBasedir.full_path("hodler/wallet.yml", :config, :read).as(String) + action = Action::Type::Report + config_file = XDGBasedir.full_path("hodler/wallet.yml", :config, :read).as(String) + verbose_enable = true # parse OptionParser.parse(args) do |parser| - parser.banner = "Usage: hodler [arguments]" + parser.banner = "Usage: #{Version::PROGRAM} [options] [commands] [arguments]" - parser.on "-w WALLET_FILE", "--wallet=WALLET_FILE", "Use the following wallet file" do |file| - wallet_file = file + parser.separator + parser.separator "Options" + + parser.on "-c CONFIG_FILE", "--config=CONFIG_FILE", "Use the following config file" do |file| + config_file = file end - # parser.on "-f DOCKER_COMPOSE_YML", "--config=DOCKER_COMPOSE_YML", "Use the following docker-compose file" do |file| - # docker_compose_yml = file - # end + parser.on "-v", "--verbose", "Be verbose" do + verbose_enable = !verbose_enable + end - parser.on "-v", "--version", "Show version" do + parser.on "--version", "Show version" do puts "version #{Version::VERSION}" exit 0 end - parser.on "-h", "--help", "Show help" do + + parser.on "-h", "--help", "Show this help" do puts parser exit 0 end - complete_with "hodler", parser + parser.on "--completion", "Provide autocompletion for bash" do + # nothing here + end + complete_with Version::PROGRAM, parser + + parser.separator + parser.separator "Commands" + + parser.on("report", "Compute report") do + parser.banner = "Usage: #{Version::PROGRAM} list [arguments]" + action = Action::Type::Report + end + + parser.on("tui", "Run ncurses interactive UI") do + parser.banner = "Usage: #{Version::PROGRAM} tui [arguments]" + action = Action::Type::TextUi + end + + parser.on("web", "Run web interactive UI") do + parser.banner = "Usage: #{Version::PROGRAM} web [arguments]" + action = Action::Type::WebUi + end + + parser.separator + + parser.missing_option do |flag| + STDERR.puts parser + STDERR.puts "ERROR: #{flag} requires an argument.".colorize(:red) + exit(1) + end + + parser.invalid_option do |flag| + STDERR.puts parser + STDERR.puts "ERROR: #{flag} is not a valid option.".colorize(:red) + exit(1) + end + + parser.unknown_args do |unknown_args| + unknown_args = unknown_args - %w(report web tui) + next if unknown_args.empty? + + STDERR.puts parser + STDERR.puts "ERROR: \"#{unknown_args[0]}\" is not a valid command.".colorize(:red) + exit(1) + end + end - @options = { - wallet_file: wallet_file - } + return { + verbose_enable: verbose_enable, + config_file: config_file, + action: action + }.as(Options) + end - return @options.as(Options) + def self.parse_config(options) + puts "Loading configuration...".colorize(:yellow) if options[:verbose_enable] + config_file = options[:config_file] + + if ! File.exists? config_file + STDERR.puts "ERROR: Unable to read configuration file '#{config_file}'".colorize(:red) + exit 1 + end + + yaml_str = File.read(config_file) + config = ConfigModel.from_yaml(yaml_str) + + if config.nil? + STDERR.puts "ERROR: Invalid YAML content in '#{config_file}'" + exit 1 + end + + return config end def self.run(args) app = Cli.new - app.parse_options(args) - # config = app.load_config(opts["config_file"]) - # env_config = App.get_config(config, opts["environment"]) + options = Cli.parse_options(args) + config = Cli.parse_config(options) + app.options = options + app.config = config + + portfolio = PortfolioFactory.build(options, config) + + action = ActionFactory.build(options, config) + action.perform + + rescue ex : YAML::ParseException + STDERR.puts "ERROR: #{ex.message}".colorize(:red) end end end diff --git a/src/main.cr b/src/main.cr index a1e4c39..0be6109 100644 --- a/src/main.cr +++ b/src/main.cr @@ -5,10 +5,10 @@ require "colorize" require "xdg_basedir" require "completion" -# require "./config" require "./version" +require "./types" +require "./models" require "./cli" - Hodler::Cli.run(ARGV) diff --git a/src/models.cr b/src/models.cr new file mode 100644 index 0000000..3335470 --- /dev/null +++ b/src/models.cr @@ -0,0 +1,21 @@ + +require "yaml" + +module Hodler + class Model + include YAML::Serializable + end +end + +require "./types" +require "./models/config" +require "./models/deposit" +require "./models/fee_trade" +require "./models/portfolio" +require "./models/ref_wallet" +require "./models/trade" +require "./models/transaction_trade" +require "./models/ui_config" +require "./models/wallet" +require "./models/wallet_value_trade" + diff --git a/src/models/config.cr b/src/models/config.cr new file mode 100644 index 0000000..137e6d2 --- /dev/null +++ b/src/models/config.cr @@ -0,0 +1,13 @@ + +require "../models" + +module Hodler + class ConfigModel < Model + + property version : String + property ui : UiConfigModel + property wallets : Array(WalletModel) + property deposits : Array(DepositModel) + property trades : Array(TradeModel) + end +end diff --git a/src/models/deposit.cr b/src/models/deposit.cr new file mode 100644 index 0000000..9b5be28 --- /dev/null +++ b/src/models/deposit.cr @@ -0,0 +1,12 @@ + +require "../models" + +module Hodler + class DepositModel < Model + include YAML::Serializable + + property id : String? + property date : String + property deposit : WalletValueTradeModel + end +end diff --git a/src/models/fee_trade.cr b/src/models/fee_trade.cr new file mode 100644 index 0000000..82c2236 --- /dev/null +++ b/src/models/fee_trade.cr @@ -0,0 +1,9 @@ + +require "../models" + +module Hodler + class FeeTradeModel < Model + property amount : AmountT + property sym : String + end +end diff --git a/src/models/portfolio.cr b/src/models/portfolio.cr new file mode 100644 index 0000000..6d700c6 --- /dev/null +++ b/src/models/portfolio.cr @@ -0,0 +1,36 @@ + +require "../models" + +module Hodler + class PortfolioModel < Model + + property wallets : Array(WalletModel) + + + def initialize + @wallets = [] of WalletModel + end + + def import_wallets(wallets : Array(WalletModel)) + wallets.each do |wallet_config| + wallet = WalletModel.new(wallet_config.name, wallet_config.type) + # wallet.import(wallet_config) + pp wallet_config + end + end + + def add_wallet + end + + def remove_wallet + end + end + + class PortfolioFactory + def self.build(options : Cli::Options, config : ConfigModel) + portfolio = PortfolioModel.new + + portfolio.import_wallets(config.wallets) + end + end +end diff --git a/src/models/ref_wallet.cr b/src/models/ref_wallet.cr new file mode 100644 index 0000000..0998f24 --- /dev/null +++ b/src/models/ref_wallet.cr @@ -0,0 +1,10 @@ + +require "../models" + +module Hodler + class RefWalletModel < Model + + property sym : String + property address : String? + end +end diff --git a/src/models/trade.cr b/src/models/trade.cr new file mode 100644 index 0000000..a70e248 --- /dev/null +++ b/src/models/trade.cr @@ -0,0 +1,12 @@ + +require "../models" + +module Hodler + class TradeModel < Model + include YAML::Serializable + + property id : String? + property date : String + property transaction : TransactionTradeModel + end +end diff --git a/src/models/transaction_trade.cr b/src/models/transaction_trade.cr new file mode 100644 index 0000000..76a4563 --- /dev/null +++ b/src/models/transaction_trade.cr @@ -0,0 +1,11 @@ + +require "yaml" + +module Hodler + class TransactionTradeModel < Model + property from : WalletValueTradeModel + property to : WalletValueTradeModel + property fee : FeeTradeModel + end +end + diff --git a/src/models/ui_config.cr b/src/models/ui_config.cr new file mode 100644 index 0000000..1d88ac3 --- /dev/null +++ b/src/models/ui_config.cr @@ -0,0 +1,11 @@ + +require "../models" + +module Hodler + class UiConfigModel < Model + + property refresh_per_sec : UInt32 + property default_sym : String + property columns : Array(String) + end +end diff --git a/src/models/wallet.cr b/src/models/wallet.cr new file mode 100644 index 0000000..9d324cb --- /dev/null +++ b/src/models/wallet.cr @@ -0,0 +1,34 @@ + +require "../models" + +module Hodler + class WalletModel < Model + enum Type + Web + Software + Cold + end + + property name : String + property type : Type + property refs : Array(RefWalletModel) + + def initialize(name, type : Type) + @name = name + @type = type + @refs = [] of RefWalletModel + end + + def add_symbol + end + + def remove_symbol + end + + def add_transaction + end + + def remove_transaction + end + end +end diff --git a/src/models/wallet_value_trade.cr b/src/models/wallet_value_trade.cr new file mode 100644 index 0000000..146db16 --- /dev/null +++ b/src/models/wallet_value_trade.cr @@ -0,0 +1,10 @@ + +require "../models" + +module Hodler + class WalletValueTradeModel < Model + property wallet : String + property amount : AmountT + property sym : String + end +end diff --git a/src/types.cr b/src/types.cr new file mode 100644 index 0000000..e044d4f --- /dev/null +++ b/src/types.cr @@ -0,0 +1,4 @@ + +module Hodler + alias AmountT = Float32 | Int32 +end diff --git a/src/version.cr b/src/version.cr index 8df0a52..5a3d758 100644 --- a/src/version.cr +++ b/src/version.cr @@ -1,6 +1,7 @@ module Hodler class Version + PROGRAM = "hodler" VERSION = "0.1.0" end end