Compare commits

...

5 commits

31 changed files with 240 additions and 131 deletions

View file

@ -1,6 +1,10 @@
BINARIES=hodler
hodler_FILES=$(wildcard src/*.cr src/**/*.cr)
BINARIES=hodlerctl hodlertui hodlerweb
lib_FILES=$(wildcard src/lib/*.cr src/lib/**/*.cr)
hodlerctl_FILES=$(wildcard src/hodlerctl/*.cr src/hodlerctl/**/*.cr) $(lib_FILES)
hodlertui_FILES=$(wildcard src/hodlertui/*.cr src/hodlertui/**/*.cr) $(lib_FILES)
hodlerweb_FILES=$(wildcard src/hodlerweb/*.cr src/hodlerweb/**/*.cr) $(lib_FILES)
all: build

View file

@ -4,6 +4,10 @@ shards:
git: https://github.com/matiasgarciaisaia/completion.git
version: 0.1.0+git.commit.780bab511917b61e7d814801fd9acd4390eda44a
tablo:
git: https://github.com/hutou/tablo.git
version: 0.9.5
xdg_basedir:
git: https://github.com/shmibs/xdg_basedir.git
version: 1.0.2

View file

@ -9,17 +9,29 @@ description: |
Crypto-currencies porfolio monitoring tool
targets:
hodler:
main: src/main.cr
hodlerctl:
main: src/hodlerctl/main.cr
hodlertui:
main: src/hodlertui/main.cr
hodlerweb:
main: src/hodlerweb/main.cr
dependencies:
# Handle XDG environment variables
xdg_basedir:
github: shmibs/xdg_basedir
# Command completion for bash
# (use pull request which fixes compatibility)
completion:
github: matiasgarciaisaia/completion
branch: crystal-0.18
# github: f/completion
# Text tables
tablo:
github: hutou/tablo
# pg:
# github: will/crystal-pg
# version: "~> 0.5"

View file

@ -1,33 +1,36 @@
require "./models"
require "./actions"
require "./models"
require "option_parser"
require "yaml"
require "colorize"
require "xdg_basedir"
require "completion"
require "log"
require "../lib/actions"
require "../lib/models"
require "../lib/types"
require "../lib/version"
module Hodler
class Cli
alias Options = {
action: Action::Type,
verbose_enable: Bool,
config_file: String
}
class CtlCli
property config : ConfigModel?
property options : Options?
property options : GlobalOptions?
def initialize
@config = nil
@options = nil
end
def self.parse_options(args) : Options
def self.parse_options(args) : GlobalOptions
# default values
action = Action::Type::Report
action = ReportAction
config_file = XDGBasedir.full_path("hodler/wallet.yml", :config, :read).as(String)
verbose_enable = true
# parse
OptionParser.parse(args) do |parser|
parser.banner = "Usage: #{Version::PROGRAM} [options] [commands] [arguments]"
parser.banner = "Usage: #{Version::PROGRAM_HODLERCTL} [options] [commands] [arguments]"
parser.separator
parser.separator "Options"
@ -53,25 +56,36 @@ module Hodler
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
parser.on("get", "Get given object") do
parser.banner = "Usage: #{Version::PROGRAM_HODLERCTL} get [arguments]"
parser.separator
parser.separator "Commands"
parser.on("portfolio", "Show current portfolio") do
parser.banner = "Usage: #{Version::PROGRAM_HODLERCTL} portfolio [arguments]"
action = GetPortfolioAction
end
parser.on("wallet", "Show given wallet") do
parser.banner = "Usage: #{Version::PROGRAM_HODLERCTL} portfolio [arguments]"
action = GetWalletAction
end
end
parser.on("tui", "Run ncurses interactive UI") do
parser.banner = "Usage: #{Version::PROGRAM} tui [arguments]"
action = Action::Type::TextUi
end
# parser.on("tui", "Run ncurses interactive UI") do
# parser.banner = "Usage: #{Version::PROGRAM} tui [arguments]"
# action = TextUiAction
# end
parser.on("web", "Run web interactive UI") do
parser.banner = "Usage: #{Version::PROGRAM} web [arguments]"
action = Action::Type::WebUi
end
# parser.on("web", "Run web interactive UI") do
# parser.banner = "Usage: #{Version::PROGRAM} web [arguments]"
# action = WebUiAction
# end
parser.separator
@ -96,18 +110,19 @@ module Hodler
exit(1)
end
complete_with Version::PROGRAM_HODLERCTL, parser
end
return {
verbose_enable: verbose_enable,
config_file: config_file,
action: action
}.as(Options)
}.as(GlobalOptions)
end
def self.parse_config(options)
puts "Loading configuration...".colorize(:yellow) if options[:verbose_enable]
config_file = options[:config_file]
puts "Loading configuration... #{config_file}".colorize(:yellow) if options[:verbose_enable]
if ! File.exists? config_file
STDERR.puts "ERROR: Unable to read configuration file '#{config_file}'".colorize(:red)
@ -126,14 +141,12 @@ module Hodler
end
def self.run(args)
app = Cli.new
options = Cli.parse_options(args)
config = Cli.parse_config(options)
app = CtlCli.new
options = CtlCli.parse_options(args)
config = CtlCli.parse_config(options)
app.options = options
app.config = config
portfolio = PortfolioFactory.build(options, config)
action = ActionFactory.build(options, config)
action.perform
@ -142,3 +155,6 @@ module Hodler
end
end
end
Hodler::CtlCli.run(ARGV)

0
src/hodlertui/main.cr Normal file
View file

0
src/hodlerweb/main.cr Normal file
View file

