Compare commits
4 Commits
developing
...
master
| Author | SHA1 | Date |
|---|---|---|
|
|
03748cd8e7 | |
|
|
462d25a663 | |
|
|
e1a239ef23 | |
|
|
441c2beda0 |
42
README.md
42
README.md
|
|
@ -1 +1,43 @@
|
|||
# METAR NAVIGATE
|
||||
A Simple METAR Application that feeds from CheckWX API
|
||||
|
||||
It has two modes of display, a simple CLI using Rich and a TUI using Textual
|
||||
|
||||
## Usage
|
||||
First, you need to install the application by issuing:
|
||||
```bash
|
||||
pip install -e .
|
||||
```
|
||||
this will install the application and the dependencies
|
||||
after that you'll need to configure the application by issuing:
|
||||
```bash
|
||||
metarNavigate --no-tui -c
|
||||
```
|
||||
or
|
||||
```bash
|
||||
metarNavigate --no-tui --configure
|
||||
```
|
||||
|
||||
you'll need a checkWX API key to use the application which is free
|
||||
|
||||
after the configuration process you can use the application by issuing:
|
||||
|
||||
```bash
|
||||
metarNavigate --no-tui <ICAO>
|
||||
```
|
||||
this will run the application in CLI mode and display the METAR for the ICAO code issued as argument.
|
||||
|
||||
If you wish to see a detailed breakdown of the METAR in CLI mode, you can issue:
|
||||
|
||||
```bash
|
||||
metarNavigate --no-tui --detailed <ICAO>
|
||||
```
|
||||
or
|
||||
```bash
|
||||
metarNavigate --no-tui -d <ICAO>
|
||||
```
|
||||
this will print all details of the METAR Message with an explanation of each part of the message.
|
||||
|
||||
>[!NOTE]
|
||||
>
|
||||
>The flags ``` -d ``` or the flag ```--detailed ``` can you be used with the flag ```--no-tui ```
|
||||
|
|
@ -17,11 +17,6 @@ classifiers = [
|
|||
"Operating System :: OS Independent",
|
||||
]
|
||||
dependencies = [
|
||||
"tomli",
|
||||
"tomli_w",
|
||||
"rich",
|
||||
"requests"
|
||||
|
||||
|
||||
]
|
||||
|
||||
|
|
|
|||
|
|
@ -1,5 +0,0 @@
|
|||
from metar_navigate.application import Application
|
||||
from metar_navigate.command import main
|
||||
|
||||
|
||||
__all__ = ["Application", "main"]
|
||||
|
|
@ -1,79 +0,0 @@
|
|||
import argparse
|
||||
import sys
|
||||
|
||||
from metar_navigate.services import METARService
|
||||
from metar_navigate.ui import RichInterface
|
||||
from metar_navigate.utils import ConfigManager
|
||||
|
||||
class Application:
|
||||
def __init__(self):
|
||||
self.parser = None
|
||||
self.args = None
|
||||
self.config = ConfigManager()
|
||||
self.METARService = METARService(self)
|
||||
self.ui = None
|
||||
self.is_configured = False
|
||||
|
||||
def setup(self) -> None:
|
||||
|
||||
if self.config.read_config() is not None:
|
||||
self.is_configured = True
|
||||
|
||||
self._validate_args()
|
||||
|
||||
if self.args.no_tui:
|
||||
self.ui = RichInterface(self)
|
||||
if self.args.configure:
|
||||
self.ui.setup(configure=True)
|
||||
if self.args.detailed:
|
||||
self.ui.setup(detailed=True)
|
||||
|
||||
|
||||
|
||||
|
||||
def _validate_args(self):
|
||||
self.parser = argparse.ArgumentParser()
|
||||
self.parser.add_argument("--no-tui", action="store_true", help="disables the TUI")
|
||||
self.parser.add_argument("--configure", "-c" ,action="store_true", help="runs the configuration wizard")
|
||||
self.parser.add_argument("--detailed", action="store_true", help="displays more detailed METAR Breakdown "
|
||||
"(Only available with --no-tui flag)")
|
||||
self.parser.add_argument("-t", "--type", choices=["TAF", "METAR"], default="METAR", help="Select the Type "
|
||||
"of message (Only Available with"
|
||||
" --no-tui flag)")
|
||||
self.parser.add_argument("ICAO", nargs='?', type=str.upper,help="ICAO code of airport (required with --no-tui flag)")
|
||||
|
||||
self.args = self.parser.parse_args()
|
||||
|
||||
|
||||
|
||||
|
||||
if self.args.configure and self.args.ICAO:
|
||||
self.parser.error("--configure não aceita código ICAO")
|
||||
|
||||
|
||||
if self.args.configure and self.args.detailed:
|
||||
self.parser.error("--configure não pode ser usado com --detailed")
|
||||
|
||||
|
||||
if self.args.configure and ('--type' in sys.argv or '-t' in sys.argv):
|
||||
self.parser.error("--configure não pode ser usado com --type/-t")
|
||||
|
||||
|
||||
if self.args.no_tui and not self.args.configure and not self.args.ICAO:
|
||||
self.parser.error("--no-tui requer código ICAO (exceto quando usado com --configure)")
|
||||
|
||||
|
||||
if self.args.ICAO and not self.args.no_tui:
|
||||
self.parser.error("Código ICAO só pode ser usado com --no-tui")
|
||||
|
||||
|
||||
if self.args.detailed and not self.args.no_tui:
|
||||
self.parser.error("--detailed requer --no-tui")
|
||||
|
||||
|
||||
if not self.args.no_tui and ('--type' in sys.argv or '-t' in sys.argv):
|
||||
self.parser.error("--type/-t requer --no-tui")
|
||||
|
||||
|
||||
def run(self):
|
||||
self.ui.run()
|
||||
|
|
@ -1,6 +1,2 @@
|
|||
from metar_navigate import Application
|
||||
|
||||
def main():
|
||||
app = Application()
|
||||
app.setup()
|
||||
app.run()
|
||||
print("Hello World")
|
||||
|
|
@ -1,20 +0,0 @@
|
|||
import requests
|
||||
import json
|
||||
|
||||
class METARService:
|
||||
def __init__(self,parent):
|
||||
self.__parent = parent
|
||||
self.url = "https://api.checkwx.com/metar/"
|
||||
def request_metar(self,ICAO_code):
|
||||
url = self.url + ICAO_code
|
||||
header = {
|
||||
'X-API-Key':self.__parent.config.get_data("checkwx_api_key").get("checkwx_api_key")
|
||||
}
|
||||
if self.__parent.args.detailed:
|
||||
url = url + "/decoded"
|
||||
response = requests.request('GET', url,headers=header)
|
||||
return response
|
||||
def get_metar(self,ICAO_code):
|
||||
request = self.request_metar(ICAO_code)
|
||||
|
||||
return request
|
||||
|
|
@ -1,3 +0,0 @@
|
|||
from .METARSerice import METARService
|
||||
|
||||
__all__ = ["METARService"]
|
||||
|
|
@ -1,3 +0,0 @@
|
|||
from .ui import RichInterface
|
||||
|
||||
__all__ = ["RichInterface",]
|
||||
|
|
@ -1,157 +0,0 @@
|
|||
from abc import ABC, abstractmethod
|
||||
|
||||
from rich import console
|
||||
|
||||
|
||||
class UIInterface(ABC):
|
||||
|
||||
@abstractmethod
|
||||
def setup(self):
|
||||
pass
|
||||
@abstractmethod
|
||||
def run(self):
|
||||
pass
|
||||
|
||||
|
||||
class RichInterface(UIInterface):
|
||||
def __init__(self,parent):
|
||||
super().__init__()
|
||||
self.__parent = parent
|
||||
self.console = console.Console()
|
||||
self.console.show_cursor(False)
|
||||
self.mode = None
|
||||
|
||||
def setup(self,**kwargs):
|
||||
for key, value in kwargs.items():
|
||||
match(key):
|
||||
case "configure":
|
||||
if value:
|
||||
self.mode = "configure"
|
||||
else:
|
||||
self.mode = "run"
|
||||
case "detailed":
|
||||
self.mode = "detailed"
|
||||
case _:
|
||||
raise ValueError(f"Invalid mode: {key}")
|
||||
def configure_run_mode(self):
|
||||
self.console.print("This Application is in [bold red]Configure Mode[/bold red]")
|
||||
self.console.print("This Application requires a CheckWX API Key to run")
|
||||
print("Please enter your CheckWX API:",end='')
|
||||
api_key = input()
|
||||
print(f"\033[A\rPlease enter your CheckWX API:{' ' * len(api_key)}\rPlease enter your CheckWX API:",end='')
|
||||
while api_key == '':
|
||||
self.console.print("[bold red]Invalid API Key: Can't be empty [/bold red]")
|
||||
print("Please enter your CheckWX API:", end='')
|
||||
api_key = input()
|
||||
print(f"\033[A\rPlease enter your CheckWX API:{' ' * len(api_key)}\rPlease enter your CheckWX API:", end='')
|
||||
print("\n")
|
||||
always_show_detailed = self.console.input("Always Show Detailed (default is: [green]No[/green]) [[u]y[/u]/[u]N[/u]]: ").upper()
|
||||
if always_show_detailed == '':
|
||||
always_show_detailed = 'N'
|
||||
while always_show_detailed not in ['Y', 'N']:
|
||||
always_show_detailed = self.console.input("Always Show Detailed (default is: [green]No[/green]) [[u]y[/u]/[u]N[/u]]: ").upper()
|
||||
if always_show_detailed == '':
|
||||
always_show_detailed = 'N'
|
||||
|
||||
wind_speed_unit = self.console.input(
|
||||
'Speed Unit (default is: [green]K[/green]) [[u]K[/u]eep/[u]M[/u]iles/K[u]N[/u]ots/KM/[u]H[/u]]: '
|
||||
).upper()
|
||||
|
||||
if wind_speed_unit == '':
|
||||
wind_speed_unit = 'K'
|
||||
while wind_speed_unit not in ['K', 'M', 'N', 'H']:
|
||||
wind_speed_unit = self.console.input(
|
||||
'Speed Unit (default is: [green]K[/green]) [[u]K[/u]eep/[u]M[/u]iles/K[u]N[/u]ots/KM/[u]H[/u]]: '
|
||||
).upper()
|
||||
if wind_speed_unit == '':
|
||||
wind_speed_unit = 'K'
|
||||
|
||||
temp_unit = self.console.input(
|
||||
'Temperature Unit (default is: [green]K[/green]) [[u]K[/u]eep/[u]C[/u]elsius/[u]F[/u]ahrenheit]: '
|
||||
).upper()
|
||||
if temp_unit == '':
|
||||
temp_unit = 'K'
|
||||
while temp_unit not in ['K', 'C', 'F']:
|
||||
temp_unit = self.console.input(
|
||||
'Temperature Unit (default is: [green]K[/green]) [[u]K[/u]eep/[u]C[/u]elsius/[u]F[/u]ahrenheit]: '
|
||||
).upper()
|
||||
if temp_unit == '':
|
||||
temp_unit = 'K'
|
||||
|
||||
visibility_unit = self.console.input(
|
||||
'Visibility Unit (default is: [green]K[/green]) [[u]K[/u]eep/[u]S[/u]M/[u]M[/u]]: '
|
||||
).upper()
|
||||
if visibility_unit == '':
|
||||
visibility_unit = 'K'
|
||||
while visibility_unit not in ['K', 'S', 'M']:
|
||||
visibility_unit = self.console.input(
|
||||
'Visibility Unit (default is: [green]K[/green]) [[u]K[/u]eep/[u]S[/u]M/[u]M[/u]]: '
|
||||
).upper()
|
||||
if visibility_unit == '':
|
||||
visibility_unit = 'K'
|
||||
|
||||
pressure_unit = self.console.input(
|
||||
'Pressure Unit (default is: [green]K[/green]) [[u]K[/u]eep/[u]I[/u]nHg/[u]H[/u]Pa]: '
|
||||
).upper()
|
||||
if pressure_unit == '':
|
||||
pressure_unit = 'K'
|
||||
while pressure_unit not in ['K', 'H', 'U']:
|
||||
pressure_unit = self.console.input(
|
||||
'Pressure Unit (default is: [green]K[/green]) [[u]K[/u]eep/[u]I[/u]nHg/[u]H[/u]Pa]: '
|
||||
).upper()
|
||||
if pressure_unit == '':
|
||||
pressure_unit = 'K'
|
||||
|
||||
self.__parent.config.set_data_dict({ "checkwx_api_key": api_key})
|
||||
|
||||
if always_show_detailed == 'Y':
|
||||
self.__parent.config.set_data_dict({"always_show_detailed":True})
|
||||
else:
|
||||
self.__parent.config.set_data_dict({"always_show_detailed": False})
|
||||
|
||||
if wind_speed_unit == 'K':
|
||||
self.__parent.config.set_data_dict({"wind_speed_unit":"Keep"})
|
||||
elif wind_speed_unit == 'M':
|
||||
self.__parent.config.set_data_dict({"wind_speed_unit":"Miles"})
|
||||
elif wind_speed_unit == 'N':
|
||||
self.__parent.config.set_data_dict({"wind_speed_unit": "Knots"})
|
||||
elif wind_speed_unit == 'H':
|
||||
self.__parent.config.set_data_dict({"wind_speed_unit": "KM/H"})
|
||||
|
||||
if temp_unit == 'K':
|
||||
self.__parent.config.set_data_dict({"temp_unit":"Keep"})
|
||||
elif temp_unit == 'C':
|
||||
self.__parent.config.set_data_dict({ "temp_unit": "Celsius"})
|
||||
elif temp_unit == 'F':
|
||||
self.__parent.config.set_data_dict({ "temp_unit": "Fahrenheit"})
|
||||
|
||||
if visibility_unit == 'K':
|
||||
self.__parent.config.set_data_dict({ "visibility_unit": "Keep"})
|
||||
elif visibility_unit == 'S':
|
||||
self.__parent.config.set_data_dict({"visibility_unit": "SM"})
|
||||
elif visibility_unit == 'M':
|
||||
self.__parent.config.set_data_dict({"visibility_unit": "Miles"})
|
||||
|
||||
if pressure_unit == 'K':
|
||||
self.__parent.config.set_data_dict({"pressure_unit": "Keep"})
|
||||
elif pressure_unit == 'H':
|
||||
self.__parent.config.set_data_dict({ "pressure_unit": "HPa"})
|
||||
elif pressure_unit == 'I':
|
||||
self.__parent.config.set_data_dict({ "pressure_unit": "InHg"})
|
||||
|
||||
self.__parent.config.write_config()
|
||||
|
||||
def run(self):
|
||||
if not self.__parent.is_configured and self.mode != "configure":
|
||||
self.console.print("[bold red]This Application Requires a configuration to run please run with -c flag to configure [/bold red]")
|
||||
self.console.show_cursor(True)
|
||||
exit(69)
|
||||
|
||||
|
||||
|
||||
if self.mode == "configure":
|
||||
self.configure_run_mode()
|
||||
else:
|
||||
self.console.print("This Application is in [bold red]Run Mode[/bold red]")
|
||||
|
||||
self.console.show_cursor(True)
|
||||
|
|
@ -1,4 +0,0 @@
|
|||
from .directory_manager import DirectoryManager
|
||||
from .config_manager import ConfigManager
|
||||
|
||||
__all__ = ["DirectoryManager", "ConfigManager"]
|
||||
|
|
@ -1,71 +0,0 @@
|
|||
import os.path
|
||||
|
||||
import tomli
|
||||
import tomli_w
|
||||
|
||||
from metar_navigate.utils import DirectoryManager
|
||||
|
||||
from threading import Lock
|
||||
|
||||
class SingletonMeta(type):
|
||||
_instances = {}
|
||||
_lock: Lock = Lock()
|
||||
|
||||
def __call__(cls, *args, **kwargs):
|
||||
with cls._lock:
|
||||
if cls not in cls._instances:
|
||||
instance = super().__call__(*args, **kwargs)
|
||||
cls._instances[cls] = instance
|
||||
return cls._instances[cls]
|
||||
|
||||
|
||||
class ConfigManager(metaclass=SingletonMeta):
|
||||
def __init__(self):
|
||||
self.config_dir = DirectoryManager.get_config_directory()
|
||||
self.__data = dict()
|
||||
|
||||
def read_config(self):
|
||||
if os.path.exists(f"{self.config_dir}/config.toml"):
|
||||
try:
|
||||
with open(f"{self.config_dir}/config.toml", "rb") as f:
|
||||
data = tomli.load(f)
|
||||
except tomli.TOMLDecodeError as e:
|
||||
raise ValueError(f"Invalid TOML file; {e}")
|
||||
except Exception as e:
|
||||
raise ValueError(f"Error reading config file; {e}")
|
||||
|
||||
self.__data = data
|
||||
return self.__data
|
||||
|
||||
else:
|
||||
return None
|
||||
def set_data_dict(self,data):
|
||||
self.__data.update(data)
|
||||
|
||||
|
||||
def write_config(self):
|
||||
default = {
|
||||
"checkwx_api_key": "",
|
||||
"always_show_detailed": False,
|
||||
"wind_speed_unit": "Keep",
|
||||
"temp_unit": "Keep",
|
||||
"visibility_unit": "Keep",
|
||||
"pressure_unit": "Keep"
|
||||
|
||||
}
|
||||
|
||||
for key, default_value in default.items():
|
||||
current_value = self.__data.get(key)
|
||||
if current_value is None or current_value == "":
|
||||
self.__data[key] = default_value
|
||||
else:
|
||||
self.__data[key] = current_value
|
||||
with open(f"{self.config_dir}/config.toml", "wb") as f:
|
||||
tomli_w.dump(self.__data, f)
|
||||
|
||||
def get_data(self,key):
|
||||
if key in self.__data.keys():
|
||||
return {key:self.__data[key]}
|
||||
else:
|
||||
return None
|
||||
|
||||
|
|
@ -1,17 +0,0 @@
|
|||
import os
|
||||
from pathlib import Path
|
||||
|
||||
|
||||
class DirectoryManager:
|
||||
|
||||
@staticmethod
|
||||
def get_config_directory()-> Path:
|
||||
home = Path.home()
|
||||
config_dir = home / ".METARNavigate"
|
||||
config_dir.mkdir(exist_ok=True)
|
||||
os.chmod(config_dir,0o744)
|
||||
|
||||
return config_dir
|
||||
|
||||
|
||||
|
||||
Loading…
Reference in New Issue