surplus: implement groundwork for #18

This commit is contained in:
Mark Joshwel 2023-09-01 16:43:59 +00:00
parent fd372fb483
commit 3b136c30ba
2 changed files with 138 additions and 28 deletions

View file

@ -54,21 +54,25 @@ pip install git+https://github.com/markjoshwel/surplus.git@future
## command-line usage ## command-line usage
```text ```text
usage: surplus [-h] [-d] [-v] [query ...] usage: surplus [-h] [-d] [-v] [-c {pluscode,localcode,latlong,string}]
[query ...]
Google Maps Plus Code to iOS Shortcuts-like shareable text Google Maps Plus Code to iOS Shortcuts-like shareable text
positional arguments: positional arguments:
query full-length Plus Code (6PH58QMF+FX), shortened query full-length Plus Code (6PH58QMF+FX), shortened
Plus Code/'local code' (8QMF+FX Singapore), Plus Code/'local code' (8QMF+FX Singapore),
latlong (1.3336875, 103.7749375), or string latlong (1.3336875, 103.7749375), or string
query (e.g., 'Wisma Atria') query (e.g., 'Wisma Atria')
options: options:
-h, --help show this help message and exit -h, --help show this help message and exit
-d, --debug prints lat, long and reverser response dict to -d, --debug prints lat, long and reverser response dict to
stderr stderr
-v, --version prints version information to stderr and exits -v, --version prints version information to stderr and exits
-c {pluscode,localcode,latlong,string},
--convert-to {pluscode,localcode,latlong,string}
converts query to another type
``` ```
## developer's guide ## developer's guide

View file

