2019-04-05 12:40:43 +00:00
|
|
|
#!/usr/bin/env python3
|
2022-05-10 10:06:48 +00:00
|
|
|
# Copyright (C) 2019 The Qt Company Ltd.
|
|
|
|
# SPDX-License-Identifier: LicenseRef-Qt-Commercial OR GPL-3.0-only WITH Qt-GPL-exception-1.0
|
2019-04-05 12:40:43 +00:00
|
|
|
|
|
|
|
from __future__ import annotations
|
|
|
|
|
|
|
|
"""
|
|
|
|
This utility script shows statistics about
|
|
|
|
converted .pro -> CMakeLists.txt files.
|
|
|
|
|
|
|
|
To execute: python3 pro_conversion_rate.py <src dir>
|
|
|
|
where <src dir> can be any qt source directory. For better statistics,
|
|
|
|
specify a module root source dir (like ./qtbase or ./qtsvg).
|
|
|
|
|
|
|
|
"""
|
|
|
|
|
|
|
|
from argparse import ArgumentParser
|
|
|
|
|
|
|
|
import os
|
|
|
|
import typing
|
2019-10-10 13:56:57 +00:00
|
|
|
from typing import Dict, Union
|
2019-04-05 12:40:43 +00:00
|
|
|
from timeit import default_timer
|
|
|
|
|
|
|
|
|
|
|
|
def _parse_commandline():
|
Improve styling of util/cmake scripts
flake8 was used to evaluate the file, with a couple of exeptions:
E501,E266,W503
black was used to reformat the code automatically
The changes were:
* Added a README that explains how to use pipenv and pip,
* Remove unnecessary return statements,
* Remove '\' from the end of the lines,
* Use f-strings (>= 3.6) since we are requiring Python 3.7,
* Commenting unused variables,
* Adding assert when Python >= 3.7 is not being used,
* Wrapping long lines to 100 (Qt Style),
* Re-factoring some lines,
* Re-ordering imports,
* Naming `except` for sympy (SympifyError, TypeError)
Change-Id: Ie05f754e7d8ee4bf427117c58e0eb1b903202933
Reviewed-by: Alexandru Croitor <alexandru.croitor@qt.io>
2019-09-16 22:11:17 +00:00
|
|
|
parser = ArgumentParser(description="Find pro files for which there are no CMakeLists.txt.")
|
|
|
|
parser.add_argument(
|
|
|
|
"source_directory", metavar="<src dir>", type=str, help="The source directory"
|
|
|
|
)
|
2019-04-05 12:40:43 +00:00
|
|
|
|
|
|
|
return parser.parse_args()
|
|
|
|
|
|
|
|
|
|
|
|
class Blacklist:
|
2022-02-24 10:31:10 +00:00
|
|
|
"""Class to check if a certain dir_name / dir_path is blacklisted"""
|
2019-04-05 12:40:43 +00:00
|
|
|
|
|
|
|
def __init__(self, names: typing.List[str], path_parts: typing.List[str]):
|
|
|
|
self.names = names
|
|
|
|
self.path_parts = path_parts
|
|
|
|
|
|
|
|
# The lookup algorithm
|
|
|
|
self.lookup = self.is_blacklisted_part
|
|
|
|
self.tree = None
|
|
|
|
|
|
|
|
try:
|
|
|
|
# If package is available, use Aho-Corasick algorithm,
|
2019-10-08 13:36:05 +00:00
|
|
|
from ahocorapy.keywordtree import KeywordTree # type: ignore
|
Improve styling of util/cmake scripts
flake8 was used to evaluate the file, with a couple of exeptions:
E501,E266,W503
black was used to reformat the code automatically
The changes were:
* Added a README that explains how to use pipenv and pip,
* Remove unnecessary return statements,
* Remove '\' from the end of the lines,
* Use f-strings (>= 3.6) since we are requiring Python 3.7,
* Commenting unused variables,
* Adding assert when Python >= 3.7 is not being used,
* Wrapping long lines to 100 (Qt Style),
* Re-factoring some lines,
* Re-ordering imports,
* Naming `except` for sympy (SympifyError, TypeError)
Change-Id: Ie05f754e7d8ee4bf427117c58e0eb1b903202933
Reviewed-by: Alexandru Croitor <alexandru.croitor@qt.io>
2019-09-16 22:11:17 +00:00
|
|
|
|
2019-04-05 12:40:43 +00:00
|
|
|
self.tree = KeywordTree(case_insensitive=True)
|
|
|
|
|
|
|
|
for p in self.path_parts:
|
|
|
|
self.tree.add(p)
|
|
|
|
self.tree.finalize()
|
|
|
|
|
|
|
|
self.lookup = self.is_blacklisted_part_aho
|
|
|
|
except ImportError:
|
|
|
|
pass
|
|
|
|
|
|
|
|
def is_blacklisted(self, dir_name: str, dir_path: str) -> bool:
|
|
|
|
# First check if exact dir name is blacklisted.
|
|
|
|
if dir_name in self.names:
|
|
|
|
return True
|
|
|
|
|
|
|
|
# Check if a path part is blacklisted (e.g. util/cmake)
|
|
|
|
return self.lookup(dir_path)
|
|
|
|
|
|
|
|
def is_blacklisted_part(self, dir_path: str) -> bool:
|
|
|
|
if any(part in dir_path for part in self.path_parts):
|
|
|
|
return True
|
|
|
|
return False
|
|
|
|
|
|
|
|
def is_blacklisted_part_aho(self, dir_path: str) -> bool:
|
2019-10-10 13:56:57 +00:00
|
|
|
return self.tree.search(dir_path) is not None # type: ignore
|
2019-04-05 12:40:43 +00:00
|
|
|
|
|
|
|
|
|
|
|
def recursive_scan(path: str, extension: str, result_paths: typing.List[str], blacklist: Blacklist):
|
2022-02-24 10:31:10 +00:00
|
|
|
"""Find files ending with a certain extension, filtering out blacklisted entries"""
|
2019-04-05 12:40:43 +00:00
|
|
|
try:
|
|
|
|
for entry in os.scandir(path):
|
|
|
|
if entry.is_file() and entry.path.endswith(extension):
|
|
|
|
result_paths.append(entry.path)
|
|
|
|
elif entry.is_dir():
|
|
|
|
if blacklist.is_blacklisted(entry.name, entry.path):
|
|
|
|
continue
|
|
|
|
recursive_scan(entry.path, extension, result_paths, blacklist)
|
|
|
|
except Exception as e:
|
|
|
|
print(e)
|
|
|
|
|
|
|
|
|
|
|
|
def check_for_cmake_project(pro_path: str) -> bool:
|
|
|
|
pro_dir_name = os.path.dirname(pro_path)
|
|
|
|
cmake_project_path = os.path.join(pro_dir_name, "CMakeLists.txt")
|
|
|
|
return os.path.exists(cmake_project_path)
|
|
|
|
|
|
|
|
|
Improve styling of util/cmake scripts
flake8 was used to evaluate the file, with a couple of exeptions:
E501,E266,W503
black was used to reformat the code automatically
The changes were:
* Added a README that explains how to use pipenv and pip,
* Remove unnecessary return statements,
* Remove '\' from the end of the lines,
* Use f-strings (>= 3.6) since we are requiring Python 3.7,
* Commenting unused variables,
* Adding assert when Python >= 3.7 is not being used,
* Wrapping long lines to 100 (Qt Style),
* Re-factoring some lines,
* Re-ordering imports,
* Naming `except` for sympy (SympifyError, TypeError)
Change-Id: Ie05f754e7d8ee4bf427117c58e0eb1b903202933
Reviewed-by: Alexandru Croitor <alexandru.croitor@qt.io>
2019-09-16 22:11:17 +00:00
|
|
|
def compute_stats(
|
|
|
|
src_path: str,
|
|
|
|
pros_with_missing_project: typing.List[str],
|
|
|
|
total_pros: int,
|
|
|
|
existing_pros: int,
|
|
|
|
missing_pros: int,
|
|
|
|
) -> dict:
|
2019-10-10 13:56:57 +00:00
|
|
|
stats: Dict[str, Dict[str, Union[str, int, float]]] = {}
|
Improve styling of util/cmake scripts
flake8 was used to evaluate the file, with a couple of exeptions:
E501,E266,W503
black was used to reformat the code automatically
The changes were:
* Added a README that explains how to use pipenv and pip,
* Remove unnecessary return statements,
* Remove '\' from the end of the lines,
* Use f-strings (>= 3.6) since we are requiring Python 3.7,
* Commenting unused variables,
* Adding assert when Python >= 3.7 is not being used,
* Wrapping long lines to 100 (Qt Style),
* Re-factoring some lines,
* Re-ordering imports,
* Naming `except` for sympy (SympifyError, TypeError)
Change-Id: Ie05f754e7d8ee4bf427117c58e0eb1b903202933
Reviewed-by: Alexandru Croitor <alexandru.croitor@qt.io>
2019-09-16 22:11:17 +00:00
|
|
|
stats["total projects"] = {"label": "Total pro files found", "value": total_pros}
|
|
|
|
stats["existing projects"] = {
|
|
|
|
"label": "Existing CMakeLists.txt files found",
|
|
|
|
"value": existing_pros,
|
|
|
|
}
|
|
|
|
stats["missing projects"] = {
|
|
|
|
"label": "Missing CMakeLists.txt files found",
|
|
|
|
"value": missing_pros,
|
|
|
|
}
|
|
|
|
stats["missing examples"] = {"label": "Missing examples", "value": 0}
|
|
|
|
stats["missing tests"] = {"label": "Missing tests", "value": 0}
|
|
|
|
stats["missing src"] = {"label": "Missing src/**/**", "value": 0}
|
|
|
|
stats["missing plugins"] = {"label": "Missing plugins", "value": 0}
|
2019-04-05 12:40:43 +00:00
|
|
|
|
|
|
|
for p in pros_with_missing_project:
|
|
|
|
rel_path = os.path.relpath(p, src_path)
|
|
|
|
if rel_path.startswith("examples"):
|
2019-10-10 13:56:57 +00:00
|
|
|
assert isinstance(stats["missing examples"]["value"], int)
|
Improve styling of util/cmake scripts
flake8 was used to evaluate the file, with a couple of exeptions:
E501,E266,W503
black was used to reformat the code automatically
The changes were:
* Added a README that explains how to use pipenv and pip,
* Remove unnecessary return statements,
* Remove '\' from the end of the lines,
* Use f-strings (>= 3.6) since we are requiring Python 3.7,
* Commenting unused variables,
* Adding assert when Python >= 3.7 is not being used,
* Wrapping long lines to 100 (Qt Style),
* Re-factoring some lines,
* Re-ordering imports,
* Naming `except` for sympy (SympifyError, TypeError)
Change-Id: Ie05f754e7d8ee4bf427117c58e0eb1b903202933
Reviewed-by: Alexandru Croitor <alexandru.croitor@qt.io>
2019-09-16 22:11:17 +00:00
|
|
|
stats["missing examples"]["value"] += 1
|
2019-04-05 12:40:43 +00:00
|
|
|
elif rel_path.startswith("tests"):
|
2019-10-10 13:56:57 +00:00
|
|
|
assert isinstance(stats["missing tests"]["value"], int)
|
Improve styling of util/cmake scripts
flake8 was used to evaluate the file, with a couple of exeptions:
E501,E266,W503
black was used to reformat the code automatically
The changes were:
* Added a README that explains how to use pipenv and pip,
* Remove unnecessary return statements,
* Remove '\' from the end of the lines,
* Use f-strings (>= 3.6) since we are requiring Python 3.7,
* Commenting unused variables,
* Adding assert when Python >= 3.7 is not being used,
* Wrapping long lines to 100 (Qt Style),
* Re-factoring some lines,
* Re-ordering imports,
* Naming `except` for sympy (SympifyError, TypeError)
Change-Id: Ie05f754e7d8ee4bf427117c58e0eb1b903202933
Reviewed-by: Alexandru Croitor <alexandru.croitor@qt.io>
2019-09-16 22:11:17 +00:00
|
|
|
stats["missing tests"]["value"] += 1
|
2019-04-05 12:40:43 +00:00
|
|
|
elif rel_path.startswith(os.path.join("src", "plugins")):
|
2019-10-10 13:56:57 +00:00
|
|
|
assert isinstance(stats["missing plugins"]["value"], int)
|
Improve styling of util/cmake scripts
flake8 was used to evaluate the file, with a couple of exeptions:
E501,E266,W503
black was used to reformat the code automatically
The changes were:
* Added a README that explains how to use pipenv and pip,
* Remove unnecessary return statements,
* Remove '\' from the end of the lines,
* Use f-strings (>= 3.6) since we are requiring Python 3.7,
* Commenting unused variables,
* Adding assert when Python >= 3.7 is not being used,
* Wrapping long lines to 100 (Qt Style),
* Re-factoring some lines,
* Re-ordering imports,
* Naming `except` for sympy (SympifyError, TypeError)
Change-Id: Ie05f754e7d8ee4bf427117c58e0eb1b903202933
Reviewed-by: Alexandru Croitor <alexandru.croitor@qt.io>
2019-09-16 22:11:17 +00:00
|
|
|
stats["missing plugins"]["value"] += 1
|
2019-04-05 12:40:43 +00:00
|
|
|
elif rel_path.startswith("src"):
|
2019-10-10 13:56:57 +00:00
|
|
|
assert isinstance(stats["missing src"]["value"], int)
|
Improve styling of util/cmake scripts
flake8 was used to evaluate the file, with a couple of exeptions:
E501,E266,W503
black was used to reformat the code automatically
The changes were:
* Added a README that explains how to use pipenv and pip,
* Remove unnecessary return statements,
* Remove '\' from the end of the lines,
* Use f-strings (>= 3.6) since we are requiring Python 3.7,
* Commenting unused variables,
* Adding assert when Python >= 3.7 is not being used,
* Wrapping long lines to 100 (Qt Style),
* Re-factoring some lines,
* Re-ordering imports,
* Naming `except` for sympy (SympifyError, TypeError)
Change-Id: Ie05f754e7d8ee4bf427117c58e0eb1b903202933
Reviewed-by: Alexandru Croitor <alexandru.croitor@qt.io>
2019-09-16 22:11:17 +00:00
|
|
|
stats["missing src"]["value"] += 1
|
2019-04-05 12:40:43 +00:00
|
|
|
|
|
|
|
for stat in stats:
|
2019-10-10 13:56:57 +00:00
|
|
|
if int(stats[stat]["value"]) > 0:
|
|
|
|
stats[stat]["percentage"] = round(float(stats[stat]["value"]) * 100 / total_pros, 2)
|
2019-04-05 12:40:43 +00:00
|
|
|
return stats
|
|
|
|
|
|
|
|
|
Improve styling of util/cmake scripts
flake8 was used to evaluate the file, with a couple of exeptions:
E501,E266,W503
black was used to reformat the code automatically
The changes were:
* Added a README that explains how to use pipenv and pip,
* Remove unnecessary return statements,
* Remove '\' from the end of the lines,
* Use f-strings (>= 3.6) since we are requiring Python 3.7,
* Commenting unused variables,
* Adding assert when Python >= 3.7 is not being used,
* Wrapping long lines to 100 (Qt Style),
* Re-factoring some lines,
* Re-ordering imports,
* Naming `except` for sympy (SympifyError, TypeError)
Change-Id: Ie05f754e7d8ee4bf427117c58e0eb1b903202933
Reviewed-by: Alexandru Croitor <alexandru.croitor@qt.io>
2019-09-16 22:11:17 +00:00
|
|
|
def print_stats(
|
|
|
|
src_path: str,
|
|
|
|
pros_with_missing_project: typing.List[str],
|
|
|
|
stats: dict,
|
|
|
|
scan_time: float,
|
|
|
|
script_time: float,
|
|
|
|
):
|
2019-04-05 12:40:43 +00:00
|
|
|
|
Improve styling of util/cmake scripts
flake8 was used to evaluate the file, with a couple of exeptions:
E501,E266,W503
black was used to reformat the code automatically
The changes were:
* Added a README that explains how to use pipenv and pip,
* Remove unnecessary return statements,
* Remove '\' from the end of the lines,
* Use f-strings (>= 3.6) since we are requiring Python 3.7,
* Commenting unused variables,
* Adding assert when Python >= 3.7 is not being used,
* Wrapping long lines to 100 (Qt Style),
* Re-factoring some lines,
* Re-ordering imports,
* Naming `except` for sympy (SympifyError, TypeError)
Change-Id: Ie05f754e7d8ee4bf427117c58e0eb1b903202933
Reviewed-by: Alexandru Croitor <alexandru.croitor@qt.io>
2019-09-16 22:11:17 +00:00
|
|
|
if stats["total projects"]["value"] == 0:
|
2019-04-05 12:40:43 +00:00
|
|
|
print("No .pro files found. Did you specify a correct source path?")
|
|
|
|
return
|
|
|
|
|
Improve styling of util/cmake scripts
flake8 was used to evaluate the file, with a couple of exeptions:
E501,E266,W503
black was used to reformat the code automatically
The changes were:
* Added a README that explains how to use pipenv and pip,
* Remove unnecessary return statements,
* Remove '\' from the end of the lines,
* Use f-strings (>= 3.6) since we are requiring Python 3.7,
* Commenting unused variables,
* Adding assert when Python >= 3.7 is not being used,
* Wrapping long lines to 100 (Qt Style),
* Re-factoring some lines,
* Re-ordering imports,
* Naming `except` for sympy (SympifyError, TypeError)
Change-Id: Ie05f754e7d8ee4bf427117c58e0eb1b903202933
Reviewed-by: Alexandru Croitor <alexandru.croitor@qt.io>
2019-09-16 22:11:17 +00:00
|
|
|
if stats["total projects"]["value"] == stats["existing projects"]["value"]:
|
2019-04-05 12:40:43 +00:00
|
|
|
print("All projects were converted.")
|
|
|
|
else:
|
|
|
|
print("Missing CMakeLists.txt files for the following projects: \n")
|
|
|
|
|
|
|
|
for p in pros_with_missing_project:
|
|
|
|
rel_path = os.path.relpath(p, src_path)
|
|
|
|
print(rel_path)
|
|
|
|
|
|
|
|
print("\nStatistics: \n")
|
|
|
|
|
|
|
|
for stat in stats:
|
Improve styling of util/cmake scripts
flake8 was used to evaluate the file, with a couple of exeptions:
E501,E266,W503
black was used to reformat the code automatically
The changes were:
* Added a README that explains how to use pipenv and pip,
* Remove unnecessary return statements,
* Remove '\' from the end of the lines,
* Use f-strings (>= 3.6) since we are requiring Python 3.7,
* Commenting unused variables,
* Adding assert when Python >= 3.7 is not being used,
* Wrapping long lines to 100 (Qt Style),
* Re-factoring some lines,
* Re-ordering imports,
* Naming `except` for sympy (SympifyError, TypeError)
Change-Id: Ie05f754e7d8ee4bf427117c58e0eb1b903202933
Reviewed-by: Alexandru Croitor <alexandru.croitor@qt.io>
2019-09-16 22:11:17 +00:00
|
|
|
if stats[stat]["value"] > 0:
|
|
|
|
print(
|
2019-09-20 09:34:16 +00:00
|
|
|
f"{stats[stat]['label']:<40}: {stats[stat]['value']} ({stats[stat]['percentage']}%)"
|
Improve styling of util/cmake scripts
flake8 was used to evaluate the file, with a couple of exeptions:
E501,E266,W503
black was used to reformat the code automatically
The changes were:
* Added a README that explains how to use pipenv and pip,
* Remove unnecessary return statements,
* Remove '\' from the end of the lines,
* Use f-strings (>= 3.6) since we are requiring Python 3.7,
* Commenting unused variables,
* Adding assert when Python >= 3.7 is not being used,
* Wrapping long lines to 100 (Qt Style),
* Re-factoring some lines,
* Re-ordering imports,
* Naming `except` for sympy (SympifyError, TypeError)
Change-Id: Ie05f754e7d8ee4bf427117c58e0eb1b903202933
Reviewed-by: Alexandru Croitor <alexandru.croitor@qt.io>
2019-09-16 22:11:17 +00:00
|
|
|
)
|
2019-04-05 12:40:43 +00:00
|
|
|
|
2019-09-20 09:34:16 +00:00
|
|
|
print(f"\n{'Scan time':<40}: {scan_time:.10f} seconds")
|
|
|
|
print(f"{'Total script time':<40}: {script_time:.10f} seconds")
|
2019-04-05 12:40:43 +00:00
|
|
|
|
|
|
|
|
|
|
|
def main():
|
|
|
|
args = _parse_commandline()
|
|
|
|
src_path = os.path.abspath(args.source_directory)
|
|
|
|
pro_paths = []
|
|
|
|
|
|
|
|
extension = ".pro"
|
|
|
|
|
|
|
|
blacklist_names = ["config.tests", "doc", "3rdparty", "angle"]
|
Improve styling of util/cmake scripts
flake8 was used to evaluate the file, with a couple of exeptions:
E501,E266,W503
black was used to reformat the code automatically
The changes were:
* Added a README that explains how to use pipenv and pip,
* Remove unnecessary return statements,
* Remove '\' from the end of the lines,
* Use f-strings (>= 3.6) since we are requiring Python 3.7,
* Commenting unused variables,
* Adding assert when Python >= 3.7 is not being used,
* Wrapping long lines to 100 (Qt Style),
* Re-factoring some lines,
* Re-ordering imports,
* Naming `except` for sympy (SympifyError, TypeError)
Change-Id: Ie05f754e7d8ee4bf427117c58e0eb1b903202933
Reviewed-by: Alexandru Croitor <alexandru.croitor@qt.io>
2019-09-16 22:11:17 +00:00
|
|
|
blacklist_path_parts = [os.path.join("util", "cmake")]
|
2019-04-05 12:40:43 +00:00
|
|
|
|
|
|
|
script_start_time = default_timer()
|
|
|
|
blacklist = Blacklist(blacklist_names, blacklist_path_parts)
|
|
|
|
|
|
|
|
scan_time_start = default_timer()
|
|
|
|
recursive_scan(src_path, extension, pro_paths, blacklist)
|
|
|
|
scan_time_end = default_timer()
|
|
|
|
scan_time = scan_time_end - scan_time_start
|
|
|
|
|
|
|
|
total_pros = len(pro_paths)
|
|
|
|
|
|
|
|
pros_with_missing_project = []
|
|
|
|
for pro_path in pro_paths:
|
|
|
|
if not check_for_cmake_project(pro_path):
|
|
|
|
pros_with_missing_project.append(pro_path)
|
|
|
|
|
|
|
|
missing_pros = len(pros_with_missing_project)
|
|
|
|
existing_pros = total_pros - missing_pros
|
|
|
|
|
Improve styling of util/cmake scripts
flake8 was used to evaluate the file, with a couple of exeptions:
E501,E266,W503
black was used to reformat the code automatically
The changes were:
* Added a README that explains how to use pipenv and pip,
* Remove unnecessary return statements,
* Remove '\' from the end of the lines,
* Use f-strings (>= 3.6) since we are requiring Python 3.7,
* Commenting unused variables,
* Adding assert when Python >= 3.7 is not being used,
* Wrapping long lines to 100 (Qt Style),
* Re-factoring some lines,
* Re-ordering imports,
* Naming `except` for sympy (SympifyError, TypeError)
Change-Id: Ie05f754e7d8ee4bf427117c58e0eb1b903202933
Reviewed-by: Alexandru Croitor <alexandru.croitor@qt.io>
2019-09-16 22:11:17 +00:00
|
|
|
stats = compute_stats(
|
|
|
|
src_path, pros_with_missing_project, total_pros, existing_pros, missing_pros
|
|
|
|
)
|
2019-04-05 12:40:43 +00:00
|
|
|
script_end_time = default_timer()
|
|
|
|
script_time = script_end_time - script_start_time
|
|
|
|
|
|
|
|
print_stats(src_path, pros_with_missing_project, stats, scan_time, script_time)
|
|
|
|
|
|
|
|
|
Improve styling of util/cmake scripts
flake8 was used to evaluate the file, with a couple of exeptions:
E501,E266,W503
black was used to reformat the code automatically
The changes were:
* Added a README that explains how to use pipenv and pip,
* Remove unnecessary return statements,
* Remove '\' from the end of the lines,
* Use f-strings (>= 3.6) since we are requiring Python 3.7,
* Commenting unused variables,
* Adding assert when Python >= 3.7 is not being used,
* Wrapping long lines to 100 (Qt Style),
* Re-factoring some lines,
* Re-ordering imports,
* Naming `except` for sympy (SympifyError, TypeError)
Change-Id: Ie05f754e7d8ee4bf427117c58e0eb1b903202933
Reviewed-by: Alexandru Croitor <alexandru.croitor@qt.io>
2019-09-16 22:11:17 +00:00
|
|
|
if __name__ == "__main__":
|
2019-04-05 12:40:43 +00:00
|
|
|
main()
|