From 426846c5d5cc3d3f8b058740a8cd31d37de6efb3 Mon Sep 17 00:00:00 2001 From: Ross <9055337+chadsr@users.noreply.github.com> Date: Sun, 26 May 2024 23:11:17 +0200 Subject: [PATCH] feat: Read config from XDG_CONFIG_HOME or custom path (#77) * read config from XDG_CONFIG_HOME or custom path * v1.5.0 * test custom config path parsing * test default/custom XDG_CONFIG_HOME values --- README.md | 94 +++++++++++++++---------------------- config.ini.example | 1 + pyproject.toml | 2 +- tests/test_waybar_crypto.py | 40 +++++++++++++++- waybar_crypto.py | 40 ++++++++++++++-- 5 files changed, 116 insertions(+), 61 deletions(-) diff --git a/README.md b/README.md index fa5a161..7124475 100644 --- a/README.md +++ b/README.md @@ -9,20 +9,25 @@ A [Waybar](https://github.com/Alexays/Waybar) module for displaying cryptocurren ## Requirements -- `python` >=3.8 +- `python` >=3.9 - `python-requests` ## Installation -### Clone to the Waybar modules directory +### AUR + +```shell +yay -S waybar-crypto +``` + +**Note:** *For the time being, the `cryptofont` font still needs installing manually* + +### (Or) Clone to the Waybar modules directory ```shell cd ~/.config/waybar/modules git clone https://github.com/chadsr/waybar-crypto.git crypto && cd crypto/ git submodule update --init --recursive --remote --progress - -# **only** If you for some reason don't want to install python-requests with a package manager, or setup a venv -pip install --user --requirement <(poetry export --without=dev --format requirements.txt) ``` ### Install the needed fonts @@ -36,7 +41,14 @@ ln -s ./.submodules/cryptofont/fonts/cryptofont.ttf ~/.local/share/fonts/TTF fc-cache -f ``` -### Update Waybar configuration +### Create Configuration File + +Create a configuration file at `$XDG_CONFIG_HOME/waybar-crypto/config.ini` (This location can be customised with the `--config-path` flag). The module **will not** run without first creating this configuration file. + +An example can be found in [`config.ini.example`](./config.ini.example) with further options described below in the [Configuration](#configuration) section. + + +### Update Waybar Configuration *Found at `~/.config/waybar/config` by default* @@ -45,7 +57,7 @@ fc-cache -f "format": "{}", "interval": 600, "return-type": "json", - "exec": "~/.config/waybar/modules/crypto/waybar_crypto.py", + "exec": "waybar-crypto", // change this if you installed manually "exec-if": "ping pro-api.coinmarketcap.com -c1" } ``` @@ -56,57 +68,29 @@ fc-cache -f ```css #custom-crypto { - font-family: cryptofont, monospace; + font-family: cryptofont; } ``` ## Configuration -Copy the example configuration file `config.ini.example` to `config.ini`. - -The information displayed can then be customised by editing the `config.ini` configuration file. -(e.g. `~/.config/waybar/modules/crypto/config.ini` if you followed the instructions above) - -```ini -[general] -currency = eur -currency_symbol = € -spacer_symbol = | -display = price,percent_change_24h,percent_change_7d -api_key = your_coinmarketcap_api_key - -[btc] -icon =  -in_tooltip = false -price_precision = 2 -change_precision = 2 -volume_precision = 2 - -[eth] -icon =  -in_tooltip = true -price_precision = 2 -change_precision = 2 -volume_precision = 2 -``` - -- **currency:** Any valid currency code should be accepted +- **currency:** A valid currency code - **currency_symbol:** A corresponding symbol of the currency you wish to display -- **spacer_symbol:** A string/character to use as a spacer between tickers. Comment out to disable. -- **api_key:** CoinmarketCap API key obtained from their [API Dashboard](https://coinmarketcap.com/api). +- **spacer_symbol:** A string/character to use as a spacer between tickers. Comment out to disable - **display:** A list of metrics you wish to display for each crypto currency. No spaces. Valid options are: - - **price:** Displays the current price. - - **percent_change_1h:** Price change over the past hour. - - **percent_change_24h:** Price change over the past 24 hours. - - **percent_change_7d:** Price change over the past week. - - **percent_change_30d:** Price change over the past thirty days. - - **percent_change_60d:** Price change over the past sixty days. - - **percent_change_90d:** Price change over the past ninety days. - - **volume_24h:** Market volume in your chosen currency, over the past 24 hours. - - **volume_change_24h:** Market volume change in your chosen currency, over the past 24 hours. - -*Alternatively, the CoinMarketCap API key can be set through the environment variable `COINMARKETCAP_API_KEY`, if you do not wish to save it to the `config.ini` configuration file.* + - **price:** current price + - **percent_change_1h:** Price change over the past hour + - **percent_change_24h:** Price change over the past 24 hours + - **percent_change_7d:** Price change over the past week + - **percent_change_30d:** Price change over the past thirty days + - **percent_change_60d:** Price change over the past sixty days + - **percent_change_90d:** Price change over the past ninety days + - **volume_24h:** Market volume in your chosen currency, over the past 24 hours + - **volume_change_24h:** Market volume change in your chosen currency, over the past 24 hours +- **api_key:** CoinmarketCap API key obtained from their [API Dashboard](https://coinmarketcap.com/api) + + *Alternatively, the CoinMarketCap API key can be set through the environment variable `COINMARKETCAP_API_KEY`, if you do not wish to save it to the `config.ini` configuration file.* ### Adding Cryptocurrencies @@ -114,8 +98,8 @@ For each cryptocurrency you wish to display, add a section as shown in the examp Valid options: -- **icon:** A character symbol to display next to this cryptocurrency's metrics. -- **in_tooltip:** Whether to display the data in the tooltip instead of the bar (defaults to false). -- **price_precision** The decimal precision at which to display the price value of the cryptocurrency. -- **change_precision** The decimal precision at which to display the change value(s) of the cryptocurrency. -- **volume_precision** The decimal precision at which to display the volume value of the cryptocurrency. +- **icon:** A character symbol to display next to this cryptocurrency's metrics +- **in_tooltip:** Whether to display the data in the tooltip instead of the bar (defaults to false) +- **price_precision** The decimal precision at which to display the price value of the cryptocurrency +- **change_precision** The decimal precision at which to display the change value(s) of the cryptocurrency +- **volume_precision** The decimal precision at which to display the volume value of the cryptocurrency diff --git a/config.ini.example b/config.ini.example index 4384867..4759556 100644 --- a/config.ini.example +++ b/config.ini.example @@ -4,6 +4,7 @@ currency_symbol = € display = price,percent_change_24h spacer_symbol = | api_key = your_coinmarketcap_api_key +; COINMARKETCAP_API_KEY env variable can alternatively be used and will take precedence [btc] icon =  diff --git a/pyproject.toml b/pyproject.toml index 17c1ba9..eb32080 100644 --- a/pyproject.toml +++ b/pyproject.toml @@ -1,6 +1,6 @@ [tool.poetry] name = "waybar-crypto" -version = "v1.4.0" +version = "v1.5.0" description = "A Waybar module for displaying cryptocurrency market information from CoinMarketCap." authors = ["Ross "] license = "MIT" diff --git a/tests/test_waybar_crypto.py b/tests/test_waybar_crypto.py index 4e7fa9b..f3bae10 100644 --- a/tests/test_waybar_crypto.py +++ b/tests/test_waybar_crypto.py @@ -1,12 +1,22 @@ import os +import argparse import pytest +from unittest import mock -from waybar_crypto import CLASS_NAME, ResponseQuotesLatest, WaybarCrypto +from waybar_crypto import ( + CLASS_NAME, + DEFAULT_XDG_CONFIG_HOME_PATH, + XDG_CONFIG_HOME_ENV, + ResponseQuotesLatest, + WaybarCrypto, + parse_args, +) # Get the absolute path of this script ABS_DIR = os.path.dirname(os.path.abspath(__file__)) CONFIG_PATH = f"{ABS_DIR}/../config.ini.example" +TEST_CONFIG_PATH = "/test_path" @pytest.fixture() @@ -87,6 +97,34 @@ def quotes_latest(): } +def test_parse_args_default_path(): + with mock.patch("sys.argv", ["waybar_crypto.py"]): + os.environ[XDG_CONFIG_HOME_ENV] = "" + args = parse_args() + assert "config_path" in args + assert os.path.expanduser(DEFAULT_XDG_CONFIG_HOME_PATH) in os.path.expanduser( + args["config_path"] + ) + + +def test_parse_args_custom_xdg_data_home(): + with mock.patch("sys.argv", ["waybar_crypto.py"]): + os.environ[XDG_CONFIG_HOME_ENV] = TEST_CONFIG_PATH + args = parse_args() + assert "config_path" in args + assert TEST_CONFIG_PATH in args["config_path"] + + +@mock.patch( + "argparse.ArgumentParser.parse_args", + return_value=argparse.Namespace(config_path=TEST_CONFIG_PATH), +) +def test_parse_args_custom_path(mock: mock.MagicMock): + args = parse_args() + assert "config_path" in args + assert args["config_path"] == TEST_CONFIG_PATH + + class TestWaybarCrypto: """Tests for the WaybarCrypto.""" diff --git a/waybar_crypto.py b/waybar_crypto.py index 7973839..e75d0e2 100755 --- a/waybar_crypto.py +++ b/waybar_crypto.py @@ -6,9 +6,14 @@ import requests import json import configparser +import argparse API_URL = "https://pro-api.coinmarketcap.com/v1/cryptocurrency/quotes/latest" API_KEY_ENV = "COINMARKETCAP_API_KEY" + +XDG_CONFIG_HOME_ENV = "XDG_CONFIG_HOME" +DEFAULT_XDG_CONFIG_HOME_PATH = "~/.config" +CONFIG_DIR = "waybar-crypto" CONFIG_FILE = "config.ini" DEFAULT_PRECISION = 2 @@ -43,6 +48,10 @@ TIMEOUT_SECONDS = 10 +class Args(TypedDict): + config_path: str + + class ConfigGeneral(TypedDict): currency: str currency_symbol: str @@ -313,12 +322,35 @@ def waybar_output(self, quotes_latest: ResponseQuotesLatest) -> WaybarOutput: return output_obj +def parse_args() -> Args: + parser = argparse.ArgumentParser() + + # Utilise XDG_CONFIG_HOME if it exists + xdg_config_home_path = os.getenv(XDG_CONFIG_HOME_ENV) + if not xdg_config_home_path: + xdg_config_home_path = DEFAULT_XDG_CONFIG_HOME_PATH + + default_config_path = os.path.join(xdg_config_home_path, CONFIG_DIR, CONFIG_FILE) + parser.add_argument( + "-c", + "--config-path", + type=str, + default=default_config_path, + help=f"Path to the configuration file (default: '{default_config_path}')", + ) + args = parser.parse_args() + + return {"config_path": args.config_path} + + def main(): - # Get the absolute path of this script - abs_dir = os.path.dirname(os.path.abspath(__file__)) - config_path = f"{abs_dir}/{CONFIG_FILE}" + args = parse_args() + + config_path = args["config_path"] + if not os.path.isfile(config_path): + raise WaybarCryptoException(f"configuration file not found at '{config_path}'") - waybar_crypto = WaybarCrypto(config_path) + waybar_crypto = WaybarCrypto(args["config_path"]) quotes_latest = waybar_crypto.coinmarketcap_latest() output = waybar_crypto.waybar_output(quotes_latest)