use std::collections::HashMap;
use std::{fs, process};
use anyhow::{bail, format_err, Error};
use serde::Deserialize;
use structopt::StructOpt;
use crate::common::{ChartType, TimeFrame};
use crate::portfolio::Portfolio;
use crate::theme::Theme;
use crate::widget::KagiOptions;
pub fn resolve_opts() -> Opts {
let mut opts = get_cli_opts();
if let Ok(config_opts) = get_config_opts() {
opts.chart_type = opts.chart_type.or(config_opts.chart_type);
opts.symbols = opts.symbols.or(config_opts.symbols);
opts.time_frame = opts.time_frame.or(config_opts.time_frame);
opts.update_interval = opts.update_interval.or(config_opts.update_interval);
opts.enable_pre_post = opts.enable_pre_post || config_opts.enable_pre_post;
opts.hide_help = opts.hide_help || config_opts.hide_help;
opts.hide_prev_close = opts.hide_prev_close || config_opts.hide_prev_close;
opts.hide_toggle = opts.hide_toggle || config_opts.hide_toggle;
opts.show_volumes = opts.show_volumes || config_opts.show_volumes;
opts.show_x_labels = opts.show_x_labels || config_opts.show_x_labels;
opts.summary = opts.summary || config_opts.summary;
opts.trunc_pre = opts.trunc_pre || config_opts.trunc_pre;
opts.theme = config_opts.theme;
opts.kagi_options = config_opts.kagi_options;
opts.portfolio = config_opts.portfolio;
}
opts
}
fn get_cli_opts() -> Opts {
Opts::from_args()
}
fn get_config_opts() -> Result<Opts, Error> {
let config_dir = dirs_next::config_dir()
.ok_or_else(|| format_err!("Could not get config directory"))?
.join("tickrs");
if !config_dir.exists() {
let _ = fs::create_dir_all(&config_dir);
}
let config_path = config_dir.join("config.yml");
if !config_path.exists() {
let _ = fs::write(&config_path, DEFAULT_CONFIG);
}
let config = fs::read_to_string(&config_path)?;
let opts = match serde_yaml::from_str::<Option<Opts>>(&config) {
Ok(Some(opts)) => opts,
Ok(None) => bail!("Empty config file"),
Err(e) => {
println!(
"Error parsing config file, make sure format is valid\n\n {}",
e
);
process::exit(1);
}
};
Ok(opts)
}
#[derive(Debug, StructOpt, Clone, Deserialize, Default)]
#[structopt(
name = "tickrs",
about = "Realtime ticker data in your terminal 📈",
version = env!("CARGO_PKG_VERSION")
)]
#[serde(default)]
pub struct Opts {
#[structopt(short, long, possible_values(&["line", "candle", "kagi"]))]
pub chart_type: Option<ChartType>,
#[structopt(short, long, use_delimiter = true)]
pub symbols: Option<Vec<String>>,
#[structopt(short = "t", long, possible_values(&["1D", "1W", "1M", "3M", "6M", "1Y", "5Y"]))]
pub time_frame: Option<TimeFrame>,
#[structopt(short = "i", long)]
pub update_interval: Option<u64>,
#[structopt(short = "p", long)]
pub enable_pre_post: bool,
#[structopt(long)]
pub hide_help: bool,
#[structopt(long)]
pub hide_prev_close: bool,
#[structopt(long)]
pub hide_toggle: bool,
#[structopt(long)]
pub show_volumes: bool,
#[structopt(short = "x", long)]
pub show_x_labels: bool,
#[structopt(long)]
pub summary: bool,
#[structopt(long)]
pub trunc_pre: bool,
#[structopt(skip)]
pub theme: Option<Theme>,
#[structopt(skip)]
pub kagi_options: HashMap<String, KagiOptions>,
#[structopt(skip)]
pub portfolio: Option<Portfolio>,
}
const DEFAULT_CONFIG: &str = "---
# List of ticker symbols to start app with
#symbols:
# - SPY
# - AMD
# Chart type to start app with
# Default is line
# Possible values: line, candle, kagi
#chart_type: candle
# Use specified time frame when starting program and when new stocks are added
# Default is 1D
# Possible values: 1D, 1W, 1M, 3M, 6M, 1Y, 5Y
#time_frame: 1D
# Interval to update data from API (seconds)
# Default is 1
#update_interval: 1
# Enable pre / post market hours for graphs
#enable_pre_post: true
# Hide help icon in top right
#hide_help: true
# Hide previous close line on 1D chart
#hide_prev_close: true
# Hide toggle block
#hide_toggle: true
# Show volumes graph
#show_volumes: true
# Show x-axis labels
#show_x_labels: true
# Start in summary mode
#summary: true
# Truncate pre market graphing to only 30 minutes prior to markets opening
#trunc_pre: true
# Ticker options for Kagi charts
#
# A map of each ticker with reversal and/or price fields (both optional). If no
# entry is defined for a symbol, a default of 'close' price and 1% for 1D and 4%
# for non-1D timeframes is used. This can be updated in the GUI by pressing 'e'
#
# reversal can be supplied as a single value, or a map on time frame to give each
# time frame a different reversal amount
#
# reversal.type can be 'amount' or 'pct'
#
# price can be 'close' or 'high_low'
#
#kagi_options:
# SPY:
# reversal:
# type: amount
# value: 5.00
# price: close
# AMD:
# price: high_low
# TSLA:
# reversal:
# type: pct
# value: 0.08
# NVDA:
# reversal:
# 1D:
# type: pct
# value: 0.02
# 5Y:
# type: pct
# value: 0.10
# Apply a custom theme
#
# All colors are optional. If commented out / omitted, the color will get sourced
# from your terminal color scheme
#theme:
# background: '#403E41'
# gray: '#727072'
# profit: '#ADD977'
# loss: '#FA648A'
# text_normal: '#FCFCFA'
# text_primary: '#FFDA65'
# text_secondary: '#79DBEA'
# border_primary: '#FC9766'
# border_secondary: '#FCFCFA'
# border_axis: '#FC9766'
# highlight_focused: '#FC9766'
# highlight_unfocused: '#727072'
# Portfolio tracking
#
# Track your portfolio holdings to see profit/loss
# Each ticker is tracked with quantity and average purchase price
# Ticker symbols must match those in the symbols list in order to be visible
#
#portfolio:
# AMD:
# quantity: 1.3
# average_price: 221
# SPY:
# quantity: 100
# average_price: 450.25
";