@ -26,11 +26,12 @@ OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE,
ARISING FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR ARISING FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR
OTHER DEALINGS IN THE SOFTWARE. OTHER DEALINGS IN THE SOFTWARE.
For more information, please refer to <http:m/unlicense.org/> For more information, please refer to <http://unlicense.org/>
""" """
from argparse import ArgumentParser from argparse import ArgumentParser
from collections import OrderedDict from collections import OrderedDict
from enum import Enum
from sys import stderr, stdout from sys import stderr, stdout
from typing import ( from typing import (
Any, Any,
@ -41,6 +42,7 @@ from typing import (
TextIO, TextIO,
TypeAlias, TypeAlias,
TypeVar, TypeVar,
Sequence,
) )
from geopy import Location as _geopy_Location # type: ignore from geopy import Location as _geopy_Location # type: ignore
@ -125,6 +127,23 @@ class IncompletePlusCodeError(Exception):
# data structures # data structures
class SurplusQueryTypes(Enum):
"""enum representing the mode of surplus"""
PLUS_CODE = "pluscode"
LOCAL_CODE = "localcode"
LATLONG = "latlong"
STRING = "string"
class SurplusOperationMode(Enum):
"""enum representing the mode of surplus"""
GENERATE_TEXT = "generate"
CONVERT_TYPES = "convert"
ResultType = TypeVar("ResultType") ResultType = TypeVar("ResultType")
@ -215,6 +234,10 @@ class Latlong(NamedTuple):
longitude: float longitude: float
def __str__(self) -> str: def __str__(self) -> str:
"""
method that returns a comma-and-space-seperated string of self.latitude and
self.longitude
"""
return f"{self.latitude}, {self.longitude}" return f"{self.latitude}, {self.longitude}"
@ -256,7 +279,10 @@ class PlusCodeQuery(NamedTuple):
except KeyError: except KeyError:
return Result[Latlong]( return Result[Latlong](
EMPTY_LATLONG, error=IncompletePlusCodeError("Plus Code is not full-length (e.g., 6PH58QMF+FX)"), EMPTY_LATLONG,
error=IncompletePlusCodeError(
"Plus Code is not full-length (e.g., 6PH58QMF+FX)"
),
) )
except Exception as exc: except Exception as exc:
@ -447,6 +473,11 @@ class Behaviour(NamedTuple):
TextIO-like object representing a writeable file. defaults to sys.stdout. TextIO-like object representing a writeable file. defaults to sys.stdout.
debug: bool = False debug: bool = False
whether to print debug information to stderr whether to print debug information to stderr
operation_mode: SurplusOperationMode = SurplusOperationMode.GENERATE_TEXT
surplus operation mode enum value
convert_to_type: SurplusQueryTypes | None = None
surplus query type enum value for when
operation_mode = SurplusOperationMode.CONVERT_TYPES
""" """
query: list[str] query: list[str]
@ -455,6 +486,9 @@ class Behaviour(NamedTuple):
stderr: TextIO = stderr stderr: TextIO = stderr
stdout: TextIO = stdout stdout: TextIO = stdout
debug: bool = False debug: bool = False
version_header: bool = False
operation_mode: SurplusOperationMode = SurplusOperationMode.GENERATE_TEXT
convert_to_type: SurplusQueryTypes | None = None
# functions # functions
@ -510,7 +544,9 @@ def parse_query(
if not validator.is_full(portion_plus_code): if not validator.is_full(portion_plus_code):
return Result[Query]( return Result[Query](
LatlongQuery(EMPTY_LATLONG), LatlongQuery(EMPTY_LATLONG),
error=IncompletePlusCodeError("Plus Code is not full-length (e.g., 6PH58QMF+FX)"), error=IncompletePlusCodeError(
"Plus Code is not full-length (e.g., 6PH58QMF+FX)"
),
) )
# found a plus code! # found a plus code!
@ -557,9 +593,11 @@ def parse_query(
# found one! # found one!
return Result[Query](mpc_result.get()) return Result[Query](mpc_result.get())
# is a plus/local code, but missing details
if isinstance(mpc_result.error, IncompletePlusCodeError): if isinstance(mpc_result.error, IncompletePlusCodeError):
return mpc_result return mpc_result # propagate back up to caller
# not a plus/local code, try to match for latlong or string query
match behaviour.query: match behaviour.query:
case [single]: case [single]:
# possibly a: # possibly a:
@ -656,8 +694,27 @@ def handle_args() -> Behaviour:
default=False, default=False,
help="prints version information to stderr and exits", help="prints version information to stderr and exits",
) )
parser.add_argument(
"-c",
"--convert-to",
type=str,
choices=[str(v.value) for v in SurplusQueryTypes],
help="converts query to another type",
default="",
)
args = parser.parse_args() args = parser.parse_args()
convert_to_type: SurplusQueryTypes | None = None
if args.convert_to != "":
convert_to_type = SurplusQueryTypes(args.convert_to)
operation_mode: SurplusOperationMode = (
SurplusOperationMode.GENERATE_TEXT
if convert_to_type is None
else SurplusOperationMode.CONVERT_TYPES
)
behaviour = Behaviour( behaviour = Behaviour(
query=args.query, query=args.query,
geocoder=default_geocoder, geocoder=default_geocoder,
@ -665,19 +722,11 @@ def handle_args() -> Behaviour:
stderr=stderr, stderr=stderr,
stdout=stdout, stdout=stdout,
debug=args.debug, debug=args.debug,
version_header=args.version,
operation_mode=operation_mode,
convert_to_type=convert_to_type,
) )
# print header
(behaviour.stdout if behaviour.debug else behaviour.stderr).write(
f"surplus version {'.'.join([str(v) for v in VERSION])}"
+ (f", debug mode" if behaviour.debug else "")
+ "\n"
)
if args.version:
exit(0)
return behaviour return behaviour
@ -685,30 +734,87 @@ def surplus(
query: PlusCodeQuery | LocalCodeQuery | LatlongQuery | StringQuery, query: PlusCodeQuery | LocalCodeQuery | LatlongQuery | StringQuery,
behaviour: Behaviour, behaviour: Behaviour,
) -> Result[str]: ) -> Result[str]:
return Result[str]("", error=NotImplementedError()) """
query to shareable text conversion function
query: PlusCodeQuery | LocalCodeQuery | LatlongQuery | StringQuery
query object to convert, see respective docstrings for more information on each
type of query object
behaviour: Behaviour
program behaviour namedtuple
"""
def _unique(l: Sequence[str]) -> list[str]:
"""(internal function) returns a in-order unique list from list"""
unique: OrderedDict = OrderedDict()
for line in l:
unique.update({line: None})
return list(unique.keys())
def _generate_text(line_keys: Sequence[str]) -> str:
"""(internal function) TODO DOCSTRING"""
# TODO
return ""
# get latlong and handle result
latlong = query.to_lat_long_coord(geocoder=behaviour.geocoder)
if not latlong:
return Result[str]("", error=latlong.error)
if behaviour.debug:
behaviour.stderr.write(f"debug: {latlong.get()=}\n")
# operate on query
text: str = ""
if behaviour.operation_mode == SurplusOperationMode.GENERATE_TEXT:
# TODO
return Result[str]("", error="not fully implemented yet")
elif behaviour.operation_mode == SurplusOperationMode.CONVERT_TYPES:
# TODO: https://github.com/markjoshwel/surplus/issues/18
return Result[str]("", error="conversion functionality is not implemented yet")
else:
return Result[str]("", error="unknown operation mode")
# command-line entry # command-line entry
def cli() -> int: def cli() -> int:
# handle arguments and print version header
behaviour = handle_args() behaviour = handle_args()
(behaviour.stdout if behaviour.version_header else behaviour.stderr).write(
f"surplus version {'.'.join([str(v) for v in VERSION])}"
+ (f", debug mode" if behaviour.debug else "")
+ "\n"
)
if behaviour.version_header:
exit(0)
# parse query and handle result
query = parse_query(behaviour=behaviour) query = parse_query(behaviour=behaviour)
if not query: if not query:
behaviour.stderr.write(f"error: {query.cry(string=True)}\n") behaviour.stderr.write(f"error: {query.cry(string=not behaviour.debug)}\n")
return -1 return -1
if behaviour.debug: if behaviour.debug:
behaviour.stderr.write(f"debug: {query.get()=}\n") behaviour.stderr.write(f"debug: {query.get()=}\n")
# run surplus
text = surplus( text = surplus(
query=query.get(), query=query.get(),
behaviour=behaviour, behaviour=behaviour,
) )
# handle and display surplus result
if not text: if not text:
behaviour.stderr.write(f"error: {text.cry(string=True)}\n") behaviour.stderr.write(f"error: {text.cry(string=not behaviour.debug)}\n")
return -2 return -2
behaviour.stdout.write(text.get() + "\n") behaviour.stdout.write(text.get() + "\n")