Package: gersemi
gersemiA formatter to make your CMake code the real treasure.InstallationYou can install gersemi from PyPI:pip3installgersemiUsageusage: gersemi [-c] [-i] [--diff] [--default-config] [--version] [-h] [-l INTEGER]
               [--unsafe] [-q] [--color] [--definitions src [src ...]]
               [--list-expansion {favour-inlining,favour-expansion}]
               [src ...]

A formatter to make your CMake code the real treasure.

positional arguments:
  src                   File or directory to format. If only `-` is provided, input is
                        taken from stdin instead.

modes:
  -c, --check           Check if files require reformatting. Return 0 when there's
                        nothing to reformat. Return 1 when some files would be
                        reformatted.
  -i, --in-place        Format files in-place.
  --diff                Show diff on stdout for each formatted file instead.
  --default-config      Generate default .gersemirc configuration file.
  --version             Show version.
  -h, --help            Show this help message and exit.

configuration:
  By default configuration is loaded from YAML formatted .gersemirc file if it's
  available. This file should be placed in one of the common parent directories of
  source files. Arguments from command line can be used to override parts of that
  configuration or supply them in absence of configuration file.

  -l INTEGER, --line-length INTEGER
                        Maximum line length in characters. [default: 80]
  --unsafe              Skip default sanity checks.
  -q, --quiet           Skip printing non-error messages to stderr.
  --color               If --diff is selected showed diff is colorized.
  --definitions src [src ...]
                        Files or directories containing custom command definitions
                        (functions or macros). If only - is provided custom definitions,
                        if there are any, are taken from stdin instead. Commands from
                        not deprecated CMake native modules don't have to be provided.
                        See: https://cmake.org/cmake/help/latest/manual/cmake-modules.7.html
  --list-expansion {favour-inlining,favour-expansion}
                        Switch controls how code is expanded into multiple lines when
                        it's not possible to keep it formatted in one line. With
                        "favour-inlining" the list of entities will be formatted in such
                        way that sublists might still be formatted into single line as
                        long as it's possible. With "favour-expansion" the list of
                        entities will be formatted in such way that sublists will be
                        completely expanded once expansion becomes necessary at all.
                        [default: favour-inlining]
  -w INTEGER, --workers INTEGER
                        Number of workers used to format multiple files in parallel.
                        [default: number of CPUs on this system]pre-commithookYou can use gersemi with a pre-commit hook by adding the following to.pre-commit-config.yamlof your repository:repos:-repo:https://github.com/BlankSpruce/gersemirev:0.11.0hooks:-id:gersemiUpdaterevto relevant version used in your repository. For more details refer tohttps://pre-commit.com/#using-the-latest-version-for-a-repositoryFormattingThe key goal is for the tool to "just work" and to have as little configuration as possible so that you don't have to worry about fine-tuning formatter to your needs - as long as you embrace thegersemistyle of formatting, similarly asblackorgofmtdo their job. Currently only line length can be changed with80as default value. Currently the basic assumption is that code to format is valid CMake language code -gersemimight be able to format some particular cases of invalid code but it's not guaranteed and it shouldn't be relied upon. Moreover only commands from CMake 3.0 onwards are supported and will be formatted properly - for instanceexec_programhas been deprecated since CMake 3.0so it won't be formatted. Be warned though it's not production ready so the changes to code might be destructive and you should always have a backup (version control helps a lot).StyleDefault stylefavour-inlininggersemiwill try to format the code in a way that respects set character limit for single line and only break line whenever necessary.Example:cmake_minimum_required(VERSION3.18FATAL_ERROR)project(exampleCXX)message(STATUS"This is example project")message(STATUS"Here is yet another but much much longer message that should be displayed")# project versionset(VERSION_MAJOR0)set(VERSION_MINOR1)set(VERSION_PATCH0)add_compile_options(-Wall-Wpedantic-fsanitize=address-fconcepts-fsomething-else)if(NOT${SOME_OPTION})add_compile_options(-Werror)endif()# foobar libraryadd_library(foobar)add_library(example::foobarALIASfoobar)target_sources(foobarPUBLICinclude/some_subdirectory/header.hppinclude/another_subdirectory/header.hppPRIVATEsrc/some_subdirectory/src1.cppsrc/some_subdirectory/src1.cppsrc/another_subdirectory/src1.cppsrc/another_subdirectory/src2.cppsrc/another_subdirectory/src3.cpp)target_include_directories(foobarINTERFACE$<BUILD_INTERFACE:${CMAKE_CURRENT_SOURCE_DIR}/include>$<INSTALL_INTERFACE:include>)target_link_libraries(foobarPUBLICexample::dependency_oneexample::dependency_twoPRIVATEexample::some_utilexternal::some_libexternal::another_libBoost::Boost)include(GNUInstallDirs)set(CMAKE_ARCHIVE_OUTPUT_DIRECTORY${CMAKE_BINARY_DIR}/${CMAKE_INSTALL_LIBDIR})set(CMAKE_LIBRARY_OUTPUT_DIRECTORY${CMAKE_BINARY_DIR}/${CMAKE_INSTALL_LIBDIR})set(CMAKE_RUNTIME_OUTPUT_DIRECTORY${CMAKE_BINARY_DIR}/${CMAKE_INSTALL_BINDIR})# example executableadd_executable(appmain.cpp)target_link_libraries(appPRIVATEexample::foobarBoost::Boost)# testsinclude(CTest)include(GTest)enable_testing()add_subdirectory(tests)# some helper function - see more details in "Let's make a deal" sectionfunction(add_test_executable)set(OPTIONSQUIETVERBOSESOME_PARTICULARLY_LONG_KEYWORD_THAT_ENABLES_SOMETHING)set(ONE_VALUE_ARGSNAMETESTED_TARGET)set(MULTI_VALUE_ARGSSOURCESDEPENDENCIES)cmake_parse_arguments(THIS_FUNCTION_PREFIX${OPTIONS}${ONE_VALUE_ARGS}${MULTI_VALUE_ARGS})# rest of the functionendfunction()add_test_executable(NAMEfoobar_testsTESTED_TARGETfoobarSOURCESsome_test1.cppsome_test2.cppsome_test3.cppsome_test4.cppsome_test5.cppQUIETDEPENDENCIESgoogletest::googletest)add_custom_command(OUTPUT${SOMETHING_TO_OUTPUT}COMMAND${CMAKE_COMMAND}-EcatfoobarCOMMANDcmake-EechofoobarCOMMANDcmake-Eecho"something quite a bit                           longer"WORKING_DIRECTORY${CMAKE_CURRENT_BINARY_DIR}/somethingDEPENDS${CMAKE_CURRENT_SOURCE_DIR}/something${CMAKE_CURRENT_SOURCE_DIR}/something_elseCOMMENT"example custom command")Alternative stylefavour-expansionIn this style lines are broken in one of these cases:there is at least one multi-value argument present a single command invocation, either keyworded one likePUBLICintarget_link_librariesor standalone one like list of files inadd_library, which has more than one valuethere are more than one multi-value arguments present in the command invocation liketarget_link_librarieswithPUBLICandPRIVATEarguments.character limit for single line is reachedOne-value arguments (likeNAMEinadd_test) will be inlined unless that'd violate character limit. Structure or control flow commands (if,while,function,foreachetc.) are exempted from these special rules and follow the same formatting asfavour-inlining. This style is more merge orgit blamefriendly because usually multi-value arguments are changed one element at a time and with this style such change will be visible as one line of code per element.Example:cmake_minimum_required(VERSION3.18FATAL_ERROR)project(exampleCXX)message(STATUS"This is example project")message(STATUS"Here is yet another but much much longer message that should be displayed")# project versionset(VERSION_MAJOR0)set(VERSION_MINOR1)set(VERSION_PATCH0)add_compile_options(-Wall-Wpedantic-fsanitize=address-fconcepts-fsomething-else)if(NOT${SOME_OPTION})add_compile_options(-Werror)endif()# foobar libraryadd_library(foobar)add_library(example::foobarALIASfoobar)target_sources(foobarPUBLICinclude/some_subdirectory/header.hppinclude/another_subdirectory/header.hppPRIVATEsrc/some_subdirectory/src1.cppsrc/some_subdirectory/src1.cppsrc/another_subdirectory/src1.cppsrc/another_subdirectory/src2.cppsrc/another_subdirectory/src3.cpp)target_include_directories(foobarINTERFACE$<BUILD_INTERFACE:${CMAKE_CURRENT_SOURCE_DIR}/include>$<INSTALL_INTERFACE:include>)target_link_libraries(foobarPUBLICexample::dependency_oneexample::dependency_twoPRIVATEexample::some_utilexternal::some_libexternal::another_libBoost::Boost)include(GNUInstallDirs)set(CMAKE_ARCHIVE_OUTPUT_DIRECTORY${CMAKE_BINARY_DIR}/${CMAKE_INSTALL_LIBDIR})set(CMAKE_LIBRARY_OUTPUT_DIRECTORY${CMAKE_BINARY_DIR}/${CMAKE_INSTALL_LIBDIR})set(CMAKE_RUNTIME_OUTPUT_DIRECTORY${CMAKE_BINARY_DIR}/${CMAKE_INSTALL_BINDIR})# example executableadd_executable(appmain.cpp)target_link_libraries(appPRIVATEexample::foobarBoost::Boost)# testsinclude(CTest)include(GTest)enable_testing()add_subdirectory(tests)# some helper function - see more details in "Let's make a deal" sectionfunction(add_test_executable)set(OPTIONSQUIETVERBOSESOME_PARTICULARLY_LONG_KEYWORD_THAT_ENABLES_SOMETHING)set(ONE_VALUE_ARGSNAMETESTED_TARGET)set(MULTI_VALUE_ARGSSOURCESDEPENDENCIES)cmake_parse_arguments(THIS_FUNCTION_PREFIX${OPTIONS}${ONE_VALUE_ARGS}${MULTI_VALUE_ARGS})# rest of the functionendfunction()add_test_executable(NAMEfoobar_testsTESTED_TARGETfoobarSOURCESsome_test1.cppsome_test2.cppsome_test3.cppsome_test4.cppsome_test5.cppQUIETDEPENDENCIESgoogletest::googletest)add_custom_command(OUTPUT${SOMETHING_TO_OUTPUT}COMMAND${CMAKE_COMMAND}-EcatfoobarCOMMANDcmake-EechofoobarCOMMANDcmake-Eecho"something quite a bit                           longer"WORKING_DIRECTORY${CMAKE_CURRENT_BINARY_DIR}/somethingDEPENDS${CMAKE_CURRENT_SOURCE_DIR}/something${CMAKE_CURRENT_SOURCE_DIR}/something_elseCOMMENT"example custom command")Let's make a dealIt's possible to provide reasonable formatting for custom commands. However on language level there are no hints available about supported keywords for given command sogersemihas to generate specialized formatter. To do that custom command definition is necessary which should be provided with--definitions. There are limitations though since it'd probably require full-blown CMake language interpreter to do it in every case so let's make a deal: if your custom command definition (function or macro) usescmake_parse_argumentsand does it in obvious manner such specialized formatter will be generated. For instance this definition is okay (you can find other examples intests/custom_command_formatting/):function(SEVEN_SAMURAIsomestandalonearguments)set(optionsKAMBEIKATSUSHIRO)set(oneValueArgsGOROBEIHEIHACHIKYUZO)set(multiValueArgsSHICHIROJIKIKUCHIYO)cmake_parse_arguments(THIS_FUNCTION_PREFIX"${options}""${oneValueArgs}""${multiValueArgs}"${ARGN})# rest of the function definition...endfunction()With this definition available it's possible to format code like so:seven_samurai(threestandaloneargumentsKAMBEIKATSUSHIROGOROBEIfooHEIHACHIbarKYUZObazSHICHIROJIfoobarbazKIKUCHIYObarbazfoo)Otherwisegersemiwill fallback to only fixing indentation and preserving original formatting. If you find these limitations too strict let me know about your case.If your definition should be ignored for purposes of generating specialized formatter you can use# gersemi: ignoreat the beginning of the custom command:function(harry_potter_and_the_philosophers_stonesomestandalonearguments)# gersemi: ignoreset(optionsHARRY)set(oneValueArgsHERMIONE)set(multiValueArgsRON)cmake_parse_arguments(THIS_FUNCTION_PREFIX"${options}""${oneValueArgs}""${multiValueArgs}"${ARGN})# rest of the definition...endfunction()# no reformattingharry_potter_and_the_philosophers_stone(HARRYHERMIONEfooRONfoobarbaz)It should be still preferred simply to not provide that definition instead.How to disable reformattingGersemi can be disallowed to format block of code using pair of comments# gersemi: off/# gersemi: on. Example:the_hobbit(BURGLAR"Bilbo Baggins"WIZARDGandalfDWARVES"Thorin Oakenshield"FiliKiliBalinDwalinOinGloinDoriNoriOriBifurBofurBombur)# gersemi: offthe_fellowship_of_the_ring(RING_BEARERFrodoGARDENERSamwiseMerryPippinAragonBoromirGimliLegolasGandalf)# gersemi: onPair of comments should be in the same scope, so the following is not supported:# gersemi: offthe_godfather()function(how_to_make_a_successful_movieargs)step_one_have_a_good_scenario()# gersemi: onstep_two_make_the_movie()endfunction()ContributingBug or style inconsitencies reports are always welcomed. In case of style enhancement or feature proposals consider providing rationale (and maybe some example) having in mind the deliberate choice mentioned above. As long as it's meant to improve something go for it and be prepared to defend your point.Running testsEntire test suite can be run with just:toxSelecting functional tests can be done like so:tox -e tests -- -k <test_pattern>If you are familiar withpytestthen you can pass relevant arguments after--.