-
Notifications
You must be signed in to change notification settings - Fork 29
New issue
Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.
By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.
Already on GitHub? Sign in to your account
Switch to pathlib and implement annotations #80
Merged
fabi1cazenave
merged 7 commits into
OneDeadKey:master
from
etienne-monier:refactor/pathlib-and-annotations
Feb 1, 2024
Merged
Changes from 2 commits
Commits
Show all changes
7 commits
Select commit
Hold shift + click to select a range
1fc29e6
refactor: Switch to pathlib and implement annotations
79bf0b0
fix: Fix file write in CLI
faacd6c
Merge branch 'master' into refactor/pathlib-and-annotations
fabi1cazenave 35a3772
Update layout.py
fabi1cazenave 6d7cb47
Update cli_xkb.py
fabi1cazenave affc8ae
Update cli.py
fabi1cazenave 805e902
Update test_serializer_klc.py
fabi1cazenave File filter
Filter by extension
Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
There are no files selected for viewing
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -1,9 +1,9 @@ | ||
#!/usr/bin/env python3 | ||
import json | ||
import os | ||
import shutil | ||
from contextlib import contextmanager | ||
from importlib import metadata | ||
from pathlib import Path | ||
from typing import List, Literal, Union | ||
|
||
import click | ||
|
||
|
@@ -12,120 +12,155 @@ | |
|
||
|
||
@click.group() | ||
def cli(): | ||
pass | ||
def cli() -> None: | ||
... | ||
|
||
|
||
def pretty_json(layout, path): | ||
"""Pretty-prints the JSON layout.""" | ||
def pretty_json(layout: KeyboardLayout, output_path: Path) -> None: | ||
"""Pretty-print the JSON layout. | ||
|
||
Parameters | ||
---------- | ||
layout : KeyboardLayout | ||
The layout to be exported. | ||
output_path : Path | ||
The output file path. | ||
""" | ||
text = ( | ||
json.dumps(layout.json, indent=2, ensure_ascii=False) | ||
.replace("\n ", " ") | ||
.replace("\n ]", " ]") | ||
.replace("\n }", " }") | ||
) | ||
with open(path, "w", encoding="utf8") as file: | ||
file.write(text) | ||
output_path.write_text(text, encoding="utf8") | ||
|
||
|
||
def make_all(layout: KeyboardLayout, output_dir_path: Path) -> None: | ||
"""Generate all layout output files. | ||
|
||
def make_all(layout, subdir): | ||
def out_path(ext=""): | ||
return os.path.join(subdir, layout.meta["fileName"] + ext) | ||
Parameters | ||
---------- | ||
layout : KeyboardLayout | ||
The layout to process. | ||
output_dir_path : Path | ||
The output directory. | ||
""" | ||
|
||
if not os.path.exists(subdir): | ||
os.makedirs(subdir) | ||
@contextmanager | ||
def file_creation_context(ext: str = "") -> Path: | ||
"""Generate an output file path for extension EXT, return it and finally echo info.""" | ||
path = output_dir_path / (layout.meta["fileName"] + ext) | ||
yield path | ||
click.echo(f"... {path}") | ||
|
||
if not output_dir_path.exists(): | ||
output_dir_path.mkdir(parents=True) | ||
|
||
# AHK driver | ||
ahk_path = out_path(".ahk") | ||
with open(ahk_path, "w", encoding="utf-8", newline="\n") as file: | ||
file.write("\uFEFF") # AHK scripts require a BOM | ||
file.write(layout.ahk) | ||
print("... " + ahk_path) | ||
with file_creation_context(".ahk") as ahk_path: | ||
with ahk_path.open("w", encoding="utf-8", newline="\n") as file: | ||
file.write("\uFEFF") # AHK scripts require a BOM | ||
file.write(layout.ahk) | ||
|
||
# Windows driver | ||
klc_path = out_path(".klc") | ||
with open(klc_path, "w", encoding="utf-16le", newline="\r\n") as file: | ||
file.write(layout.klc) | ||
print("... " + klc_path) | ||
with file_creation_context(".klc") as klc_path: | ||
with klc_path.open("w", encoding="utf-16le", newline="\r\n") as file: | ||
file.write(layout.klc) | ||
|
||
# macOS driver | ||
osx_path = out_path(".keylayout") | ||
with open(osx_path, "w", encoding="utf-8", newline="\n") as file: | ||
file.write(layout.keylayout) | ||
print("... " + osx_path) | ||
with file_creation_context(".keylayout") as osx_path: | ||
with osx_path.open("w", encoding="utf-8", newline="\n") as file: | ||
file.write(layout.keylayout) | ||
|
||
# Linux driver, user-space | ||
xkb_path = out_path(".xkb") | ||
with open(xkb_path, "w", encoding="utf-8", newline="\n") as file: | ||
file.write(layout.xkb) | ||
print("... " + xkb_path) | ||
with file_creation_context(".xkb") as xkb_path: | ||
with xkb_path.open("w", encoding="utf-8", newline="\n") as file: | ||
file.write(layout.xkb) | ||
|
||
# Linux driver, root | ||
xkb_custom_path = out_path(".xkb_custom") | ||
with open(xkb_custom_path, "w", encoding="utf-8", newline="\n") as file: | ||
file.write(layout.xkb_patch) | ||
print("... " + xkb_custom_path) | ||
with file_creation_context(".xkb_custom") as xkb_custom_path: | ||
with xkb_custom_path.open("w", encoding="utf-8", newline="\n") as file: | ||
file.write(layout.xkb_patch) | ||
|
||
# JSON data | ||
json_path = out_path(".json") | ||
pretty_json(layout, json_path) | ||
print("... " + json_path) | ||
with file_creation_context(".json") as json_path: | ||
pretty_json(layout, json_path) | ||
|
||
# SVG data | ||
svg_path = out_path(".svg") | ||
layout.svg.write(svg_path, pretty_print=True, encoding="utf-8") | ||
print("... " + svg_path) | ||
with file_creation_context(".svg") as svg_path: | ||
layout.svg.write(svg_path, pretty_print=True, encoding="utf-8") | ||
|
||
|
||
@cli.command() | ||
@click.argument("layout_descriptors", nargs=-1, type=click.Path(exists=True)) | ||
@click.argument( | ||
"layout_descriptors", | ||
nargs=-1, | ||
type=click.Path(exists=True, dir_okay=False, path_type=Path), | ||
) | ||
@click.option( | ||
"--out", default="all", type=click.Path(), help="Keyboard drivers to generate." | ||
"--out", | ||
default="all", | ||
type=click.Path(), | ||
help="Keyboard drivers to generate.", | ||
) | ||
def make(layout_descriptors, out): | ||
def make(layout_descriptors: List[Path], out: Union[Path, Literal["all"]]): | ||
"""Convert TOML/YAML descriptions into OS-specific keyboard drivers.""" | ||
|
||
for input_file in layout_descriptors: | ||
layout = KeyboardLayout(input_file) | ||
|
||
# default: build all in the `dist` subdirectory | ||
if out == "all": | ||
make_all(layout, "dist") | ||
make_all(layout, Path(__file__).parent.parent / "dist") | ||
continue | ||
|
||
# Transform out into Path. | ||
out = Path(out) | ||
|
||
# quick output: reuse the input name and change the file extension | ||
if out in ["keylayout", "klc", "xkb", "xkb_custom", "svg"]: | ||
output_file = os.path.splitext(input_file)[0] + "." + out | ||
output_file = input_file.with_suffix("." + out) | ||
else: | ||
output_file = out | ||
|
||
# detailed output | ||
if output_file.endswith(".ahk"): | ||
with open(output_file, "w", encoding="utf-8", newline="\n") as file: | ||
if output_file.suffix == ".ahk": | ||
with output_file.open("w", encoding="utf-8", newline="\n") as file: | ||
file.write("\uFEFF") # AHK scripts require a BOM | ||
file.write(layout.ahk) | ||
elif output_file.endswith(".klc"): | ||
with open(output_file, "w", encoding="utf-16le", newline="\r\n") as file: | ||
|
||
elif output_file.suffix == ".klc": | ||
with output_file.open("w", encoding="utf-16le", newline="\r\n") as file: | ||
file.write(layout.klc) | ||
elif output_file.endswith(".keylayout"): | ||
with open(output_file, "w", encoding="utf-8", newline="\n") as file: | ||
|
||
elif output_file.suffix == ".keylayout": | ||
with output_file.open( | ||
"w", encoding="utf-8", newline="\n" | ||
) as file: | ||
file.write(layout.keylayout) | ||
elif output_file.endswith(".xkb"): | ||
with open(output_file, "w", encoding="utf-8", newline="\n") as file: | ||
|
||
elif output_file.suffix == ".xkb": | ||
with output_file.open("w", encoding="utf-8", newline="\n") as file: | ||
file.write(layout.xkb) | ||
elif output_file.endswith(".xkb_custom"): | ||
with open(output_file, "w", encoding="utf-8", newline="\n") as file: | ||
|
||
elif output_file.suffix == ".xkb_custom": | ||
with output_file.open( | ||
"w", encoding="utf-8", newline="\n" | ||
) as file: | ||
file.write(layout.xkb_patch) | ||
elif output_file.endswith(".json"): | ||
|
||
elif output_file.suffix == ".json": | ||
pretty_json(layout, output_file) | ||
elif output_file.endswith(".svg"): | ||
|
||
elif output_file.suffix == ".svg": | ||
layout.svg.write(output_file, pretty_print=True, encoding="utf-8") | ||
|
||
else: | ||
print("Unsupported output format.") | ||
click.echo("Unsupported output format.", err=True) | ||
return | ||
|
||
# successfully converted, display file name | ||
print("... " + output_file) | ||
click.echo(f"... {output_file}") | ||
|
||
|
||
TOML_HEADER = """# kalamine keyboard layout descriptor | ||
|
@@ -145,28 +180,32 @@ def make(layout_descriptors, out): | |
1dk_shift = "'" # apostrophe""" | ||
|
||
|
||
# TODO: Provide geometry choices | ||
@cli.command() | ||
@click.argument("output_file", nargs=1, type=click.Path(exists=False)) | ||
@click.argument("output_file", nargs=1, type=click.Path(exists=False, path_type=Path)) | ||
@click.option("--geometry", default="ISO", help="Specify keyboard geometry.") | ||
@click.option("--altgr/--no-altgr", default=False, help="Set an AltGr layer.") | ||
@click.option("--1dk/--no-1dk", "odk", default=False, help="Set a custom dead key.") | ||
def create(output_file, geometry, altgr, odk): | ||
def create(output_file: Path, geometry: str, altgr: bool, odk: bool): | ||
"""Create a new TOML layout description.""" | ||
base_dir_path = Path(__file__).resolve(strict=True).parent.parent | ||
|
||
root = Path(__file__).resolve(strict=True).parent.parent | ||
|
||
def get_layout(name): | ||
layout = KeyboardLayout(str(root / "layouts" / f"{name}.toml")) | ||
def get_layout(name: str) -> KeyboardLayout: | ||
"""Return a layout of type NAME with constrained geometry.""" | ||
layout = KeyboardLayout(base_dir_path / "layouts" / f"{name}.toml") | ||
layout.geometry = geometry | ||
return layout | ||
|
||
def keymap(layout_name, layout_layer, layer_name=""): | ||
layer = "\n" | ||
layer += f"\n{layer_name or layout_layer} = '''" | ||
layer += "\n" | ||
layer += "\n".join(getattr(get_layout(layout_name), layout_layer)) | ||
layer += "\n'''" | ||
return layer | ||
def keymap(layout_name: str, layout_layer: str, layer_name: str = "") -> str: | ||
return """ | ||
|
||
{} = ''' | ||
{} | ||
''' | ||
""".format( | ||
layer_name or layout_layer, | ||
"\n".join(getattr(get_layout(layout_name), layout_layer)), | ||
) | ||
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. (Chipotage…) Là j’avoue que j’avais fait à coups de |
||
|
||
content = f'{TOML_HEADER}"{geometry.upper()}"' | ||
if odk: | ||
|
@@ -181,32 +220,29 @@ def keymap(layout_name, layout_layer, layer_name=""): | |
content += keymap("ansi", "base") | ||
|
||
# append user guide sections | ||
with (root / "docs" / "README.md").open() as f: | ||
with (base_dir_path / "docs" / "README.md").open() as f: | ||
sections = "".join(f.readlines()).split("\n\n\n") | ||
for topic in sections[1:]: | ||
content += "\n\n" | ||
content += "\n# " | ||
content += "\n# ".join(topic.rstrip().split("\n")) | ||
|
||
with open(output_file, "w", encoding="utf-8", newline="\n") as file: | ||
file.write(content) | ||
print("... " + output_file) | ||
output_file.write_text(content, "w", encoding="utf-8", newline="\n") | ||
click.echo(f"... {output_file}") | ||
|
||
|
||
@cli.command() | ||
@click.argument("input", nargs=1, type=click.Path(exists=True)) | ||
def watch(input): | ||
@click.argument("filepath", nargs=1, type=click.Path(exists=True, path_type=Path)) | ||
def watch(filepath: Path) -> None: | ||
"""Watch a TOML/YAML layout description and display it in a web server.""" | ||
|
||
keyboard_server(input) | ||
keyboard_server(filepath) | ||
|
||
|
||
@cli.command() | ||
def version(): | ||
def version() -> None: | ||
"""Show version number and exit.""" | ||
|
||
print(f"kalamine { metadata.version('kalamine') }") | ||
click.echo(f"kalamine { metadata.version('kalamine') }") | ||
|
||
|
||
if __name__ == "__main__": | ||
cli() | ||
cli() |
Oops, something went wrong.
Add this suggestion to a batch that can be applied as a single commit.
This suggestion is invalid because no changes were made to the code.
Suggestions cannot be applied while the pull request is closed.
Suggestions cannot be applied while viewing a subset of changes.
Only one suggestion per line can be applied in a batch.
Add this suggestion to a batch that can be applied as a single commit.
Applying suggestions on deleted lines is not supported.
You must change the existing code in this line in order to create a valid suggestion.
Outdated suggestions cannot be applied.
This suggestion has been applied or marked resolved.
Suggestions cannot be applied from pending reviews.
Suggestions cannot be applied on multi-line comments.
Suggestions cannot be applied while the pull request is queued to merge.
Suggestion cannot be applied right now. Please check back later.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Ce changement a l'air d'aller au-delà de l'utilisation de pathlib et des annotations. Je n'ai pas vérifié ce qu'il fait.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Merci du heads-up. En fait on ne veut pas de cette ligne.
C’est bourrin mais par défaut, kalamine génère tous les pilotes dans un sous-répertoire
dist
du répertoire courant. Ça mériterait certainement une option (e.g.--output-dir
), mais on ne veut pas générer les fichiers dans le dossier de kalamine.