Refactor and add documentation.

This commit is contained in:
Sergey Vartanov 2021-10-17 13:39:26 +03:00
parent d2813da063
commit 010368dfea
10 changed files with 24 additions and 3 deletions

View file

@ -0,0 +1,3 @@
"""
Documentation utilities.
"""

View file

@ -0,0 +1,317 @@
"""
Moire markup extension for Map Machine.
"""
import argparse
from abc import ABC
from pathlib import Path
from typing import Any, Union
from moire.default import Default, DefaultHTML, DefaultMarkdown, DefaultWiki
from moire.moire import Tag
from map_machine.ui import cli
from map_machine.pictogram.icon import ShapeExtractor
from map_machine.ui.cli import COMMAND_LINES
from map_machine.workspace import workspace
__author__ = "Sergey Vartanov"
__email__ = "me@enzet.ru"
Arguments = list[Any]
Code = Union[str, Tag, list]
PREFIX: str = "https://wiki.openstreetmap.org/wiki/"
def parse_text(text: str, margins: str, tag_id: str) -> Code:
"""Parse formal arguments."""
word: str = ""
result: Code = []
inside: bool = False
for character in text:
if character not in margins:
word += character
continue
if word:
result.append(Tag(tag_id, [word]) if inside else word)
word = ""
inside = not inside
if word:
result.append(word)
return result
class ArgumentParser(argparse.ArgumentParser):
"""
Argument parser that stores arguments and creates help in Moire markup.
"""
def __init__(self, *args, **kwargs) -> None:
self.arguments: list[dict[str, Any]] = []
super(ArgumentParser, self).__init__(*args, **kwargs)
def add_argument(self, *args, **kwargs) -> None:
"""Just store argument with options."""
super(ArgumentParser, self).add_argument(*args, **kwargs)
argument: dict[str, Any] = {"arguments": [x for x in args]}
for key in kwargs:
argument[key] = kwargs[key]
self.arguments.append(argument)
def get_moire_help(self) -> Tag:
"""
Return Moire table with "Option" and "Description" columns filled with
arguments.
"""
table: Code = [[["Option"], ["Description"]]]
for option in self.arguments:
if option["arguments"][0] == "-h":
continue
array: Code = [
[Tag("no_wrap", [Tag("m", [x])]), ", "]
for x in option["arguments"]
]
cell: Code = [x for y in array for x in y][:-1]
if "metavar" in option:
cell += [
" ",
Tag("m", [parse_text(option["metavar"], "<>", "formal")]),
]
row: Code = [cell]
if "help" in option:
help_value: list = parse_text(option["help"], "`", "m")
if (
"default" in option
and option["default"]
and option["default"] != "==SUPPRESS=="
):
if (
"action" in option
and option["action"] == argparse.BooleanOptionalAction
):
if option["default"] is True:
help_value += [", set by default"]
elif option["default"] is False:
help_value += [", not set by default"]
else:
default_value: Code = Tag("m", [str(option["default"])])
if "type" in option and option["type"] in [int, float]:
default_value = str(option["default"])
help_value += [", default value: ", default_value]
row.append(help_value)
else:
row.append([])
table.append(row)
return Tag("table", table)
class MapMachineMoire(Default, ABC):
"""
Moire extension stub for Map Machine.
"""
def osm(self, args: Arguments) -> str:
"""OSM tag key or keyvalue pair of tag."""
spec: str = self.clear(args[0])
if "=" in spec:
key, tag = spec.split("=")
return (
self.get_ref_(f"{PREFIX}Key:{key}", self.m([key]))
+ "="
+ self.get_ref_(f"{PREFIX}Tag:{key}={tag}", self.m([tag]))
)
else:
return self.get_ref_(f"{PREFIX}Key:{spec}", self.m([spec]))
def color(self, args: Arguments) -> str:
"""Simple color sample."""
raise NotImplementedError("color")
def page_icon(self, args: Arguments) -> str:
"""HTML page icon."""
return ""
def command(self, args: Arguments) -> str:
"""Bash command from integration tests."""
return "map-machine " + " ".join(COMMAND_LINES[self.clear(args[0])])
def icon(self, args: Arguments) -> str:
"""Image with Röntgen icon."""
raise NotImplementedError("icon")
def options(self, args: Arguments) -> str:
"""Table with option descriptions."""
parser: ArgumentParser = ArgumentParser()
command: str = self.clear(args[0])
if command == "render":
cli.add_render_arguments(parser)
elif command == "server":
cli.add_server_arguments(parser)
elif command == "tile":
cli.add_tile_arguments(parser)
elif command == "map":
cli.add_map_arguments(parser)
elif command == "element":
cli.add_element_arguments(parser)
elif command == "mapcss":
cli.add_mapcss_arguments(parser)
else:
raise NotImplementedError(
"no separate function for parser creation"
)
return self.parse(parser.get_moire_help())
def kbd(self, args: Arguments) -> str:
"""Keyboard key."""
return self.m(args)
def no_wrap(self, args: Arguments) -> str:
"""Do not wrap text at white spaces."""
return self.parse(args[0])
class MapMachineHTML(MapMachineMoire, DefaultHTML):
"""
Simple HTML.
"""
def __init__(self) -> None:
super().__init__()
self.images: dict = {}
def table(self, arg: Arguments) -> str:
"""Simple table. First row is treated as header."""
content: str = ""
cell: str = "".join(
["<th>" + self.parse(td, in_block=True) + "</th>" for td in arg[0]]
)
content += f"<tr>{cell}</tr>"
for tr in arg[1:]:
cell: str = "".join(
["<td>" + self.parse(td, in_block=True) + "</td>" for td in tr]
)
content += f"<tr>{cell}</tr>"
return f"<table>{content}</table>"
def color(self, args: Arguments) -> str:
"""Simple color sample."""
return (
f'<span class="color" '
f'style="background-color: {self.clear(args[0])};"></span>'
)
def icon(self, args: Arguments) -> str:
"""Image with Röntgen icon."""
size: str = self.clear(args[1]) if len(args) > 1 else 16
return (
f'<img class="icon" style="width: {size}px; height: {size}px;" '
f'src="out/icons_by_id/{self.clear(args[0])}.svg" />'
)
def kbd(self, args: Arguments) -> str:
"""Keyboard key."""
return f"<kbd>{self.clear(args[0])}</kbd>"
def page_icon(self, args: Arguments) -> str:
"""HTML page icon."""
return (
f'<link rel="icon" type="image/svg" href="{self.clear(args[0])}"'
' sizes="16x16">'
)
def no_wrap(self, args: Arguments) -> str:
"""Do not wrap text at white spaces."""
return (
f'<span style="white-space: nowrap;">{self.parse(args[0])}</span>'
)
def formal(self, args: Arguments) -> str:
"""Formal variable."""
return f'<span class="formal">{self.parse(args[0])}</span>'
class MapMachineOSMWiki(MapMachineMoire, DefaultWiki):
"""
OpenStreetMap wiki.
See https://wiki.openstreetmap.org/wiki/Main_Page
"""
def __init__(self) -> None:
super().__init__()
self.images: dict = {}
self.extractor: ShapeExtractor = ShapeExtractor(
workspace.ICONS_PATH, workspace.ICONS_CONFIG_PATH
)
def osm(self, args: Arguments) -> str:
"""OSM tag key or keyvalue pair of tag."""
spec: str = self.clear(args[0])
if "=" in spec:
key, tag = spec.split("=")
return f"{{{{Key|{key}|{tag}}}}}"
else:
return f"{{{{Tag|{spec}}}}}"
def color(self, args: Arguments) -> str:
"""Simple color sample."""
return f"{{{{Color box|{self.clear(args[0])}}}}}"
def icon(self, args: Arguments) -> str:
"""Image with Röntgen icon."""
size: str = self.clear(args[1]) if len(args) > 1 else 16
shape_id: str = self.clear(args[0])
name: str = self.extractor.get_shape(shape_id).name
return f"[[File:Röntgen {name}.svg|{size}px]]"
class MapMachineMarkdown(MapMachineMoire, DefaultMarkdown):
"""
GitHub flavored markdown.
"""
images = {}
def color(self, args: Arguments) -> str:
"""Simple color sample."""
return self.clear(args[0])
def icon(self, args: Arguments) -> str:
"""Image with Röntgen icon."""
return f"[{self.clear(args[0])}]"
def kbd(self, args: Arguments) -> str:
"""Keyboard key."""
return f"<kbd>{self.clear(args[0])}</kbd>"
def no_wrap(self, args: Arguments) -> str:
"""Do not wrap text at white spaces."""
return (
f'<span style="white-space: nowrap;">{self.parse(args[0])}</span>'
)
def formal(self, args: Arguments) -> str:
"""Formal variable."""
return f"<{self.parse(args[0])}>"
def convert(input_path: Path, output_path: Path) -> None:
"""Convert Moire file to Markdown."""
with input_path.open(encoding="utf-8") as input_file:
with output_path.open("w+", encoding="utf-8") as output_file:
output_file.write(MapMachineMarkdown().convert(input_file.read()))
if __name__ == "__main__":
convert(Path("readme.moi"), Path("../../README.md"))
convert(Path("contributing.moi"), Path("../../.github/CONTRIBUTING.md"))

