From 7055d36b4459845272928a4ff6b4a53943930237 Mon Sep 17 00:00:00 2001 From: cytopia Date: Thu, 1 Feb 2018 15:14:06 +0100 Subject: [PATCH 1/2] Make columns configurable in config file --- .travis.yml | 1 + README.md | 8 +++ bin/coinwatch | 180 +++++++++++++++++++++++++++++++------------------- 3 files changed, 121 insertions(+), 68 deletions(-) diff --git a/.travis.yml b/.travis.yml index 8d956ea..5456fde 100644 --- a/.travis.yml +++ b/.travis.yml @@ -64,3 +64,4 @@ script: - if flake8 --version >/dev/null 2>&1; then flake8 --max-line-len=100 bin/coinwatch; fi # Run - ./bin/coinwatch -c example/config.yml + - ./bin/coinwatch -c example/config.yml -r "name diffprice amount invest wealth profit" diff --git a/README.md b/README.md index ee0f30d..8458486 100644 --- a/README.md +++ b/README.md @@ -76,6 +76,14 @@ Configuration is done in yaml format. If you have never heard about yaml before, ### Structure The configuration file is build up like this: ```yml +# Configure coinwatch +config: + # Configure what columns to display and in what order. + # To see all available columns view help: $ coinwatch --help + # Columns specified via command line (-r) take precedence + columns: name date buyprice diffprice nowprice amount invest wealth profit percent + +# Configure your purchases trades: # CURRENCY_ID is found by looking up the 'id' key from # https://api.coinmarketcap.com/v1/ticker/?limit=0 diff --git a/bin/coinwatch b/bin/coinwatch index ce6f107..1511a57 100755 --- a/bin/coinwatch +++ b/bin/coinwatch @@ -59,28 +59,41 @@ except Exception: NAME = 'coinwatch' AUTHOR = 'cytopia' -VERSION = '0.5' +VERSION = '0.6' API_URL = 'https://api.coinmarketcap.com/v1/ticker/?limit=0' # Row settings -ROW_SETTINGS = { +COL_SETTINGS = { # colname #col width #precision #headline - 'coin': {'width': 13, 'prec': None, 'head': 'COIN'}, + 'name': {'width': 13, 'prec': None, 'head': 'CURRENCY'}, 'date': {'width': 10, 'prec': None, 'head': 'BUY DATE'}, 'buyprice': {'width': 14, 'prec': 6, 'head': '$ BUY PRICE/c'}, 'diffprice': {'width': 15, 'prec': 6, 'head': '$ DIFF PRICE/c'}, 'nowprice': {'width': 14, 'prec': 6, 'head': '$ NOW PRICE/c'}, - 'amount': {'width': 15, 'prec': 8, 'head': 'AMOUNT'}, + 'amount': {'width': 16, 'prec': 6, 'head': 'NUM COINS'}, 'invest': {'width': 10, 'prec': 2, 'head': '$ INVEST'}, 'wealth': {'width': 10, 'prec': 2, 'head': '$ WEALTH'}, 'profit': {'width': 12, 'prec': 2, 'head': '$ PROFIT'}, 'percent': {'width': 7, 'prec': 1, 'head': 'PERCENT'} } -# What rows to display and in what order -# Can be overwritten via command line arguments -ROW_DISPLAY = [ - 'coin', +# All available columns +COL_AVAILABLE = [ + 'name', + 'date', + 'buyprice', + 'diffprice', + 'nowprice', + 'amount', + 'invest', + 'wealth', + 'profit', + 'percent' +] +# Default columns to display if not otherwise overwritten via +# configuration file or command line arguments +COL_DEFAULT = [ + 'name', 'date', 'buyprice', 'diffprice', @@ -127,6 +140,15 @@ EXAMPLE_CONFIG = '''--- # Example config: # --------------- + +# Configure coinwatch +config: + # Configure what columns to display and in what order. + # To see all available columns view help: $ coinwatch --help + # Columns specified via command line (-r) take precedence + columns: name date buyprice diffprice nowprice amount invest wealth profit percent + +# Configure your purchases trades: bitcoin: # Options-1 @@ -464,24 +486,28 @@ def print_help(): %s [--version] %s is a low-dependency python[23] client to keep track of your crypto trades -and easily let's you see if you are winning or losing. +and easily let's you see if you are winning or losing. If you are not actually +trading you can use it to simulate purchases and see what would have happened if. OPTIONS: - -c, --config Specify path of an alternative configuration file. + -c, --config Specify path of an alternative configuration file. Store + different configurations in different configuration files in + order to simulate multiple profiles. Examples: - -c path/to/conf.yml - -r, --row Specify the order and columns to use in a row. - In case you dont need all columns to be shown or want - a different order of columns, use this argument to specify it. + -c path/to/conf/john.yml + -c path/to/conf/jane.yml + -r, --row Specify the order and columns to use in a row. In case you + dont need all columns to be shown or want a different order of + columns, use this argument to specify it. + Available columns: + %s Examples: -r "coin date profit percent" -r "coin buyprice nowprice amount wealth" - Default: - -r "coin date buyprice diffprice nowprice amount invest wealth profit percent" - -t, --table Specify different table border. + -t, --table Specify different table border. In case you need to process + the output of this tool use 'ascii'. Available values: 'thin', 'thick' and 'ascii'. The default is 'thin'. - In case you need to process the output of this tool use 'ascii'. Examples: -t thin -t thick @@ -495,18 +521,10 @@ NOTE: No financial aid, support or any other recommendation is provided. Trade at your own risk! And only invest what you can effort to lose. -API: - Currently supported remote price and coin API's are: - - coinmarketcap - CONFIGURATION: When starting %s for the first time a base configuration file will be - created in ~/.config/%s/config.yml. - You should edit this file and add your trades: - - What currency - - When bought - - How much bought - - Price for 1 coin of currency at that date''' % (NAME, NAME, NAME, NAME, NAME, NAME)) + created in ~/.config/%s/config.yml''' % + (NAME, NAME, NAME, NAME, ' '.join(COL_AVAILABLE), NAME, NAME)) def fdec(number, length, places, settings, colorize): @@ -560,35 +578,56 @@ def get_config_path(): return conf -def get_trades(settings): +def read_config(path): '''Read trades from local yaml configuration file''' - if settings['config']: - path = settings['config'] - else: + if not path: path = get_config_path() + data = dict() if os.path.isfile(path): with open(path, 'r') as stream: data = to_yaml(stream) - if 'trades' in data: - return data['trades'] - return dict() + # Fill up defaults + if 'trades' not in data: + data['trades'] = dict() + if 'config' not in data: + data['config'] = dict() + + return data + + +def validate_config(config): + '''Validate configuration file''' + + # Validate config.columns + if 'config' in config: + if 'columns' in config['config'] and config['config']['columns']: + for col in config['config']['columns'].split(' '): + if col not in COL_AVAILABLE: + logerr('[ERR] Invalid colum name in config: \'' + col + '\'') + logerr('[ERR] Valid names: ' + ', '.join(COL_AVAILABLE)) + sys.exit(2) + + # Validate trades + if not config['trades']: + print('No trades found, check your config') + sys.exit(0) def format_column(colname, value, settings): '''Format each column''' # No special formatting for those - if colname == 'coin' or colname == 'date': + if colname == 'name' or colname == 'date': return value # Now we make it really nice and tidy color = bool(colname == 'profit' or colname == 'diffprice') return fdec( value, - ROW_SETTINGS[colname]['width'], - ROW_SETTINGS[colname]['prec'], + COL_SETTINGS[colname]['width'], + COL_SETTINGS[colname]['prec'], settings, color ) @@ -605,15 +644,15 @@ def print_stats(currencies, trades, settings): } # Get columns to display - display_columns = settings['rows'] + display_columns = settings['cols'] # Initialize the table tbl = Table(len(display_columns), settings['table']) - tbl.set_col_widths(*[ROW_SETTINGS[x]['width'] for x in display_columns]) + tbl.set_col_widths(*[COL_SETTINGS[x]['width'] for x in display_columns]) # Print headline print(tbl.sep_first()) - print(tbl.row(*[ROW_SETTINGS[x]['head'] for x in display_columns])) + print(tbl.row(*[COL_SETTINGS[x]['head'] for x in display_columns])) print(tbl.sep()) for currency in currencies: @@ -672,7 +711,7 @@ def print_stats(currencies, trades, settings): # Add all available row values into dict values = dict() - values['coin'] = format_column('coin', name, settings) + values['name'] = format_column('name', name, settings) values['date'] = format_column('date', str(trade.get('date', '-')), settings) values['buyprice'] = format_column('buyprice', buyprice, settings) values['diffprice'] = format_column('diffprice', diffprice, settings) @@ -697,7 +736,7 @@ def print_stats(currencies, trades, settings): # Print overall summary values = dict() - values['coin'] = 'TOTAL' + values['name'] = 'TOTAL' values['date'] = '' values['buyprice'] = '' values['diffprice'] = '' @@ -766,11 +805,11 @@ def parse_args(argv, settings): # Custom rows to display in the given order elif opt in ('-r', '--row'): for col in arg.split(' '): - if col not in ROW_DISPLAY: + if col not in COL_AVAILABLE: logerr('[ERR] Invalid colum name: \'' + col + '\'') - logerr('[ERR] Valid names: ' + ', '.join(ROW_DISPLAY)) + logerr('[ERR] Valid names: ' + ', '.join(COL_AVAILABLE)) sys.exit(2) - settings['rows'] = arg.split() + settings['cols'] = arg.split() # Choose table border elif opt in ('-t', '--table'): if arg not in('thin', 'thick', 'ascii'): @@ -783,7 +822,7 @@ def parse_args(argv, settings): if not os.path.isfile(arg): logerr('[ERR] ' + opt + ' specified config does not exist: ' + arg) sys.exit(2) - settings['config'] = arg + settings['path'] = arg # Disable color elif opt in ('-n', '--nocolor'): settings['color'] = False @@ -804,37 +843,42 @@ def parse_args(argv, settings): def main(argv): '''Main entrypoint.''' - # Default settings if not otherwise specified via cmd args + # Default settings if not otherwise specified via config or cmd args settings = { - 'config': None, - 'color': True, - 'human': False, - 'verbose': False, - 'table': 'thin', - 'rows': ROW_DISPLAY + 'path': None, # Path to configuration file + 'color': True, # Colorize output + 'human': False, # Human readable number format + 'verbose': False, # Verbosity? + 'table': 'thin', # Table border style + 'cols': dict() # What columns in what order to display } - # Overwrite settings - settings = parse_args(argv, settings) - - # bootstrap (creating config & dir) + # Bootstrap application (creating config & dir) bootstrap() - # Read local trading config - trades = get_trades(settings) + # Get configuration from command line arguments + settings = parse_args(argv, settings) - if not trades: - if settings['config']: - conf = settings['config'] - else: - conf = get_config_path() - print('No trades found, check your config:', conf) - sys.exit(0) + # Read and validate configuration file + config = read_config(settings['path']) + validate_config(config) + + # Apply column settings + if settings['cols']: + # Set via cmd args, all good + pass + elif 'columns' in config['config'] and config['config']['columns']: + # Available in configuration file + settings['cols'] = config['config']['columns'].split() + else: + # Nowhere set, use defaults + settings['cols'] = COL_DEFAULT # Get remote price info currencies = to_json(fetch_url(API_URL)) + # Show trading stats - print_stats(currencies, trades, settings) + print_stats(currencies, config['trades'], settings) ############################################################ From 9b1e54b6cdd95ac4bd385b7ad76d5776b1f5f10b Mon Sep 17 00:00:00 2001 From: cytopia Date: Thu, 1 Feb 2018 15:42:21 +0100 Subject: [PATCH 2/2] Make table border configurable in config file --- README.md | 6 +++++- bin/coinwatch | 26 ++++++++++++++++++++++++-- 2 files changed, 29 insertions(+), 3 deletions(-) diff --git a/README.md b/README.md index 8458486..384a40b 100644 --- a/README.md +++ b/README.md @@ -82,6 +82,10 @@ config: # To see all available columns view help: $ coinwatch --help # Columns specified via command line (-r) take precedence columns: name date buyprice diffprice nowprice amount invest wealth profit percent + # Specify your table border style + # Available values: thin, thick and ascii + # Use ascii if you want to further process the output of this application + table: thin # Configure your purchases trades: @@ -94,7 +98,7 @@ trades: date: # <-- [yyyy-mm-dd] When was that bought ``` -`[1]` `amount`, `invest` and `price` at the same time? Yes that's right there is duplication, however only always two of those three can be specified at the same time. This gives the possibility to record you trades in three different ways: +**`[1]`** `amount`, `invest` and `price` at the same time? Yes that's right there is duplication, however only always two of those three can be specified at the same time. This gives the possibility to record you trades in three different ways: #### Option-1: amount and invest How many coins did you buy and how much money in total did you spent on that? diff --git a/bin/coinwatch b/bin/coinwatch index 1511a57..f553f7a 100755 --- a/bin/coinwatch +++ b/bin/coinwatch @@ -147,6 +147,10 @@ config: # To see all available columns view help: $ coinwatch --help # Columns specified via command line (-r) take precedence columns: name date buyprice diffprice nowprice amount invest wealth profit percent + # Specify your table border style + # Available values: thin, thick and ascii + # Use ascii if you want to further process the output of this application + table: thin # Configure your purchases trades: @@ -600,14 +604,21 @@ def read_config(path): def validate_config(config): '''Validate configuration file''' - # Validate config.columns + # Validate config if 'config' in config: + # Columns if 'columns' in config['config'] and config['config']['columns']: for col in config['config']['columns'].split(' '): if col not in COL_AVAILABLE: logerr('[ERR] Invalid colum name in config: \'' + col + '\'') logerr('[ERR] Valid names: ' + ', '.join(COL_AVAILABLE)) sys.exit(2) + # Table border + if 'table' in config['config'] and config['config']['table']: + if config['config']['table'] not in ('thin', 'thick', 'ascii'): + logerr('[ERR] Invalid table border style in config: ' + config['config']['table']) + logerr('[ERR] Allowed values: thin, thick and ascii') + sys.exit(2) # Validate trades if not config['trades']: @@ -849,7 +860,7 @@ def main(argv): 'color': True, # Colorize output 'human': False, # Human readable number format 'verbose': False, # Verbosity? - 'table': 'thin', # Table border style + 'table': None, # Table border style 'cols': dict() # What columns in what order to display } @@ -874,6 +885,17 @@ def main(argv): # Nowhere set, use defaults settings['cols'] = COL_DEFAULT + # Apply table border settings + if settings['table']: + # Set via cmd args, all good + pass + elif 'table' in config['config'] and config['config']['table']: + # Available in configuration file + settings['table'] = config['config']['table'] + else: + # Nowhere set, use defaults + settings['table'] = 'thin' + # Get remote price info currencies = to_json(fetch_url(API_URL))