Skip to content

Commit 2d7c01f

Browse files
committed
Entry point to create and compare translation
1 parent f4e7486 commit 2d7c01f

9 files changed

Lines changed: 263 additions & 38 deletions

src/SeleniumLibrary/entry/__main__.py

Lines changed: 67 additions & 12 deletions
Original file line numberDiff line numberDiff line change
@@ -13,24 +13,21 @@
1313
# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
1414
# See the License for the specific language governing permissions and
1515
# limitations under the License.
16+
import json
17+
from pathlib import Path
18+
from typing import Optional
1619
import click
1720

18-
from print_version import print_version
21+
from .get_versions import get_version
22+
from .translation import compare_translatoin, get_library_translaton
23+
1924

2025
CONTEXT_SETTINGS = {"help_option_names": ["-h", "--help"]}
26+
VERSION = get_version()
2127

2228

23-
@click.group(
24-
invoke_without_command=True, context_settings=CONTEXT_SETTINGS, no_args_is_help=True
25-
)
26-
@click.option(
27-
"--version",
28-
is_flag=True,
29-
help="Prints versions and exits",
30-
callback=print_version,
31-
expose_value=False,
32-
is_eager=True,
33-
)
29+
@click.group()
30+
@click.version_option(VERSION)
3431
def cli():
3532
"""Robot Framework SeleniumLibrary command line tool.
3633
@@ -44,6 +41,64 @@ def cli():
4441
"""
4542
pass
4643

44+
@cli.command()
45+
@click.argument(
46+
"filename",
47+
type=click.Path(exists=False, dir_okay=False, path_type=Path),
48+
required=True,
49+
)
50+
@click.option(
51+
"--plugings",
52+
help="Same as plugins argument in the library import.",
53+
default=None,
54+
type=str,
55+
)
56+
@click.option(
57+
"--compare",
58+
help="Compares the translation file sha256 sum to library documentation.",
59+
default=False,
60+
is_flag=True,
61+
show_default=True,
62+
)
63+
def translation(
64+
filename: Path,
65+
plugings: Optional[str] = None,
66+
compare: bool = False,
67+
):
68+
"""Default translation file from library keywords.
69+
70+
This will help users to create their own translation as Python plugins. Command
71+
will populate json file with english language. To create proper translation
72+
file, users needs to translate the keyword name and doc arguments values in
73+
json file.
74+
75+
The filename argument will tell where the default json file is saved.
76+
77+
The --pluging argument is same as plugins argument in the library import.
78+
If you use plugins, it is also get default translation json file also witht
79+
the plugin keyword included in the library.
80+
81+
If the --compare flag is set, then command does not generate template
82+
translation file. Then it compares sha256 sums from the filenane
83+
to ones read from the library documenentation. It will print out a list
84+
of keywords which documentation sha256 does not match. This will ease
85+
translation projects to identify keywords which documentation needs updating.
86+
"""
87+
translation = get_library_translaton(plugings)
88+
if compare:
89+
if table := compare_translatoin(filename, translation):
90+
print(
91+
"Found differences between translation and library, see below for details."
92+
)
93+
for line in table:
94+
print(line)
95+
else:
96+
print("Translation is valid, no updated needed.")
97+
else:
98+
with filename.open("w") as file:
99+
json.dump(translation, file, indent=4)
100+
print(f"Translation file created in {filename.absolute()}")
101+
47102

48103
if __name__ == "__main__":
49104
cli()

src/SeleniumLibrary/entry/print_version.py renamed to src/SeleniumLibrary/entry/get_versions.py

Lines changed: 11 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -13,6 +13,7 @@
1313
# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
1414
# See the License for the specific language governing permissions and
1515
# limitations under the License.
16+
1617
from pathlib import Path
1718
import re
1819
import subprocess
@@ -37,12 +38,17 @@ def get_library_version() -> str:
3738
return re.search('\n__version__ = "(.*)"', data).group(1)
3839

3940