View file

@ -0,0 +1,87 @@
"""
Creating Taginfo project file.
See https://wiki.openstreetmap.org/wiki/Taginfo/Projects
"""
import json
import logging
from datetime import datetime
from pathlib import Path
from map_machine import (
__author__,
__description__,
__doc_url__,
__email__,
__project__,
__url__,
)
from map_machine.scheme import Scheme
from map_machine.workspace import workspace
class TaginfoProjectFile:
"""
JSON structure with OpenStreetMap tag usage.
"""
def __init__(self, path: Path, scheme: Scheme) -> None:
self.path: Path = path
self.structure = {
"data_format": 1,
"data_url": __url__ + "/" + str(path),
"data_updated": datetime.now().strftime("%Y%m%dT%H%M%SZ"),
"project": {
"name": __project__,
"description": __description__,
"project_url": __url__,
"doc_url": __doc_url__,
"icon_url": "http://enzet.ru/map-machine/image/logo.png",
"contact_name": __author__,
"contact_email": __email__,
},
"tags": [],
}
tags = self.structure["tags"]
for matcher in scheme.node_matchers:
if (
not matcher.location_restrictions
and matcher.shapes
and len(matcher.tags) == 1
and not matcher.add_shapes
):
key: str = list(matcher.tags.keys())[0]
value: str = matcher.tags[key]
ids: list[str] = [
(x if isinstance(x, str) else x["shape"])
for x in matcher.shapes
]
icon_id: str = "___".join(ids)
if value == "*":
continue
tag = {
"key": key,
"value": value,
"object_types": ["node", "area"],
"description": "Rendered",
"icon_url": "http://enzet.ru/map-machine/"
f"roentgen_icons_mapcss/{icon_id}.svg",
}
tags.append(tag)
def write(self) -> None:
"""Write Taginfo JSON file."""
with self.path.open("w+", encoding="utf-8") as output_file:
json.dump(self.structure, output_file, indent=4, sort_keys=True)
def write_taginfo_project_file(scheme: Scheme) -> None:
"""Write Taginfo JSON file."""
out_file: Path = workspace.get_taginfo_file_path()
logging.info(f"Write Map Machine project file for Taginfo to {out_file}...")
taginfo_project_file: TaginfoProjectFile = TaginfoProjectFile(
out_file, scheme
)
taginfo_project_file.write()