This commit is contained in:
Mark Joshwel 2023-06-16 08:08:23 +00:00 committed by GitHub
commit e61960dcfb
No known key found for this signature in database
GPG key ID: 4AEE18F83AFDEB23
5 changed files with 271 additions and 117 deletions

31
.github/workflows/check-quality.yml vendored Normal file
View file

@ -0,0 +1,31 @@
name: qc (black/mypy/isort checks)
on:
workflow_dispatch:
push:
jobs:
analyse:
runs-on: ubuntu-latest
steps:
- name: checkout
uses: actions/checkout@v3
- name: install devbox
uses: jetpack-io/devbox-install-action@v0.3.0
- name: install dependencies
run: devbox run poetry install
- name: install dependencies
id: build
run: devbox run poetry build
- name: analyse with mypy
run: devbox run poetry run mypy surplus.py
- name: check for black formatting compliance
run: devbox run poetry run "black --check surplus.py"
- name: analyse isort compliance
run: devbox run poetry run "isort --check surplus.py"

View file

@ -1,6 +1,9 @@
name: release (slsa 3)
name: release with slsa 3 compliance
on:
push:
branches:
main
tags:
- '*'

View file

@ -8,6 +8,7 @@ Plus Code to iOS-Shortcuts-like shareable text
- [API Reference](#api-reference)
- [surplus.surplus()](#surplussurplus)
- [surplus.parse_query()](#surplusparse_query)
- [surplus.handle_query()](#surplushandle_query)
- [surplus.Localcode](#surpluslocalcode)
- [surplus.Latlong](#surpluslatlong)
- [Developing](#developing)
@ -16,11 +17,12 @@ Plus Code to iOS-Shortcuts-like shareable text
```text
$ surplus 9R3J+R9 Singapore
surplus version 1.1.0
surplus version 1.1.1
Thomson Plaza
301 Upper Thomson Road, Bishan
Sin Ming, Bishan
574408
Singapore
Central, Singapore
```
```python
@ -28,7 +30,7 @@ Singapore
>>> Localcode(code="8RPQ+JW", locality="Singapore").full_length()
(True, '6PH58RPQ+JW')
>>> surplus("6PH58RPQ+JW")
(True, 'Caldecott Stn Exit 4\nToa Payoh Link\n298106\nSingapore')
(True, 'Caldecott Stn Exit 4\nToa Payoh Link\n298106\nCentral, Singapore')
```
## Installing
@ -44,16 +46,19 @@ pip install git+https://github.com/markjoshwel/surplus
### Command-line Interface
```text
usage: surplus [-h] [-d] query
usage: surplus [-h] [-d] query [query ...]
Plus Code to iOS-Shortcuts-like shareable text
positional arguments:
query full-length Plus Code (6PH58QMF+FX), local codes (8QMF+FX Singapore), or latlong (1.3336875, 103.7749375)
query full-length Plus Code (6PH58QMF+FX),
local code (8QMF+FX Singapore), or
latlong (1.3336875, 103.7749375)
options:
-h, --help show this help message and exit
-d, --debug prints lat, long and reverser response dict to stderr
-d, --debug prints lat, long and reverser response
dict to stderr
```
### API Reference
@ -76,9 +81,12 @@ pluscode to shareable text conversion function
- arguments
- `query: str | surplus.Localcode | surplus.Latlong`
str - normal longcode (6PH58QMF+FX)
surplus.Localcode - shortcode with locality (8QMF+FX Singapore)
surplus.Latlong - latlong
- str
normal longcode (6PH58QMF+FX)
- [`surplus.Localcode`](#surpluslocalcode)
shortcode with locality (8QMF+FX Singapore)
- [`surplus.Latlong`](#surpluslatlong)
latlong
- `reverser: typing.Callable = geopy.geocoders.Nominatim(user_agent="surplus").reverser`
latlong to location function, accesses a dict from .raw attribute of return object
@ -97,22 +105,22 @@ pluscode to shareable text conversion function
- returns `tuple[bool, str]`
- `(True, <str>)`
conversion was successful, str is resultant text
conversion succeeded, second element is the resultant string
- `(False, <str>)`
conversion failed, str is error message
conversion failed, second element is an error message string
---
#### `surplus.parse_query()`
function that parses a string Plus Code, local code or latlong into a str, surplus.Localcode or surplus.Latlong respectively
function that parses a string Plus Code, local code or latlong into a str, [`surplus.Localcode`](#surpluslocalcode) or [`surplus.Latlong`](#surpluslatlong) respectively
- signature:
```python
def parse_query(
query: str, debug: bool = False
) -> tuple[bool, str | Localcode | Latlong]:
) -> tuple[Literal[True], str | Localcode | Latlong] | tuple[Literal[False], str]:
```
- arguments:
@ -120,12 +128,43 @@ function that parses a string Plus Code, local code or latlong into a str, surpl
- `query: str`
string Plus Code, local code or latlong
- returns `tuple[bool, str | Localcode | Latlong]`
- returns `tuple[Literal[True], str | Localcode | Latlong] | tuple[Literal[False], str]`
- `(True, <str | Localcode | Latlong>)`
conversion was successful, second element is result
- `(True, <str | surplus.Localcode | surplus.Latlong>)`
conversion succeeded, second element is resultant Plus code string, [`surplus.Localcode`](#surpluslocalcode) or [`surplus.Latlong`](#surpluslatlong)
- `(False, <str>)`
conversion failed, str is error message
conversion failed, second element is an error message string
---
#### `surplus.handle_query()`
function that gets returns a [surplus.Latlong](#surpluslatlong) from a Plus Code string, [`surplus.Localcode`](#surpluslocalcode) or [`surplus.Latlong`](#surpluslatlong) object.
used after [`surplus.parse_query()`](#surplusparse_query).
- signature:
```python
def handle_query(
query: str | Localcode | Latlong, debug: bool = False
) -> tuple[Literal[True], Latlong] | tuple[Literal[False], str]:
```
- arguments:
- `query: str | Localcode | Latlong`
- str
normal longcode (6PH58QMF+FX)
- [`surplus.Localcode`](#surpluslocalcode)
shortcode with locality (8QMF+FX Singapore)
- [`surplus.Latlong`](#surpluslatlong)
latlong
- returns `tuple[Literal[True], Latlong] | tuple[Literal[False], str]`
- `(True, <surplus.Latlong>)`
conversion succeeded, second element is a [`surplus.Latlong`](#surpluslatlong)
- `(False, <str>)`
conversion failed, second element is an error message string
---
@ -136,9 +175,9 @@ function that parses a string Plus Code, local code or latlong into a str, surpl
- parameters:
- `code: str`
Plus Code - e.g.: "8QMF+FX"
Plus Code - e.g.: `"8QMF+FX"`
- `locality: str`
e.g.: "Singapore"
e.g.: `"Singapore"`
---
@ -162,9 +201,9 @@ method that calculates full-length Plus Code using locality
- returns:
- `(True, <str>)`
conversion was successful, str is resultant Plus Code
conversion succeeded, second element is the resultant Plus Code string
- `(False, <str>)`
conversion failed, str is error message
conversion failed, second element is an error message string
---

View file

@ -1,6 +1,6 @@
[tool.poetry]
name = "surplus"
version = "1.1.0"
version = "1.1.1"
description = "Plus Code to iOS-Shortcuts-like shareable text"
authors = ["Mark Joshwel <mark@joshwel.co>"]
license = "Unlicence"

View file

@ -30,8 +30,9 @@ For more information, please refer to <http://unlicense.org/>
"""
from argparse import ArgumentParser
from collections import OrderedDict
from sys import stderr
from typing import Any, Callable, Final, NamedTuple
from typing import Any, Callable, Final, Literal, NamedTuple
from geopy import Location # type: ignore
from geopy.geocoders import Nominatim # type: ignore
@ -39,7 +40,7 @@ from pluscodes import PlusCode # type: ignore
from pluscodes.openlocationcode import recoverNearest # type: ignore
from pluscodes.validator import Validator # type: ignore
VERSION: Final[tuple[int, int, int]] = (1, 1, 0)
VERSION: Final[tuple[int, int, int]] = (1, 1, 1)
class Localcode(NamedTuple):
@ -130,49 +131,24 @@ def surplus(
(True, <str>) - conversion was successful, str is resultant text
(False, <str>) - conversion failed, str is error message
"""
lat: float = 0.0
lon: float = 0.0
if isinstance(query, Latlong):
lat, lon = query.lat, query.long
def _unique(l: list[str]) -> list[str]:
unique: OrderedDict = OrderedDict()
for line in l:
unique.update({line: None})
return list(unique.keys())
else: # instances: str | Localcode
str_pcode: str = ""
_latlong = handle_query(query=query, debug=debug)
if isinstance(query, Localcode):
result = query.full_length()
if _latlong[0] is False:
assert isinstance(_latlong[1], str)
return False, _latlong[1]
if not result[0]:
return False, result[1]
str_pcode = result[1]
else:
str_pcode = query
assert isinstance(_latlong[1], Latlong)
latlong = _latlong[1]
try:
pcode = PlusCode(str_pcode)
except KeyError:
return (
False,
"enter full-length Plus Code including area code, e.g.: 6PH58QMF+FX",
)
except Exception as pcderr:
return (
False,
f"error while decoding Plus Code: {pcderr.__class__.__name__} - {pcderr}",
)
lat = pcode.area.center().lat
lon = pcode.area.center().lon
if debug:
stderr.write(f"debug: {lat=}, {lon=}\n")
try:
_reversed: Location | None = reverser(f"{lat}, {lon}")
_reversed: Location | None = reverser(f"{latlong.lat}, {latlong.long}")
if _reversed is None:
raise Exception(f"reverser function returned None")
@ -182,28 +158,32 @@ def surplus(
except Exception as reverr:
return (
False,
f"error while reversing latlong ({lat},{lon}): {reverr.__class__.__name__} - {reverr}",
f"error while reversing latlong ({Latlong}): {reverr.__class__.__name__} - {reverr}",
)
if debug:
stderr.write(f"debug: {location=}\n")
data: list[str] = [
(
",".join(
text: list[str] = _unique(
[
location["address"].get(detail, "")
(
", ".join(
[
d
for d in _unique(
[
location["address"].get(detail, None)
for detail in (
"emergency, historic, military, natural, landuse, place, railway,"
"man_made, aerialway, boundary, amenity, aeroway, club, craft,"
"emergency, historic, military, natural, landuse, place, railway, "
"man_made, aerialway, boundary, amenity, aeroway, club, craft, "
"leisure, office, mountain_pass, shop, tourism, bridge, tunnel, waterway"
).split(", ")
]
)
).strip(","),
# location["address"].get("leisure"),
# location["address"].get("shop"),
# location["address"].get("railway"),
if d is not None
]
)
).strip(", "),
(
location["address"].get("building")
if (
@ -218,35 +198,73 @@ def surplus(
+ (" " + location["address"].get("house_name", "")).strip()
+ " "
+ location["address"].get("road", "")
+ (
", " + location["address"].get("suburb", "")
# dont repeat if suburb is mentioned in the road itself
# 'Toa Payoh' in 'Lorong 1A Toa Payoh'
if location["address"].get("suburb", "")
not in location["address"].get("road", "")
else ""
)
# + (
# ", " + location["address"].get("suburb", "")
# # dont repeat if suburb is mentioned in the road itself
# # 'Toa Payoh' in 'Lorong 1A Toa Payoh'
# if location["address"].get("suburb", "")
# not in location["address"].get("road", "")
# else None
# )
).strip(),
(
",".join(
", ".join(
[
d
for d in _unique(
[
location["address"].get(detail, "")
for detail in (
"residential, neighbourhood, allotments, quarter"
"residential, neighbourhood, allotments, quarter, "
"city_district, district, borough, suburb, subdivision, "
"municipality, city, town, village"
).split(", ")
]
)
if all(
[
d != "",
d not in location["address"].get("road", ""),
d
not in [
location["address"].get(detail, "")
for detail in (
"region, state, state_district, county, "
"state, country, continent"
).split(", ")
],
]
)
]
)
).strip(","),
location["address"].get("postcode"),
location["address"].get("country"),
(
", ".join(
[
d
for d in _unique(
[
location["address"].get(detail, None)
for detail in (
"region, state, state_district, county, "
"state, country, continent"
).split(", ")
]
)
if d is not None
]
)
),
]
)
return True, "\n".join([d for d in data if ((d != None) and d != "")])
return True, "\n".join([d for d in text if ((d != None) and d != "")])
def parse_query(
query: str, debug: bool = False
) -> tuple[bool, str | Localcode | Latlong]:
) -> tuple[Literal[True], str | Localcode | Latlong] | tuple[Literal[False], str]:
"""
function that parses a string Plus Code, local code or latlong into a str,
surplus.Localcode or surplus.Latlong respectively
@ -263,7 +281,7 @@ def parse_query(
def _word_match(
oquery: str, squery: list[str]
) -> tuple[bool, str | Localcode | Latlong]:
) -> tuple[Literal[True], str | Localcode | Latlong] | tuple[Literal[False], str]:
"""
internal helper code reuse function
@ -285,6 +303,9 @@ def parse_query(
locality = oquery.replace(pcode, "")
locality = locality.strip().strip(",").strip()
if debug:
stderr.write(f"debug: {pcode=}, {locality=}\n")
return True, Localcode(code=pcode, locality=locality)
return False, "unable to find a pluscode/match to a format"
@ -324,6 +345,66 @@ def parse_query(
return _word_match(oquery=query, squery=squery)
def handle_query(
query: str | Localcode | Latlong, debug: bool = False
) -> tuple[Literal[True], Latlong] | tuple[Literal[False], str]:
"""
function that gets returns a surplus.Latlong from a Plus Code string,
surplus.Localcode or surplus.Latlong object.
used after surplus.parse_query().
query: str | Localcode | Latlong
debug: bool = False
returns tuple[bool, str | Latlong]
(True, Latlong) - conversion was successful, second element is latlong
(False, <str>) - conversion failed, str is error message
"""
lat: float = 0.0
lon: float = 0.0
if isinstance(query, Latlong):
return True, query
else: # instances: str | Localcode
str_pcode: str = ""
if isinstance(query, Localcode):
result = query.full_length()
if not result[0]:
return False, result[1]
str_pcode = result[1]
else:
str_pcode = query
try:
pcode = PlusCode(str_pcode)
except KeyError:
return (
False,
"code given is not a full-length Plus Code (including area code), e.g.: 6PH58QMF+FX",
)
except Exception as pcderr:
return (
False,
f"error while decoding Plus Code: {pcderr.__class__.__name__} - {pcderr}",
)
lat = pcode.area.center().lat
lon = pcode.area.center().lon
if debug:
stderr.write(f"debug: {lat=}, {lon=}\n")
return True, Latlong(lat=lat, long=lon)
def cli() -> None:
parser = ArgumentParser(
prog="surplus",