View file

@ -1,4 +1,6 @@
require "tablo"
module Hodler
class Action
enum Type
@ -11,7 +13,8 @@ module Hodler
return false
end
def initialize(config : ConfigModel)
def initialize(global_options : GlobalOptions, config : ConfigModel)
@global_options = global_options
@config = config
end
@ -29,26 +32,16 @@ module Hodler
wallets[from_id] -= tr.from.amount
wallets[fee_id] -= tr.fee.amount
wallets[to_id] += tr.to.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)
action = options[:action].new(options, config)
end
end
end
require "./actions/report"
require "./actions/tui"
require "./actions/web"
require "./actions/*"

View file

@ -0,0 +1,12 @@
require "../actions"
module Hodler
class GetPortfolioAction < Action
# Display Wallet, Symbol, Held, Initial Value, Current Value, Evolution (%)
def perform
portfolio = PortfolioFactory.build(@global_options, @config)
puts portfolio.symbol_report
end
end
end

View file

View file

@ -0,0 +1,11 @@
require "../actions"
module Hodler
class GetWalletAction < Action
def perform
portfolio = PortfolioFactory.build(@global_options, @config)
puts portfolio.wallet_report
end
end
end

22
src/lib/models.cr Normal file
View file

@ -0,0 +1,22 @@
require "yaml"
module Hodler
class Model
include YAML::Serializable
end
end
require "./types"
require "./models/*"
# 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"
#

View file

@ -0,0 +1,95 @@
require "../models"
module Hodler
class PortfolioModel < Model
getter wallets : Array(WalletModel)
def initialize()
@wallets = [] of WalletModel
@trades = [] of TradeModel
@deposits = [] of DepositModel
end
def import_deposits(deposits : Array(DepositModel))
@deposits = deposits
end
def import_wallets(wallets : Array(WalletModel))
@wallets = wallets
end
def import_trades(trades : Array(TradeModel))
@trades = trades
end
def add_wallet
end
def remove_wallet
end
def symbol_report
wallets = {} of Tuple(String,String) => Float32
@deposits.each do |deposit_wrapper|
deposit = deposit_wrapper.deposit
id = {deposit.wallet, deposit.sym}
wallets[id] ||= 0
wallets[id] += deposit.amount
end
@trades.each do |trade_wrapper|
trade = trade_wrapper.transaction
from_id = {trade.from.wallet, trade.from.sym}
to_id = {trade.to.wallet, trade.to.sym}
fee_id = {trade.from.wallet, trade.fee.sym}
wallets[from_id] ||= 0
wallets[fee_id] ||= 0
wallets[to_id] ||= 0
wallets[from_id] -= trade.from.amount
wallets[fee_id] -= trade.fee.amount
wallets[to_id] += trade.to.amount
end
data = [] of Array(String)
wallets
.each{|key, val| data << [ key[0], key[1].to_s, "%f" % val] }
data.sort! do |x,y|
resA = x[0] <=> y[0]
resB = x[1] <=> y[1]
((resA == 0) ? resB : resA)
end
table = Tablo::Table.new(data, connectors: Tablo::CONNECTORS_SINGLE_ROUNDED) do |t|
t.add_column("Wallet") { |row| row[0] }
t.add_column("Symbol") { |row| row[1] }
t.add_column("Held", align_header: Tablo::Justify::Right, align_body: Tablo::Justify::Right) { |row| row[2] }
t.add_column("Invested") { |row| "FIXME" }
t.add_column("Cur. Price") { |row| "FIXME" }
t.add_column("Cur. Value") { |row| "FIXME" }
end
end
def wallet_report
data = @wallets.map{|row| [row.name, row.type.to_s, row.refs.map{|ref| ref.sym}.join(",")] }
table = Tablo::Table.new(data, connectors: Tablo::CONNECTORS_SINGLE_ROUNDED) do |t|
t.add_column("Name") { |row| row[0] }
t.add_column("Type") { |row| row[1] }
t.add_column("Symbols", width: 40) { |row| row[2] }
end
end
end
class PortfolioFactory
def self.build(options : GlobalOptions, config : ConfigModel) : PortfolioModel
portfolio = PortfolioModel.new
portfolio.import_wallets(config.wallets)
portfolio.import_trades(config.trades)
portfolio.import_deposits(config.deposits)
return portfolio
end
end
end

13
src/lib/types.cr Normal file
View file

@ -0,0 +1,13 @@
require "./actions/*"
module Hodler
alias AmountT = Float64 | Int64
TABLO_CONNECTORS_EMPTY=" "
alias GlobalOptions = {
action: Action.class,
verbose_enable: Bool,
config_file: String
}
end

9
src/lib/version.cr Normal file
View file

@ -0,0 +1,9 @@
module Hodler
class Version
PROGRAM_HODLERCTL = "hodlerctl"
PROGRAM_HODLERTUI = "hodlertui"
PROGRAM_HODLERWEB = "hodlerweb"
VERSION = "0.1.0"
end
end

View file

@ -1,14 +0,0 @@
require "option_parser"
require "yaml"
require "colorize"
require "xdg_basedir"
require "completion"
require "./version"
require "./types"
require "./models"
require "./cli"
Hodler::Cli.run(ARGV)

View file

@ -1,21 +0,0 @@
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"

View file

@ -1,36 +0,0 @@
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

View file

@ -1,4 +0,0 @@
module Hodler
alias AmountT = Float32 | Int32
end

View file

@ -1,7 +0,0 @@
module Hodler
class Version
PROGRAM = "hodler"
VERSION = "0.1.0"
end
end