40-
def print_version(ctx, param, value):
41+
def get_version():
4142
"""Display Python, Robot Framework, SeleniumLibrary and selenium versions"""
4243
python_version = (
4344
f"{sys.version_info.major}.{sys.version_info.minor}.{sys.version_info.micro}"
4445
)
45-
print(f"Used Python is: {sys.executable}\nVersion: {python_version}")
46-
print(f'Robot Framework version: "{get_rf_version()}"')
47-
print(f"Installed SeleniumLibrary version is: {get_library_version()}")
48-
print(f"Installed selenium version is: {__version__}")
46+
#print(f"Used Python is: {sys.executable}\nVersion: {python_version}")
47+
#print(f'Robot Framework version: "{get_rf_version()}"')
48+
49+
return (
50+
f"\nUsed Python is: {sys.executable}\n\tVersion: {python_version}\n"
51+
f'Robot Framework version: "{get_rf_version()}\n"'
52+
f"Installed SeleniumLibrary version is: {get_library_version()}\n"
53+
f"Installed selenium version is: {__version__}\n"
54+
)
Lines changed: 124 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,124 @@
1+
# Copyright 2008-2011 Nokia Networks
2+
# Copyright 2011-2016 Ryan Tomac, Ed Manlove and contributors
3+
# Copyright 2016- Robot Framework Foundation
4+
#
5+
# Licensed under the Apache License, Version 2.0 (the "License");
6+
# you may not use this file except in compliance with the License.
7+
# You may obtain a copy of the License at
8+
#
9+
# http://www.apache.org/licenses/LICENSE-2.0
10+
#
11+
# Unless required by applicable law or agreed to in writing, software
12+
# distributed under the License is distributed on an "AS IS" BASIS,
13+
# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
14+
# See the License for the specific language governing permissions and
15+
# limitations under the License.
16+
17+
import hashlib
18+
import inspect
19+
import json
20+
from pathlib import Path
21+
from typing import List, Optional
22+
23+
KEYWORD_NAME = "Keyword name"
24+
DOC_CHANGED = "Documentation update needed"
25+
NO_LIB_KEYWORD = "Keyword not found from library"
26+
MISSING_TRANSLATION = "Keyword is missing translation"
27+
MISSING_CHECKSUM = "Keyword tranlsaton is missing checksum"
28+
MAX_REASON_LEN = max(
29+
len(DOC_CHANGED),
30+
len(NO_LIB_KEYWORD),
31+
len(MISSING_TRANSLATION),
32+
len(MISSING_CHECKSUM),
33+
)
34+
35+
36+
def get_library_translaton(
37+
plugings: Optional[str] = None
38+
) -> dict:
39+
from SeleniumLibrary import SeleniumLibrary
40+
41+
selib = SeleniumLibrary(plugins=plugings)
42+
translation = {}
43+
for function in selib.attributes.values():
44+
translation[function.__name__] = {
45+
"name": function.__name__,
46+
"doc": function.__doc__,
47+
"sha256": hashlib.sha256(function.__doc__.encode("utf-16")).hexdigest(),
48+
}
49+
translation["__init__"] = {
50+
"name": "__init__",
51+
"doc": inspect.getdoc(selib),
52+
"sha256": hashlib.sha256(inspect.getdoc(selib).encode("utf-16")).hexdigest(), # type: ignore
53+
}
54+
translation["__intro__"] = {
55+
"name": "__intro__",
56+
"doc": selib.__doc__,
57+
"sha256": hashlib.sha256(selib.__doc__.encode("utf-16")).hexdigest(), # type: ignore
58+
}
59+
return translation
60+
61+
62+
def _max_kw_name_lenght(project_tanslation: dict) -> int:
63+
max_lenght = 0
64+
for keyword_data in project_tanslation.values():
65+
if (current_kw_length := len(keyword_data["name"])) > max_lenght:
66+
max_lenght = current_kw_length
67+
return max_lenght
68+
69+
70+
def _get_heading(max_kw_lenght: int) -> List[str]:
71+
heading = f"| {KEYWORD_NAME} "
72+
next_line = f"| {'-' * len(KEYWORD_NAME)}"
73+
if (padding := max_kw_lenght - len(KEYWORD_NAME)) > 0:
74+
heading = f"{heading}{' ' * padding}"
75+
next_line = f"{next_line}{'-' * padding}"
76+
reason = "Reason"
77+
reason_padding = MAX_REASON_LEN - len(reason)
78+
heading = f"{heading}| {reason}{' ' * reason_padding} |"
79+
next_line = f"{next_line} | {'-' * MAX_REASON_LEN} |"
80+
return [heading, next_line]
81+
82+
83+
def _table_doc_updated(lib_kw: str, max_name_lenght: int, reason: str) -> str:
84+
line = f"| {lib_kw} "
85+
if (padding := max_name_lenght - len(lib_kw) - 0) > 0:
86+
line = f"{line}{' ' * padding}| {reason} "
87+
else:
88+
line = f"{line}| {reason} "
89+
if reason_padding := MAX_REASON_LEN - len(reason):
90+
line = f"{line}{' ' * reason_padding}"
91+
return f"{line}|"
92+
93+
94+
def compare_translatoin(filename: Path, library_translation: dict):
95+
with filename.open("r") as file:
96+
project_translation = json.load(file)
97+
max_kw_lenght = _max_kw_name_lenght(library_translation)
98+
table_body = []
99+
for lib_kw, lib_kw_data in library_translation.items():
100+
project_kw_data = project_translation.get(lib_kw)
101+
if not project_kw_data:
102+
table_body.append(
103+
_table_doc_updated(lib_kw, max_kw_lenght, MISSING_TRANSLATION)
104+
)
105+
continue
106+
sha256_value = project_kw_data.get("sha256")
107+
if not sha256_value:
108+
table_body.append(
109+
_table_doc_updated(lib_kw, max_kw_lenght, MISSING_CHECKSUM)
110+
)
111+
continue
112+
if project_kw_data["sha256"] != lib_kw_data["sha256"]:
113+
table_body.append(_table_doc_updated(lib_kw, max_kw_lenght, DOC_CHANGED))
114+
for project_kw in project_translation:
115+
if project_kw not in library_translation:
116+
table_body.append(
117+
_table_doc_updated(project_kw, max_kw_lenght, NO_LIB_KEYWORD)
118+
)
119+
if not table_body:
120+
return []
121+
122+
table = _get_heading(max_kw_lenght)
123+
table.extend(table_body)
124+
return table
Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,2 @@
1+
No changes
2+
Lines changed: 3 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,3 @@
1+
{
2+
"subdirectory": "approved_files"
3+
}
Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,2 @@
1+
No changes
2+
Lines changed: 6 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,6 @@
1+
Changes
2+
3+
0) | Keyword name | Reason |
4+
1) | ---------------------------------------- | -------------------------------------- |
5+
2) | alert_should_be_present | Documentation update needed |
6+
3) | handle_alert | Keyword is missing translation |
Lines changed: 48 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,48 @@
1+
import json
2+
from pathlib import Path
3+
import sys
4+
5+
from approvaltests import verify_all
6+
7+
from SeleniumLibrary.entry.get_versions import get_version
8+
from SeleniumLibrary.entry.translation import compare_translatoin, get_library_translaton
9+
10+
11+
def test_version():
12+
lines = get_version().splitlines()
13+
python_version = (
14+
f"{sys.version_info.major}.{sys.version_info.minor}.{sys.version_info.micro}"
15+
)
16+
assert len(lines) == 6
17+
assert "Used Python is: " in lines[1], lines[1]
18+
assert python_version in lines[2], lines[2]
19+
assert 'Robot Framework version: "' in lines[3], lines[3]
20+
assert 'Installed SeleniumLibrary version is: ' in lines[4], lines[4]
21+
assert 'Installed selenium version is: ' in lines[5], lines[5]
22+
23+
24+
def test_get_translation():
25+
data = get_library_translaton()
26+
for item in data.values():
27+
assert item["name"], item["name"]
28+
assert item["doc"], item["doc"]
29+
assert item["sha256"], item["sha256"]
30+
31+
def test_compare_translation(tmp_path: Path):
32+
translation = tmp_path / "translation.json"
33+
data = get_library_translaton()
34+
with translation.open("w") as file:
35+
json.dump(data, file, indent=4)
36+
table = compare_translatoin(translation, data)
37+
verify_all("No changes", table)
38+
39+
40+
def test_compare_translation_changes(tmp_path: Path):
41+
translation = tmp_path / "translation.json"
42+
data = get_library_translaton()
43+
data.pop("handle_alert", None)
44+
data["alert_should_be_present"]["sha256"] = "foo"
45+
with translation.open("w") as file:
46+
json.dump(data, file, indent=4)
47+
table = compare_translatoin(translation, get_library_translaton())
48+
verify_all("Changes", table)

utest/test/entry/test_version.py

Lines changed: 0 additions & 21 deletions
This file was deleted.

0 commit comments

Comments
 (0)