Skip to content

Commit

Permalink
CPP-AP: version 1.2
Browse files Browse the repository at this point in the history
- Added the `demo` workflow
- Cleanup of the scripts, CMake and workflow files
- Improved error logic for argument value getting
  • Loading branch information
SpectraL519 committed Dec 8, 2024
1 parent a4ed360 commit 855015a
Show file tree
Hide file tree
Showing 9 changed files with 134 additions and 85 deletions.
Original file line number Diff line number Diff line change
@@ -1,10 +1,10 @@
name: licence
name: license
on:
push:
branches:
- '*'
paths:
- .github/workflows/licence.yaml
- .github/workflows/license.yaml
- scripts/check_licence.py
- include/**

Expand All @@ -25,4 +25,4 @@ jobs:
- name: Test licence
shell: bash
run: |
python3 scripts/check_licence.py
python3 scripts/check_license.py
2 changes: 1 addition & 1 deletion Doxyfile
Original file line number Diff line number Diff line change
Expand Up @@ -48,7 +48,7 @@ PROJECT_NAME = "CPP-AP"
# could be handy for archiving the generated documentation or if some version
# control system is used.

PROJECT_NUMBER = 1.1.1
PROJECT_NUMBER = 1.2

# Using the PROJECT_BRIEF tag one can provide an optional one line description
# for a project that appears at the top of each page and should give viewer a
Expand Down
6 changes: 3 additions & 3 deletions README.md
Original file line number Diff line number Diff line change
Expand Up @@ -45,7 +45,7 @@ The `CPP-AP` library does not require installing any additional tools or heavy l
- [Formatting](#formatting)
- [Documentation](#documentation)
- [Compiler support](#compiler-support)
- [Licence](#licence)
- [License](#license)

<br />
<br />
Expand Down Expand Up @@ -587,6 +587,6 @@ The documentation for this project can be generated using Doxygen:
<br />
<br />
## Licence
## License
The `CPP-AP` project uses the [MIT Licence](https://mit-license.org/) which can be found in the [LICENCE](/LICENSE) file
The `CPP-AP` project uses the [MIT License](https://mit-license.org/) which can be found in the [LICENSE](/LICENSE) file
78 changes: 50 additions & 28 deletions include/ap/argument_parser.hpp
Original file line number Diff line number Diff line change
@@ -1,14 +1,37 @@
// Copyright (c) 2023-2024 Jakub Musiał
// This file is part of the CPP-AP project (https://github.com/SpectraL519/cpp-ap).
// Licensed under the MIT License. See the LICENSE file in the project root for full license information.
/*
CPP-AP: Command-line argument parser for C++20
MIT License
Copyright (c) 2023-2024 Jakub Musiał and other contributors
https://github.com/SpectraL519/cpp-ap
Permission is hereby granted, free of charge, to any person obtaining a copy
of this software and associated documentation files (the "Software"), to deal
in the Software without restriction, including without limitation the rights
to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
copies of the Software, and to permit persons to whom the Software is
furnished to do so, subject to the following conditions:
The above copyright notice and this permission notice shall be included in all
copies or substantial portions of the Software.
THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR 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 OTHER DEALINGS IN THE
SOFTWARE.
*/

/*!
* @file argument_parser.hpp
* @brief CPP-AP library header file.
* @brief CPP-AP library source file.
*
* This header file contians the entire CPP-AP library implementation.
*
* @version 1.1
* @version 1.2
*/

