diff --git a/.github/workflows/build.yml b/.github/workflows/build.yml index cd387a140..c957afcf9 100644 --- a/.github/workflows/build.yml +++ b/.github/workflows/build.yml @@ -20,6 +20,8 @@ jobs: run: python3 tools/stringtable_validator.py - name: Validate Return Types run: python3 tools/return_checker.py + - name: Validate Comments + run: python3 tools/comment_linter.py - name: Check for BOM uses: arma-actions/bom-check@master build: diff --git a/addons/ai/functions/fnc_waypointFastrope.sqf b/addons/ai/functions/fnc_waypointFastrope.sqf index d45e0819d..a8f337c6a 100644 --- a/addons/ai/functions/fnc_waypointFastrope.sqf +++ b/addons/ai/functions/fnc_waypointFastrope.sqf @@ -82,12 +82,12 @@ waitUntil { // Manaully position the helicopter to be almost exactly over the waypoint's position // Without manual handling, the helicopter will not fly directly over the waypoint's position // Instead, it will stop ~100 m away from it - this looks a bit rough but is the most reliable -// method for getting the helicopter into the correct position +// Method for getting the helicopter into the correct position private _startPos = getPosASL _vehicle; private _endPos = +_waypointPosition; // Using the waypointPosition command to get the height of the waypoint -// since the position provided to the waypoint script always has a height of zero +// Since the position provided to the waypoint script always has a height of zero _endPos set [2, FASTROPE_HEIGHT + (waypointPosition _waypoint select 2)]; _endPos = AGLtoASL _endPos; diff --git a/addons/area_markers/functions/fnc_configure.sqf b/addons/area_markers/functions/fnc_configure.sqf index 741ea3399..049571e74 100644 --- a/addons/area_markers/functions/fnc_configure.sqf +++ b/addons/area_markers/functions/fnc_configure.sqf @@ -183,7 +183,7 @@ private _keyDownEH = _display displayAddEventHandler ["KeyDown", { ctrlDelete _ctrlConfigure; }; - true // handled + true // Handled }; }]; diff --git a/addons/common/functions/fnc_deserializeObjects.sqf b/addons/common/functions/fnc_deserializeObjects.sqf index 8d3b3e1f2..770aaf63b 100644 --- a/addons/common/functions/fnc_deserializeObjects.sqf +++ b/addons/common/functions/fnc_deserializeObjects.sqf @@ -185,7 +185,7 @@ private _fnc_deserializeVehicle = { switch (_role) do { case "driver": { if (getText (configOf _unit >> "simulation") == "UAVPilot") then { - _unit moveInAny _vehicle; // moveInDriver does not work for virtual UAV crew, moveInAny does + _unit moveInAny _vehicle; // MoveInDriver does not work for virtual UAV crew, moveInAny does } else { _unit moveInDriver _vehicle; }; diff --git a/addons/common/functions/fnc_exportMissionSQF.sqf b/addons/common/functions/fnc_exportMissionSQF.sqf index 144598bfd..5b58e4c39 100644 --- a/addons/common/functions/fnc_exportMissionSQF.sqf +++ b/addons/common/functions/fnc_exportMissionSQF.sqf @@ -35,12 +35,12 @@ params [ ]; // Keep track of all processed objects and groups, their index in their corresponding array -// is used to determine the variable name used in the exported SQF +// Is used to determine the variable name used in the exported SQF private _processedObjects = []; private _processedGroups = []; // Separate the exported SQF into different sections, this is used to ensure the correct -// ordering of the output (for example, applying group properties after all units are created) +// Ordering of the output (for example, applying group properties after all units are created) private _outputGroups1 = []; private _outputObjects = []; private _outputGroups2 = []; @@ -290,7 +290,7 @@ private _fnc_processVehicle = { switch (toLower _role) do { case "driver": { - // moveInDriver does not work for virtual UAV crew, moveInAny does + // MoveInDriver does not work for virtual UAV crew, moveInAny does if (getText (configOf _unit >> "simulation") == "UAVPilot") then { _outputCrew pushBack ["%1 moveInAny %2;", _unitVarName, _varName]; } else { diff --git a/addons/common/functions/fnc_fireArtillery.sqf b/addons/common/functions/fnc_fireArtillery.sqf index fbcd3e12f..1ca716b9b 100644 --- a/addons/common/functions/fnc_fireArtillery.sqf +++ b/addons/common/functions/fnc_fireArtillery.sqf @@ -37,7 +37,7 @@ if (_position isEqualType "") then { }; // For small spread values, use doArtilleryFire directly to avoid delay -// between firing caused by using doArtilleryFire one round at a time +// Between firing caused by using doArtilleryFire one round at a time if (_spread <= LOW_SPREAD_THRESHOLD) exitWith { _unit doArtilleryFire [_position, _magazine, _rounds]; }; diff --git a/addons/common/functions/fnc_fireVLS.sqf b/addons/common/functions/fnc_fireVLS.sqf index e0682e8dd..fa9b27f84 100755 --- a/addons/common/functions/fnc_fireVLS.sqf +++ b/addons/common/functions/fnc_fireVLS.sqf @@ -28,7 +28,7 @@ params [["_unit", objNull, [objNull]], ["_position", [0, 0, 0], [[], objNull, ""], 3], ["_spread", 0, [0]], ["_magazine", "", [""]], ["_rounds", 1, [0]]]; // If an object is given as the position, the dummy targets will be -// attached to this object in order to make the missile track the object +// Attached to this object in order to make the missile track the object private _isObject = _position isEqualType objNull; if (_position isEqualType "") then { diff --git a/addons/common/functions/fnc_hasDefaultInventory.sqf b/addons/common/functions/fnc_hasDefaultInventory.sqf index b91ddbe97..e9720b306 100644 --- a/addons/common/functions/fnc_hasDefaultInventory.sqf +++ b/addons/common/functions/fnc_hasDefaultInventory.sqf @@ -27,7 +27,7 @@ if (_object isKindOf "CAManBase") exitWith { _default set [7, _current select 7]; // Sort uniform, vest, and backpack item arrays, their contents can be indentical - // but the arrays may be different depending on the order they were added to the container + // But the arrays may be different depending on the order they were added to the container { // 3, 4, and 5 are indices of uniform, vest, and backpack info in loadout arrays, respectively for "_i" from 3 to 5 do { @@ -43,7 +43,7 @@ private _current = [getItemCargo _object, getWeaponCargo _object, getMagazineCar private _default = [_object] call FUNC(getDefaultInventory); // Sort inventory arrays, their contents can be indentical but the arrays may be different -// depending on the order they were added to the container +// Depending on the order they were added to the container { { { diff --git a/addons/common/functions/fnc_selectPosition.sqf b/addons/common/functions/fnc_selectPosition.sqf index a356f0e16..66edcc160 100644 --- a/addons/common/functions/fnc_selectPosition.sqf +++ b/addons/common/functions/fnc_selectPosition.sqf @@ -101,7 +101,7 @@ private _keyboardEH = [_display, "KeyDown", { GVAR(selectPositionActive) = false; - true // handled + true // Handled }, [_objects, _function, _args]] call CBA_fnc_addBISEventHandler; private _drawEH = [_ctrlMap, "Draw", { diff --git a/addons/common/functions/fnc_setVehicleAmmo.sqf b/addons/common/functions/fnc_setVehicleAmmo.sqf index 7b6a20cbc..9d1320c5d 100644 --- a/addons/common/functions/fnc_setVehicleAmmo.sqf +++ b/addons/common/functions/fnc_setVehicleAmmo.sqf @@ -19,7 +19,7 @@ params ["_vehicle", "_percentage"]; // Set ammo for pylons with magazines, group pylons with the same -// magazine to better handle magazines with a low maximum ammo counts +// Magazine to better handle magazines with a low maximum ammo counts private _pylonMagazines = getPylonMagazines _vehicle; private _cfgMagazines = configFile >> "CfgMagazines"; diff --git a/addons/common/gui.hpp b/addons/common/gui.hpp index 8ec689c61..53c352277 100644 --- a/addons/common/gui.hpp +++ b/addons/common/gui.hpp @@ -191,7 +191,7 @@ class GVAR(RscOwners): RscControlsGroupNoScrollbars { h = POS_H(1); sizeEx = 4.32 * (1 / (getResolution select 3)) * pixelGrid * 0.5; colorBackground[] = {0, 0, 0, 0.5}; - colorBackgroundActive[] = COLOR_SETTING(EGVAR(common,darkMode),1,1,1,0.15,0.1,0.1,0.1,0.2); // lighter + colorBackgroundActive[] = COLOR_SETTING(EGVAR(common,darkMode),1,1,1,0.15,0.1,0.1,0.1,0.2); // Lighter colorBackgroundDisabled[] = COLOR_BACKGROUND_SETTING; colorDisabled[] = {1, 1, 1, 1}; colorFocused[] = {1, 1, 1, 0.1}; diff --git a/addons/context_actions/functions/fnc_teleportPlayers.sqf b/addons/context_actions/functions/fnc_teleportPlayers.sqf index cfc6e7139..661098461 100644 --- a/addons/context_actions/functions/fnc_teleportPlayers.sqf +++ b/addons/context_actions/functions/fnc_teleportPlayers.sqf @@ -27,7 +27,7 @@ private _players = _this select {isPlayer _x}; [_players, _entity] call EFUNC(common,teleportIntoVehicle); } else { if (count _players > 1) then { - // setVehiclePosition places units on surface directly below position + // SetVehiclePosition places units on surface directly below position // Sometimes this will be the second surface below the selected position // Adding a small vertical offset allows units to be teleported consistently onto surfaces such as rooftops _position = ASLtoATL _position vectorAdd [0, 0, 0.1]; diff --git a/addons/context_menu/functions/fnc_open.sqf b/addons/context_menu/functions/fnc_open.sqf index 6c1728302..412c6f040 100644 --- a/addons/context_menu/functions/fnc_open.sqf +++ b/addons/context_menu/functions/fnc_open.sqf @@ -28,7 +28,7 @@ GVAR(selected) = curatorSelected; // Handle currently hovered entity curatorMouseOver params ["_type", "_entity", "_index"]; -// curatorMouseOver returns group and index separately when hovering over a waypoint +// CuratorMouseOver returns group and index separately when hovering over a waypoint if (_type == "ARRAY") then { _entity = [_entity, _index]; }; @@ -45,7 +45,7 @@ if (_category != -1) then { }; // Add units of hovered groups to the selected units array to - // simulate selecting the group and then opening the menu + // Simulate selecting the group and then opening the menu if (_type == "GROUP") then { {GVAR(selected) select 0 pushBackUnique _x} forEach units _entity; }; diff --git a/addons/editor/functions/fnc_addGroupIcons.sqf b/addons/editor/functions/fnc_addGroupIcons.sqf index 63c1da6a1..058d0d923 100644 --- a/addons/editor/functions/fnc_addGroupIcons.sqf +++ b/addons/editor/functions/fnc_addGroupIcons.sqf @@ -22,7 +22,7 @@ params ["_display"]; private _config = configFile >> "CfgGroups"; // Iterate through every tree path and use the tree item data -// to find the corresponding config for each group +// To find the corresponding config for each group { private _ctrlTree = _display displayCtrl _x; private _color = [_forEachIndex] call BIS_fnc_sideColor; diff --git a/addons/editor/functions/fnc_handleLoad.sqf b/addons/editor/functions/fnc_handleLoad.sqf index 079cc9d61..06373acc2 100644 --- a/addons/editor/functions/fnc_handleLoad.sqf +++ b/addons/editor/functions/fnc_handleLoad.sqf @@ -1,5 +1,5 @@ #include "script_component.hpp" -#include "\a3\ui_f\hpp\defineResinclDesign.inc" // can't put this in config due to undef error +#include "\a3\ui_f\hpp\defineResinclDesign.inc" // Can't put this in config due to undef error /* * Author: mharis001 * Handles initializing the Zeus Display. @@ -63,7 +63,7 @@ _display displayAddEventHandler ["KeyDown", {call FUNC(handleKeyDown)}]; } forEach IDCS_MODE_BUTTONS; // Need events to check if side buttons are hovered since changing the mode -// also triggers the button click event for the side buttons +// Also triggers the button click event for the side buttons { private _ctrl = _display displayCtrl _x; _ctrl ctrlAddEventHandler ["ButtonClick", {call FUNC(handleSideButtons)}]; @@ -103,8 +103,8 @@ _ctrlTreeRecent ctrlAddEventHandler ["TreeSelChanged", { params ["_ctrlTreeRecent", "_selectedPath"]; // Store data of selected item to allow for deleting the of crew of objects placed through the recent tree - // tvCurSel is unavailable once the selected item has been placed, the empty path check ensures that the - // data is not cleared since this event occurs before the object placed event + // TvCurSel is unavailable once the selected item has been placed, the empty path check ensures that the + // Data is not cleared since this event occurs before the object placed event if !(_selectedPath isEqualTo []) then { GVAR(recentTreeData) = _ctrlTreeRecent tvData _selectedPath; }; @@ -128,7 +128,7 @@ GVAR(iconsVisible) = true; [{ // For compatibility with Zeus Game Master missions, wait until the respawn placement phase is complete - // and the create trees have been refreshed after curator addons are changed + // And the create trees have been refreshed after curator addons are changed [{ params ["_display"]; diff --git a/addons/editor/initKeybinds.sqf b/addons/editor/initKeybinds.sqf index dc2b179e6..6579f56e5 100644 --- a/addons/editor/initKeybinds.sqf +++ b/addons/editor/initKeybinds.sqf @@ -13,7 +13,7 @@ [_x] call EFUNC(common,deployCountermeasures); } forEach SELECTED_OBJECTS; - true // handled + true // Handled }; }, {}, [DIK_C, [true, false, false]]] call CBA_fnc_addKeybind; // Default: SHIFT + C @@ -23,7 +23,7 @@ [_x] call EFUNC(common,ejectPassengers); } forEach SELECTED_OBJECTS; - true // handled, prevents vanilla eject + true // Handled, prevents vanilla eject }; }, {}, [DIK_G, [false, true, false]]] call CBA_fnc_addKeybind; // Default: CTRL + G @@ -34,7 +34,7 @@ playSound ["RscDisplayCurator_error01", true]; - true // handled, prevents vanilla copy + true // Handled, prevents vanilla copy }; }, {}, [DIK_C, [true, true, false]]] call CBA_fnc_addKeybind; // Default: CTRL + SHIFT + C @@ -45,7 +45,7 @@ playSound ["RscDisplayCurator_error01", true]; - true // handled, prevents vanilla paste + true // Handled, prevents vanilla paste }; }, {}, [DIK_V, [true, true, false]]] call CBA_fnc_addKeybind; // Default: CTRL + SHIFT + V @@ -65,7 +65,7 @@ }; } forEach SELECTED_OBJECTS; - true // handled + true // Handled }; }, {}, [DIK_X, [false, true, false]]] call CBA_fnc_addKeybind; // Default: CTRL + X diff --git a/addons/faction_filter/XEH_preStart.sqf b/addons/faction_filter/XEH_preStart.sqf index 2ed911ff1..d9fd6b635 100644 --- a/addons/faction_filter/XEH_preStart.sqf +++ b/addons/faction_filter/XEH_preStart.sqf @@ -12,7 +12,7 @@ private _cfgEditorCategories = configFile >> "CfgEditorCategories"; if (_x isKindOf "AllVehicles" && {!(_x isKindOf "Animal")}) then { private _config = _cfgVehicles >> _x; - // scopeCurator always has priority over scope, scope is only used if scopeCurator is not defined + // ScopeCurator always has priority over scope, scope is only used if scopeCurator is not defined if (getNumber (_config >> "scopeCurator") == 2 || {getNumber (_config >> "scope") == 2 && {!isNumber (_config >> "scopeCurator")}}) then { private _side = getNumber (_config >> "side"); diff --git a/addons/flashlight/initKeybinds.sqf b/addons/flashlight/initKeybinds.sqf index 73c18ff84..649874167 100644 --- a/addons/flashlight/initKeybinds.sqf +++ b/addons/flashlight/initKeybinds.sqf @@ -3,6 +3,6 @@ GVAR(state) = !GVAR(state); GVAR(state) call FUNC(toggle); - true // handled + true // Handled }; }, {}, [DIK_L, [false, false, false]]] call CBA_fnc_addKeybind; // Default: L diff --git a/addons/main/script_macros.hpp b/addons/main/script_macros.hpp index 3713d17d0..e854f7643 100644 --- a/addons/main/script_macros.hpp +++ b/addons/main/script_macros.hpp @@ -43,9 +43,9 @@ #define TYPE_WEAPON_SECONDARY 4 // Magazine types -#define TYPE_MAGAZINE_HANDGUN_AND_GL 16 // mainly +#define TYPE_MAGAZINE_HANDGUN_AND_GL 16 // Mainly #define TYPE_MAGAZINE_PRIMARY_AND_THROW 256 -#define TYPE_MAGAZINE_SECONDARY_AND_PUT 512 // mainly +#define TYPE_MAGAZINE_SECONDARY_AND_PUT 512 // Mainly #define TYPE_MAGAZINE_MISSILE 768 // More types @@ -60,11 +60,11 @@ #define TYPE_FLASHLIGHT 301 #define TYPE_BIPOD 302 #define TYPE_FIRST_AID_KIT 401 -#define TYPE_FINS 501 // not implemented -#define TYPE_BREATHING_BOMB 601 // not implemented +#define TYPE_FINS 501 // Not implemented +#define TYPE_BREATHING_BOMB 601 // Not implemented #define TYPE_NVG 602 #define TYPE_GOGGLE 603 -#define TYPE_SCUBA 604 // not implemented +#define TYPE_SCUBA 604 // Not implemented #define TYPE_HEADGEAR 605 #define TYPE_FACTOR 607 #define TYPE_RADIO 611 diff --git a/addons/modules/functions/fnc_compileFlags.sqf b/addons/modules/functions/fnc_compileFlags.sqf index b1d4f06e9..aa9b32b5f 100644 --- a/addons/modules/functions/fnc_compileFlags.sqf +++ b/addons/modules/functions/fnc_compileFlags.sqf @@ -26,7 +26,7 @@ private _sortHelper = []; private _flagTexture = toLower (_initText splitString "'""" param [1, ""]); if (_flagTexture != "") then { - // getForcedFlagTexture returns texture without leading slash + // GetForcedFlagTexture returns texture without leading slash if (_flagTexture select [0, 1] == "\") then { _flagTexture = _flagTexture select [1]; }; diff --git a/addons/modules/functions/fnc_gui_spawnReinforcements.sqf b/addons/modules/functions/fnc_gui_spawnReinforcements.sqf index b7e667750..0100bed0e 100644 --- a/addons/modules/functions/fnc_gui_spawnReinforcements.sqf +++ b/addons/modules/functions/fnc_gui_spawnReinforcements.sqf @@ -364,7 +364,7 @@ private _fnc_listKeyDown = { private _ctrlUnitCount = ctrlParent _ctrlUnitList displayCtrl IDC_SPAWNREINFORCEMENTS_UNIT_COUNT; _ctrlUnitCount ctrlSetText str lbSize _ctrlUnitList; - true // handled + true // Handled }; false diff --git a/addons/placement/functions/fnc_setupPreview.sqf b/addons/placement/functions/fnc_setupPreview.sqf index ba4327641..7907a8c2c 100644 --- a/addons/placement/functions/fnc_setupPreview.sqf +++ b/addons/placement/functions/fnc_setupPreview.sqf @@ -40,7 +40,7 @@ GVAR(object) enableSimulation false; GVAR(object) allowDamage false; // Calculate the vertical offset as the difference between the model center position -// and the land contact transformed position +// And the land contact transformed position private _offset = (getPosWorld GVAR(object) select 2) - (getPosASL GVAR(object) select 2); GVAR(object) attachTo [GVAR(helper), [0, 0, _offset]]; diff --git a/addons/pylons/functions/fnc_handleConfirm.sqf b/addons/pylons/functions/fnc_handleConfirm.sqf index 6207a7973..e9669f6e1 100644 --- a/addons/pylons/functions/fnc_handleConfirm.sqf +++ b/addons/pylons/functions/fnc_handleConfirm.sqf @@ -51,7 +51,7 @@ private _pylonLoadout = []; // Get all compatible default pylon weapons for the aircraft // These are added automatically if a compatible weapon for the magazine -// does not exist when using the setPylonLoadout command +// Does not exist when using the setPylonLoadout command private _pylonWeapons = []; { @@ -65,7 +65,7 @@ _pylonWeapons = _pylonWeapons arrayIntersect _pylonWeapons; // Remove default pylons weapons that will no longer be used // Prevents weapons with no magazines from showing up when cycling through weapons // This will also handle rare situations where a compatible weapon that is not a -// default pylon weapon was already present on the turret +// Default pylon weapon was already present on the turret { _x params ["_weaponsToKeep", "_turretPath"]; diff --git a/docs/development/coding_guidelines.md b/docs/development/coding_guidelines.md index 1736df70d..b98a25f37 100644 --- a/docs/development/coding_guidelines.md +++ b/docs/development/coding_guidelines.md @@ -302,12 +302,14 @@ call { Inline comments should use `//`. Usage of `/* */` is allowed for larger comment blocks. +All inline comments must start with a uppercase letter. + Example: ```sqf -//// Comment // < incorrect -// Comment // < correct -/* Comment */ // < correct +//// Comment // < Incorrect +// Comment // < Correct +/* Comment */ // < Correct ``` ### 5.4 Comments In Code @@ -319,7 +321,7 @@ Comments within the code shall be used when they are describing a complex and cr **Good:** ```sqf -// find the object with the most blood loss +// Find the object with the most blood loss _highestObj = objNull; _highestLoss = -1; { @@ -474,7 +476,7 @@ Declarations should be at the smallest feasible scope. ```sqf if (call FUNC(myCondition)) then { - private _areAllAboveTen = true; // <- smallest feasable scope + private _areAllAboveTen = true; // <- Smallest feasable scope { if (_x >= 10) then { @@ -491,7 +493,7 @@ if (call FUNC(myCondition)) then { **Bad:** ```sqf -private _areAllAboveTen = true; // <- this is bad, because it can be initialized in the if statement +private _areAllAboveTen = true; // <- This is bad, because it can be initialized in the if statement if (call FUNC(myCondition)) then { { if (_x >= 10) then { @@ -512,7 +514,7 @@ Private variables will not be introduced until they can be initialized with mean **Good:** ```sqf -private _myVariable = 0; // good because the value will be used +private _myVariable = 0; // Good because the value will be used { _x params ["_value", "_amount"]; if (_value > 0) then { @@ -760,7 +762,7 @@ while {_original < _weaponThreshold} do { ```sqf while {true} do { - // anything + // Anything }; ``` diff --git a/tools/comment_linter.py b/tools/comment_linter.py new file mode 100644 index 000000000..844746c22 --- /dev/null +++ b/tools/comment_linter.py @@ -0,0 +1,97 @@ +#!/usr/bin/env python3 + +# COMMENT VALIDATOR +# Author: CreepPork +# --------------------- +# Verifies all *.cpp, *.hpp and *.sqf files in the project. +# Checks if there are comments that start with a lowercase letter (all other symbols are ignored). + +import fnmatch +import os +import re +import sys + +EXCLUDE_PATHS = ['./include'] + + +def get_files(): + # Allow running from root directory and tools directory + root_dir = '..' + if os.path.exists('addons'): + root_dir = '.' + + code_files = [] + + for root, _, files in os.walk(root_dir): + for file in files: + filepath = os.path.join(root, file) + + # Ignore filepath if it is excluded from the search + if list(filter(filepath.startswith, EXCLUDE_PATHS)) != []: + continue + + if file.lower().endswith(('.cpp', '.hpp', '.sqf')): + code_files.append(filepath) + + code_files.sort() + + return code_files + + +def lint_file_for_comments(filepath: str): + invalid_comments = [] + + with open(filepath, 'r') as file_contents: + for (line_number, line) in enumerate(file_contents): + contents = line.strip() + + # If regex matched a comment + if re.search('^((?!http).)*//(\s|)*((?!http.*))[a-z]', contents): + # Lines start with 1, but as indexes start with 0, so we have to increment + invalid_comments.append((line_number + 1, contents)) + + return invalid_comments + + +def main(): + print('Validating code comments') + print('------------------------') + print() + + exit_code = 0 + validated_files = 0 + errors_found = 0 + + files = get_files() + + for filepath in files: + comments = lint_file_for_comments(filepath) + + validated_files += 1 + + # Skip files without errors + if len(comments) == 0: + continue + + errors_found += 1 + + for (line_number, comment) in comments: + if exit_code == 0: + exit_code = 1 + + print(f'ERROR: Comment must start with an uppercase letter.') + print(f' {filepath}:{line_number}') + print(f' {comment}') + print() + + print(f'Checked {validated_files} files, found errors in {errors_found}.') + print(( + 'Comment validation PASSED' if exit_code == 0 + else 'Comment validation FAILED' + )) + + return exit_code + + +if __name__ == '__main__': + sys.exit(main())