Compare commits
4 Commits
developing
...
master
| Author | SHA1 | Date |
|---|---|---|
|
|
03748cd8e7 | |
|
|
462d25a663 | |
|
|
e1a239ef23 | |
|
|
441c2beda0 |
42
README.md
42
README.md
|
|
@ -1 +1,43 @@
|
||||||
# METAR NAVIGATE
|
# 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",
|
"Operating System :: OS Independent",
|
||||||
]
|
]
|
||||||
dependencies = [
|
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():
|
def main():
|
||||||
app = Application()
|
print("Hello World")
|
||||||
app.setup()
|
|
||||||
app.run()
|
|
||||||
|
|
@ -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