#pragma once
Expand Down Expand Up @@ -55,22 +78,22 @@ namespace utility {
* @tparam T Type to check.
*/
template <typename T>
concept readable = requires(T value, std::istream& input_stream) { input_stream >> value; };
concept c_readable = requires(T value, std::istream& input_stream) { input_stream >> value; };

/**
* @brief The concept is satisfied when `T` is readable, copy constructible and assignable.
* @brief The concept is satisfied when `T` is c_readable, copy constructible and assignable.
* @tparam T Type to check.
*/
template <typename T>
concept valid_argument_value_type =
readable<T> and std::copy_constructible<T> and std::assignable_from<T&, const T&>;
concept c_argument_value_type =
c_readable<T> and std::copy_constructible<T> and std::assignable_from<T&, const T&>;

/**
* @brief The concept is satisfied when `T` is comparable using the equality operator `==`.
* @tparam T Type to check.
*/
template <typename T>
concept equality_comparable = requires(T lhs, T rhs) {
concept c_equality_comparable = requires(T lhs, T rhs) {
{ lhs == rhs } -> std::convertible_to<bool>;
};

Expand Down Expand Up @@ -556,13 +579,13 @@ class range {

/// @brief Defines valued argument action traits.
struct valued_action {
template <ap::utility::valid_argument_value_type T>
template <ap::utility::c_argument_value_type T>
using type = std::function<T(const T&)>;
};

/// @brief Defines void argument action traits.
struct void_action {
template <ap::utility::valid_argument_value_type T>
template <ap::utility::c_argument_value_type T>
using type = std::function<void(T&)>;
};

Expand All @@ -581,15 +604,14 @@ namespace detail {
* @tparam AS The action specifier type.
*/
template <typename AS>
concept valid_action_specifier =
ap::utility::is_valid_type_v<AS, ap::valued_action, ap::void_action>;
concept c_action_specifier = ap::utility::is_valid_type_v<AS, ap::valued_action, ap::void_action>;

/// @brief Template argument action callable type alias.
template <valid_action_specifier AS, ap::utility::valid_argument_value_type T>
template <c_action_specifier AS, ap::utility::c_argument_value_type T>
using callable_type = typename AS::template type<T>;

/// @brief Template argument action callabla variant type alias.
template <ap::utility::valid_argument_value_type T>
template <ap::utility::c_argument_value_type T>
using action_variant_type =
std::variant<callable_type<ap::valued_action, T>, callable_type<ap::void_action, T>>;

Expand All @@ -599,15 +621,15 @@ using action_variant_type =
* @param action The action variant.
* @return True if the held action is a void action.
*/
template <ap::utility::valid_argument_value_type T>
template <ap::utility::c_argument_value_type T>
[[nodiscard]] inline bool is_void_action(const action_variant_type<T>& action) noexcept {
return std::holds_alternative<callable_type<ap::void_action, T>>(action);
}

} // namespace detail

/// @brief Returns a default argument action.
template <ap::utility::valid_argument_value_type T>
template <ap::utility::c_argument_value_type T>
detail::callable_type<ap::void_action, T> default_action() noexcept {
return [](T&) {};
}
Expand All @@ -629,7 +651,7 @@ namespace argument {
* @brief "Positional argument class of type T.
* @tparam T The type of the argument value.
*/
template <utility::valid_argument_value_type T = std::string>
template <utility::c_argument_value_type T = std::string>
class positional_argument : public detail::argument_interface {
public:
using value_type = T; ///< Type of the argument value.
Expand Down Expand Up @@ -672,7 +694,7 @@ class positional_argument : public detail::argument_interface {
* @note Requires T to be equality comparable.
*/
positional_argument& choices(const std::vector<value_type>& choices) noexcept
requires(utility::equality_comparable<value_type>)
requires(utility::c_equality_comparable<value_type>)
{
this->_choices = choices;
return *this;
Expand All @@ -685,7 +707,7 @@ class positional_argument : public detail::argument_interface {
* @param action The action function to set.
* @return Reference to the positional_argument.
*/
template <ap::action::detail::valid_action_specifier AS, std::invocable<value_type&> F>
template <ap::action::detail::c_action_specifier AS, std::invocable<value_type&> F>
positional_argument& action(F&& action) noexcept {
using callable_type = ap::action::detail::callable_type<AS, value_type>;
this->_action = std::forward<callable_type>(action);
Expand Down Expand Up @@ -847,7 +869,7 @@ class positional_argument : public detail::argument_interface {
* @brief Optional argument class of type T.
* @tparam T The type of the argument value.
*/
template <utility::valid_argument_value_type T = std::string>
template <utility::c_argument_value_type T = std::string>
class optional_argument : public detail::argument_interface {
public:
using value_type = T;
Expand Down Expand Up @@ -940,7 +962,7 @@ class optional_argument : public detail::argument_interface {
* @param action The action function to set.
* @return Reference to the optional_argument.
*/
template <ap::action::detail::valid_action_specifier AS, std::invocable<value_type&> F>
template <ap::action::detail::c_action_specifier AS, std::invocable<value_type&> F>
optional_argument& action(F&& action) noexcept {
using callable_type = ap::action::detail::callable_type<AS, value_type>;
this->_action = std::forward<callable_type>(action);
Expand All @@ -954,7 +976,7 @@ class optional_argument : public detail::argument_interface {
* @note Requires T to be equality comparable.
*/
optional_argument& choices(const std::vector<value_type>& choices) noexcept
requires(utility::equality_comparable<value_type>)
requires(utility::c_equality_comparable<value_type>)
{
this->_choices = choices;
return *this;
Expand Down Expand Up @@ -1239,7 +1261,7 @@ class argument_parser {
* @param primary_name The primary name of the argument.
* @return Reference to the added positional argument.
*/
template <utility::valid_argument_value_type T = std::string>
template <utility::c_argument_value_type T = std::string>
argument::positional_argument<T>& add_positional_argument(std::string_view primary_name) {
// TODO: check forbidden characters

Expand All @@ -1259,7 +1281,7 @@ class argument_parser {
* @param secondary_name The secondary name of the argument.
* @return Reference to the added positional argument.
*/
template <utility::valid_argument_value_type T = std::string>
template <utility::c_argument_value_type T = std::string>
argument::positional_argument<T>& add_positional_argument(
std::string_view primary_name, std::string_view secondary_name
) {
Expand All @@ -1280,7 +1302,7 @@ class argument_parser {
* @param primary_name The primary name of the argument.
* @return Reference to the added optional argument.
*/
template <utility::valid_argument_value_type T = std::string>
template <utility::c_argument_value_type T = std::string>
argument::optional_argument<T>& add_optional_argument(std::string_view primary_name) {
// TODO: check forbidden characters

Expand All @@ -1299,7 +1321,7 @@ class argument_parser {
* @param secondary_name The secondary name of the argument.
* @return Reference to the added optional argument.
*/
template <utility::valid_argument_value_type T = std::string>
template <utility::c_argument_value_type T = std::string>
argument::optional_argument<T>& add_optional_argument(
std::string_view primary_name, std::string_view secondary_name
) {
Expand Down
49 changes: 37 additions & 12 deletions scripts/check_licence.py → scripts/check_license.py
Original file line number Diff line number Diff line change
@@ -1,19 +1,17 @@
import argparse
import sys
from collections.abc import Iterable
from enum import IntEnum
from pathlib import Path

from common import find_files


LICENCE_INFO = [
"// Copyright (c) 2023-2024 Jakub Musiał",
"// This file is part of the CPP-AP project (https://github.com/SpectraL519/cpp-ap).",
"// Licensed under the MIT License. See the LICENSE file in the project root for full license information.",
]
ADDITIONAL_LICENCE_INFO = ["CPP-AP: Command-line argument parser for C++20"]


class DefaultParameters:
licence_file: Path = Path("LICENSE")
search_paths: list[str] = ["include"]
file_patterns: list[str] = ["*.cpp", "*.hpp", "*.c", "*.h"]
exclude_paths: list[str] = []
Expand All @@ -22,6 +20,13 @@ class DefaultParameters:

def parse_args():
parser = argparse.ArgumentParser()
parser.add_argument(
"-l", "--licence-file",
type=Path,
default=DefaultParameters.licence_file,
nargs="?",
help="path to the licence file"
)
parser.add_argument(
"-p", "--search-paths",
type=str,
Expand Down Expand Up @@ -50,14 +55,28 @@ def parse_args():
return vars(parser.parse_args())


def expected_licence(licence_file: Path, pre: Iterable[str] = None, post: Iterable[str] = None) -> list[str]:
with licence_file.open("r", encoding="utf-8") as f:
licence_content = f.read().splitlines()

content = []
if pre:
content.extend([*pre, ""])
content.extend(licence_content)
if post:
content.extend(["", *post])

return ["/*", *content, "*/"]


class ReturnCode(IntEnum):
ok = 0
file_to_short = -1
missing_licence = -2
invalid_licence = -3


def check_licence(files: set[Path]) -> int:
def check_licence(expected_licence: Iterable[str], files: set[Path]) -> int:
n_files = len(files)
print(f"Files to check: {n_files}")

Expand All @@ -66,7 +85,7 @@ def _set_return_code(c: ReturnCode):
nonlocal return_code
return_code = c if not return_code else return_code

n_licence_lines = len(LICENCE_INFO)
n_licence_lines = len(expected_licence)

def _check_file(file: Path):
with open(file, "r", encoding="utf-8") as f:
Expand All @@ -78,7 +97,7 @@ def _check_file(file: Path):
print(f"[Licence error] File `{file}` to short")
return

matching_lines = [lines[i] == LICENCE_INFO[i] for i in range(n_licence_lines)]
matching_lines = [lines[i] == expected_licence[i] for i in range(n_licence_lines)]
correct_licence = all(matching_lines)
if not correct_licence:
missing_info = any(matching_lines)
Expand All @@ -94,16 +113,22 @@ def _check_file(file: Path):
print(f"[{i + 1}/{n_files}] {file}")
_check_file(file)

print("Done!")

return return_code


def main(
search_paths: list[str],
file_patterns: list[str],
exclude_paths: list[str]
licence_file: Path,
search_paths: Iterable[str],
file_patterns: Iterable[str],
exclude_paths: Iterable[str]
):
files_to_check = find_files(search_paths, file_patterns, exclude_paths)
sys.exit(check_licence(files_to_check))
sys.exit(check_licence(
expected_licence(licence_file, pre=ADDITIONAL_LICENCE_INFO),
files_to_check
))


if __name__ == "__main__":
Expand Down
7 changes: 4 additions & 3 deletions scripts/common.py
Original file line number Diff line number Diff line change
@@ -1,10 +1,11 @@
from collections.abc import Iterable
from pathlib import Path


def find_files(
search_paths: list[str],
file_patterns: list[str],
exclude_paths: list[str]
search_paths: Iterable[str],
file_patterns: Iterable[str],
exclude_paths: Iterable[str]
) -> set[Path]:
matching_files = []
for search_path in search_paths:
Expand Down
Loading

0 comments on commit 855015a

Please sign in to comment.