2016-03-19 09:59:06 +01:00
|
|
|
# This module is designed to allow the Awesome documentation examples to be
|
|
|
|
# tested.
|
|
|
|
#
|
|
|
|
# It shims enough of the Awesome C API to allow code to be executed without an
|
|
|
|
# actual X server or running Awesome process. These tests are not genuine
|
|
|
|
# integration tests, but they are the next best thing.
|
|
|
|
#
|
2016-10-01 17:22:30 +02:00
|
|
|
# As secondary goals, this module also generates images of the test result
|
|
|
|
# where relevant. Those images are used by the documentation and help the
|
|
|
|
# developers track user interface regressions and glitches. Finally, it also
|
|
|
|
# helps to find broken code.
|
2017-07-17 10:11:52 +02:00
|
|
|
|
|
|
|
if(NOT DEFINED PROJECT_NAME)
|
|
|
|
project(awesome-tests-examples NONE)
|
|
|
|
endif()
|
|
|
|
|
2016-03-19 09:59:06 +01:00
|
|
|
cmake_minimum_required(VERSION 3.0.0)
|
|
|
|
|
2017-08-31 21:21:19 +02:00
|
|
|
# Get and update the LUA_PATH so the scripts can be executed without awesome.
|
|
|
|
execute_process(COMMAND lua -e "p = package.path:gsub(';', '\\\\;'); io.stdout:write(p)"
|
|
|
|
OUTPUT_VARIABLE "LUA_PATH_")
|
2016-03-19 09:59:06 +01:00
|
|
|
|
2017-01-03 14:29:10 +01:00
|
|
|
# Make sure the system can be called from the test directory
|
|
|
|
if(NOT SOURCE_DIR AND ${CMAKE_CURRENT_SOURCE_DIR} MATCHES "/tests/examples")
|
|
|
|
get_filename_component(TOP_SOURCE_DIR
|
|
|
|
"${CMAKE_CURRENT_SOURCE_DIR}/../.." ABSOLUTE)
|
|
|
|
# Used by .luacov.
|
|
|
|
set(ENV{SOURCE_DIRECTORY} ${TOP_SOURCE_DIR})
|
|
|
|
else()
|
|
|
|
set(TOP_SOURCE_DIR ${CMAKE_SOURCE_DIR})
|
|
|
|
endif()
|
|
|
|
|
2017-08-16 00:48:42 +02:00
|
|
|
if (DO_COVERAGE)
|
2017-01-03 14:29:10 +01:00
|
|
|
execute_process(
|
|
|
|
COMMAND lua -e "require('luacov.runner')('${TOP_SOURCE_DIR}/.luacov')"
|
|
|
|
RESULT_VARIABLE TEST_RESULT
|
|
|
|
ERROR_VARIABLE TEST_ERROR
|
|
|
|
ERROR_STRIP_TRAILING_WHITESPACE)
|
|
|
|
if (TEST_RESULT OR NOT TEST_ERROR STREQUAL "")
|
|
|
|
message(${TEST_ERROR})
|
|
|
|
message(FATAL_ERROR "Failed to run luacov.runner.")
|
|
|
|
endif()
|
2017-01-04 20:26:46 +01:00
|
|
|
set(LUA_COV_RUNNER lua "-erequire('luacov.runner')('${TOP_SOURCE_DIR}/.luacov')")
|
|
|
|
else()
|
|
|
|
set(LUA_COV_RUNNER lua)
|
2017-01-03 14:29:10 +01:00
|
|
|
endif()
|
|
|
|
|
2016-03-19 09:59:06 +01:00
|
|
|
# Add the main awesome lua libraries.
|
2017-08-31 21:21:19 +02:00
|
|
|
set(LUA_PATH_ "\
|
|
|
|
${TOP_SOURCE_DIR}/lib/?.lua\\;\
|
|
|
|
${TOP_SOURCE_DIR}/lib/?/init.lua\\;\
|
|
|
|
${TOP_SOURCE_DIR}/lib/?\\;\
|
|
|
|
${TOP_SOURCE_DIR}/themes/?.lua\\;\
|
|
|
|
${TOP_SOURCE_DIR}/themes/?\\;\
|
|
|
|
${LUA_PATH_}")
|
2016-03-19 09:59:06 +01:00
|
|
|
|
|
|
|
# Add the C API shims.
|
2017-08-31 21:21:19 +02:00
|
|
|
set(LUA_PATH_ "\
|
|
|
|
${TOP_SOURCE_DIR}/tests/examples/shims/?.lua\\;\
|
|
|
|
${TOP_SOURCE_DIR}/tests/examples/shims/?/init.lua\\;\
|
|
|
|
${TOP_SOURCE_DIR}/tests/examples/shims/?\\;\
|
|
|
|
${LUA_PATH_}")
|
2016-03-19 09:59:06 +01:00
|
|
|
|
2017-08-31 21:21:19 +02:00
|
|
|
set(LUA_COV_RUNNER env -u LUA_PATH_5_1 -u LUA_PATH_5_2 -u LUA_PATH_5_3 "LUA_PATH=${LUA_PATH_}" ${LUA_COV_RUNNER})
|
2016-09-20 19:36:23 +02:00
|
|
|
|
2017-07-02 16:55:21 +02:00
|
|
|
# Done in 3 variables to avoid CMake from implicitly converting into a list.
|
|
|
|
set(ENV{LUA_PATH} "${LUA_PATH3_}${LUA_PATH2_}${LUA_PATH_}")
|
|
|
|
|
|
|
|
set(ENV{AWESOME_THEMES_PATH} "${TOP_SOURCE_DIR}/themes/")
|
|
|
|
|
2016-10-01 17:22:30 +02:00
|
|
|
# The documentation images directory.
|
2016-03-19 09:59:06 +01:00
|
|
|
set(IMAGE_DIR "${CMAKE_BINARY_DIR}/doc/images")
|
|
|
|
file(MAKE_DIRECTORY "${IMAGE_DIR}")
|
|
|
|
|
|
|
|
# Escape potentially multiline strings to be part of the API doc.
|
|
|
|
# * add "--" in front of each lines
|
|
|
|
# * add a custom prefix in front of each lines
|
|
|
|
# * drop empty lines
|
|
|
|
# * convert " " lines into empty lines
|
|
|
|
# * drop lines ending with "--DOC_SOMETHING", they are handled elsewhere
|
|
|
|
function(escape_string variable content escaped_content line_prefix)
|
2016-10-01 17:22:30 +02:00
|
|
|
# If DOC_HIDE_ALL is present, do nothing.
|
2016-03-19 09:59:06 +01:00
|
|
|
if(variable MATCHES "--DOC_HIDE_ALL")
|
|
|
|
return()
|
|
|
|
endif()
|
|
|
|
string(REGEX REPLACE "\n" ";" var_lines "${variable}")
|
|
|
|
|
|
|
|
set(tmp_output ${content})
|
|
|
|
foreach (LINE ${var_lines})
|
2016-12-31 13:58:35 +01:00
|
|
|
if(NOT LINE MATCHES "^.*--DOC_[A-Z]+")
|
2017-02-11 21:35:08 +01:00
|
|
|
set(tmp_output ${tmp_output}\n${DOC_LINE_PREFIX}${line_prefix}${LINE})
|
2016-03-19 09:59:06 +01:00
|
|
|
endif()
|
|
|
|
endforeach()
|
|
|
|
|
|
|
|
set(${escaped_content} ${tmp_output} PARENT_SCOPE)
|
|
|
|
endfunction()
|
|
|
|
|
2016-10-01 17:22:30 +02:00
|
|
|
# Extract lines with the --DOC_HEADER marker.
|
2016-03-19 09:59:06 +01:00
|
|
|
function(extract_header variable pre_output post_output)
|
|
|
|
string(REGEX REPLACE "\n" ";" var_lines "${variable}")
|
|
|
|
|
|
|
|
set(IS_POST 0)
|
|
|
|
|
|
|
|
foreach (LINE ${var_lines})
|
|
|
|
# This function doesn't escape the lines, so make sure they are
|
|
|
|
# already comments.
|
|
|
|
if(LINE MATCHES "^--.*--DOC_HEADER")
|
|
|
|
# Remove the header tag
|
|
|
|
string(REGEX REPLACE "[ ]*--DOC_HEADER" "" LINE "${LINE}")
|
|
|
|
|
2016-10-01 17:22:30 +02:00
|
|
|
# ldoc is picky about what happens after the first --@, so split
|
|
|
|
# the output on that.
|
2016-03-19 09:59:06 +01:00
|
|
|
if (NOT IS_POST AND LINE MATCHES "^--[ ]*@")
|
|
|
|
set(IS_POST 1)
|
|
|
|
endif()
|
|
|
|
|
|
|
|
if (IS_POST)
|
|
|
|
set(tmp_post_output ${tmp_post_output}\n${line_prefix}${LINE})
|
|
|
|
else()
|
|
|
|
set(tmp_pre_output ${tmp_pre_output}\n${line_prefix}${LINE})
|
|
|
|
endif()
|
|
|
|
endif()
|
|
|
|
endforeach()
|
|
|
|
|
2017-02-11 21:35:08 +01:00
|
|
|
set(${post_output} "${tmp_post_output}\n${DOC_LINE_PREFIX}" PARENT_SCOPE)
|
|
|
|
set(${pre_output} "${tmp_pre_output}\n${DOC_LINE_PREFIX}" PARENT_SCOPE)
|
2016-03-19 09:59:06 +01:00
|
|
|
endfunction()
|
|
|
|
|
|
|
|
# Read a code file and convert it to ldoc usage example.
|
|
|
|
# * add "--" in front of each lines
|
|
|
|
# * drop empty lines
|
|
|
|
# * convert " " lines into empty lines
|
|
|
|
# * drop lines ending with "--DOC_HIDE"
|
|
|
|
function(escape_code path escaped_content pre_header post_header)
|
|
|
|
file(READ ${path} path)
|
|
|
|
|
2017-01-01 21:16:07 +01:00
|
|
|
escape_string("${path}\n" "" escaped_code " ")
|
2016-03-19 09:59:06 +01:00
|
|
|
|
|
|
|
extract_header("${path}" example_pre_header example_post_header)
|
|
|
|
|
|
|
|
set(${escaped_content} ${escaped_code} PARENT_SCOPE)
|
|
|
|
set(${pre_header} ${example_pre_header} PARENT_SCOPE)
|
|
|
|
set(${post_header} ${example_post_header} PARENT_SCOPE)
|
|
|
|
endfunction()
|
|
|
|
|
2016-06-04 14:35:46 +02:00
|
|
|
# Find the template.lua that is closest to the given file. For example, if a
|
|
|
|
# template.lua is present in the same directory, its path will be returned. If
|
|
|
|
# one is present in the parent directory, that path is returned etc.
|
|
|
|
function(find_template result_variable file)
|
|
|
|
get_filename_component(path "${file}" DIRECTORY)
|
|
|
|
|
|
|
|
while(NOT EXISTS "${path}/template.lua")
|
|
|
|
set(last_path "${path}")
|
|
|
|
get_filename_component(path "${path}" DIRECTORY)
|
|
|
|
if(last_path STREQUAL path)
|
|
|
|
message(FATAL_ERROR "Failed to find template.lua for ${file}")
|
|
|
|
endif()
|
|
|
|
endwhile()
|
2016-03-19 09:59:06 +01:00
|
|
|
|
2016-06-04 14:35:46 +02:00
|
|
|
set(${result_variable} "${path}/template.lua" PARENT_SCOPE)
|
|
|
|
endfunction()
|
|
|
|
|
2016-10-01 17:22:30 +02:00
|
|
|
# Get the namespace of a file.
|
2016-06-04 14:50:41 +02:00
|
|
|
function(get_namespace result_variable file)
|
|
|
|
get_filename_component(path "${file}" DIRECTORY)
|
2017-01-03 14:29:10 +01:00
|
|
|
string(LENGTH "${TOP_SOURCE_DIR}/tests/examples" prefix_length)
|
2016-06-04 14:50:41 +02:00
|
|
|
string(REPLACE "/" "_" namespace "${path}")
|
|
|
|
string(SUBSTRING "${namespace}" "${prefix_length}" -1 namespace)
|
|
|
|
|
|
|
|
set(${result_variable} "${namespace}" PARENT_SCOPE)
|
|
|
|
endfunction()
|
|
|
|
|
2016-10-01 17:22:30 +02:00
|
|
|
# Execute a Lua file.
|
2016-06-04 14:35:46 +02:00
|
|
|
function(run_test test_path namespace escaped_content)
|
|
|
|
find_template(template "${test_path}")
|
2016-03-19 09:59:06 +01:00
|
|
|
|
2017-02-11 21:35:08 +01:00
|
|
|
file(READ ${test_path} tmp_content)
|
|
|
|
|
|
|
|
# Add "--" in front of each line. This is required for method doc, but not
|
|
|
|
# for documentation pages
|
|
|
|
if(NOT tmp_content MATCHES "--DOC_NO_DASH")
|
|
|
|
set(DOC_LINE_PREFIX "--")
|
|
|
|
endif()
|
|
|
|
|
|
|
|
# Do not use the @usage tag, but 4 spaces.
|
|
|
|
if(NOT tmp_content MATCHES "--DOC_NO_USAGE")
|
|
|
|
set(DOC_BLOCK_PREFIX "@usage")
|
|
|
|
endif()
|
|
|
|
|
2016-10-01 17:22:30 +02:00
|
|
|
# Get the file name without the extension.
|
2016-03-19 09:59:06 +01:00
|
|
|
get_filename_component(${test_path} TEST_FILE_NAME NAME)
|
2016-04-01 05:42:05 +02:00
|
|
|
set(IMAGE_PATH "${IMAGE_DIR}/AUTOGEN${namespace}_${TEST_FILE_NAME}")
|
2016-03-19 09:59:06 +01:00
|
|
|
|
2016-10-01 17:22:30 +02:00
|
|
|
# Execute the script, leave the image extension decision to the test.
|
2016-03-19 09:59:06 +01:00
|
|
|
# SVG is preferred, but PNG is better suited for some tests, like bitmap
|
|
|
|
# patterns.
|
2017-08-31 21:21:19 +02:00
|
|
|
file(RELATIVE_PATH rel_test_path "${TOP_SOURCE_DIR}" "${test_path}")
|
|
|
|
message(STATUS "Running ${rel_test_path}…")
|
|
|
|
|
2016-03-19 09:59:06 +01:00
|
|
|
execute_process(
|
2017-01-04 20:26:46 +01:00
|
|
|
COMMAND ${LUA_COV_RUNNER} ${template} ${test_path} ${IMAGE_PATH}
|
|
|
|
RESULT_VARIABLE TEST_RESULT
|
2016-03-19 09:59:06 +01:00
|
|
|
OUTPUT_VARIABLE TEST_OUTPUT
|
|
|
|
ERROR_VARIABLE TEST_ERROR
|
|
|
|
)
|
2017-01-04 20:26:46 +01:00
|
|
|
if (TEST_RESULT OR NOT TEST_ERROR STREQUAL "")
|
|
|
|
message("Result: ${TEST_RESULT}")
|
|
|
|
if (NOT TEST_OUTPUT STREQUAL "")
|
|
|
|
message("Output: ${TEST_OUTPUT}")
|
|
|
|
endif()
|
|
|
|
if (NOT TEST_ERROR STREQUAL "")
|
|
|
|
message("Error: ${TEST_ERROR}")
|
|
|
|
endif()
|
2016-08-20 18:59:28 +02:00
|
|
|
if (STRICT_TESTS)
|
2016-10-01 17:24:44 +02:00
|
|
|
message(FATAL_ERROR ${test_path} " An example test failed, aborting.")
|
2016-08-20 18:59:28 +02:00
|
|
|
endif()
|
2016-03-19 09:59:06 +01:00
|
|
|
endif()
|
|
|
|
|
|
|
|
# Read the code and turn it into an usage example.
|
|
|
|
escape_code(${test_path} TEST_CODE TEST_PRE_HEADER TEST_POST_HEADER)
|
|
|
|
|
|
|
|
# Build the documentation.
|
|
|
|
set(TEST_DOC_CONTENT "${TEST_PRE_HEADER}")
|
|
|
|
|
|
|
|
# If the image has been created, then add it.
|
|
|
|
if(EXISTS "${IMAGE_PATH}.svg")
|
2017-08-31 21:21:19 +02:00
|
|
|
set(OUTPUT_IMAGE_PATH "${IMAGE_PATH}.svg")
|
2016-03-19 09:59:06 +01:00
|
|
|
escape_string(
|
2016-04-01 05:42:05 +02:00
|
|
|
"![Usage example](../images/AUTOGEN${namespace}_${TEST_FILE_NAME}.svg)\n"
|
2016-03-19 09:59:06 +01:00
|
|
|
"${TEST_DOC_CONTENT}" TEST_DOC_CONTENT ""
|
|
|
|
)
|
|
|
|
elseif(EXISTS "${IMAGE_PATH}.png")
|
2017-08-31 21:21:19 +02:00
|
|
|
set(OUTPUT_IMAGE_PATH "${IMAGE_PATH}.png")
|
2016-03-19 09:59:06 +01:00
|
|
|
escape_string(
|
2016-04-01 05:42:05 +02:00
|
|
|
"![Usage example](../images/AUTOGEN${namespace}_${TEST_FILE_NAME}.png)\n"
|
2016-03-19 09:59:06 +01:00
|
|
|
"${TEST_DOC_CONTENT}" TEST_DOC_CONTENT ""
|
|
|
|
)
|
2017-08-31 21:21:19 +02:00
|
|
|
else()
|
|
|
|
set(OUTPUT_IMAGE_PATH "")
|
2016-03-19 09:59:06 +01:00
|
|
|
endif()
|
|
|
|
|
|
|
|
# If there is an output, assume it is relevant and add it to the
|
|
|
|
# documentation under the image.
|
|
|
|
if(NOT ${TEST_OUTPUT} STREQUAL "")
|
|
|
|
set(TEST_DOC_CONTENT
|
2017-02-11 21:35:08 +01:00
|
|
|
"${TEST_DOC_CONTENT}\n${DOC_LINE_PREFIX}\n${DOC_LINE_PREFIX}**Usage example output**:\n${DOC_LINE_PREFIX}"
|
2016-03-19 09:59:06 +01:00
|
|
|
)
|
|
|
|
|
2016-10-01 17:22:30 +02:00
|
|
|
# Markdown requires an empty line before and after, and 4 spaces.
|
2016-03-19 09:59:06 +01:00
|
|
|
escape_string(
|
|
|
|
"\n${TEST_OUTPUT}"
|
|
|
|
"${TEST_DOC_CONTENT}" TEST_DOC_CONTENT " "
|
|
|
|
)
|
2017-02-11 21:35:08 +01:00
|
|
|
set(TEST_DOC_CONTENT "${TEST_DOC_CONTENT}\n${DOC_LINE_PREFIX}")
|
2016-03-19 09:59:06 +01:00
|
|
|
endif()
|
|
|
|
|
|
|
|
# If there is some @* content, append it.
|
|
|
|
set(TEST_DOC_CONTENT "${TEST_DOC_CONTENT}${TEST_POST_HEADER}")
|
|
|
|
|
|
|
|
# Only add it if there is something to display.
|
2017-02-11 21:35:08 +01:00
|
|
|
if(NOT ${TEST_CODE} STREQUAL "\n${DOC_LINE_PREFIX}")
|
2016-03-19 09:59:06 +01:00
|
|
|
escape_string(
|
2017-02-11 21:35:08 +01:00
|
|
|
" ${DOC_BLOCK_PREFIX}"
|
2016-03-19 09:59:06 +01:00
|
|
|
"${TEST_DOC_CONTENT}" TEST_DOC_CONTENT ""
|
|
|
|
)
|
|
|
|
set(TEST_DOC_CONTENT "${TEST_DOC_CONTENT}${TEST_CODE}")
|
|
|
|
endif()
|
|
|
|
|
2017-08-31 21:21:19 +02:00
|
|
|
if(NOT ${OUTPUT_IMAGE_PATH} STREQUAL "")
|
|
|
|
file(RELATIVE_PATH rel_template "${TOP_SOURCE_DIR}" "${template}")
|
|
|
|
file(RELATIVE_PATH rel_output "${TOP_SOURCE_DIR}" "${OUTPUT_IMAGE_PATH}")
|
|
|
|
add_custom_command(
|
|
|
|
COMMAND ${LUA_COV_RUNNER} ${template} ${test_path} ${IMAGE_PATH}
|
|
|
|
COMMENT "Generating ${rel_output} (via ${rel_test_path} (${rel_template}))"
|
|
|
|
DEPENDS ${template} ${test_path}
|
|
|
|
OUTPUT ${OUTPUT_IMAGE_PATH}
|
|
|
|
VERBATIM)
|
|
|
|
set(EXAMPLE_DOC_GENERATED_FILES ${OUTPUT_IMAGE_PATH} ${EXAMPLE_DOC_GENERATED_FILES}
|
|
|
|
PARENT_SCOPE)
|
|
|
|
endif()
|
|
|
|
|
2016-10-01 17:22:30 +02:00
|
|
|
# Export the outout to the parent scope.
|
2016-03-19 09:59:06 +01:00
|
|
|
set(${escaped_content} "${TEST_DOC_CONTENT}" PARENT_SCOPE)
|
|
|
|
endfunction()
|
|
|
|
|
2017-08-31 21:21:19 +02:00
|
|
|
# Find all test files, and run them (generating custom commands for updating them).
|
2016-06-04 15:01:30 +02:00
|
|
|
file(GLOB_RECURSE test_files LIST_DIRECTORIES false
|
2017-01-03 14:29:10 +01:00
|
|
|
"${TOP_SOURCE_DIR}/tests/examples/*.lua")
|
2016-03-19 09:59:06 +01:00
|
|
|
|
2017-08-31 21:21:19 +02:00
|
|
|
# TODO: check for changed files (timestamp)?!
|
|
|
|
if(NOT "${test_files}" STREQUAL "${EXAMPLE_DOC_SOURCE_FILES}")
|
|
|
|
set(EXAMPLE_DOC_SOURCE_FILES "${test_files}" CACHE INTERNAL "Source files used to generate doc files.")
|
2016-06-04 15:14:56 +02:00
|
|
|
|
2017-08-31 21:21:19 +02:00
|
|
|
# Find and run all test files.
|
|
|
|
foreach(file ${test_files})
|
|
|
|
if ((NOT "${file}" MATCHES ".*/shims/.*")
|
|
|
|
AND (NOT "${file}" MATCHES ".*/template.lua"))
|
2016-03-19 09:59:06 +01:00
|
|
|
|
2017-08-31 21:21:19 +02:00
|
|
|
# Get the file name without the extension.
|
|
|
|
get_filename_component(TEST_FILE_NAME ${file} NAME_WE)
|
2016-03-19 09:59:06 +01:00
|
|
|
|
2017-08-31 21:21:19 +02:00
|
|
|
get_namespace(namespace "${file}")
|
2016-03-19 09:59:06 +01:00
|
|
|
|
2017-08-31 21:21:19 +02:00
|
|
|
run_test("${file}" "${namespace}" ESCAPED_CODE_EXAMPLE)
|
2016-03-19 09:59:06 +01:00
|
|
|
|
2017-08-31 21:21:19 +02:00
|
|
|
# Set the test name.
|
|
|
|
set(TEST_NAME DOC${namespace}_${TEST_FILE_NAME}_EXAMPLE)
|
|
|
|
|
|
|
|
# Anything called @DOC_`namespace`_EXAMPLE@
|
|
|
|
# in the Lua or C sources will be replaced by the content of that
|
|
|
|
# variable during the pre-processing.
|
|
|
|
# While at it, replace \" created by CMake by ',
|
|
|
|
# " wont work in <code>.
|
|
|
|
string(REPLACE "\"" "'" ${TEST_NAME} ${ESCAPED_CODE_EXAMPLE})
|
|
|
|
endif()
|
|
|
|
endforeach()
|
|
|
|
|
|
|
|
set(EXAMPLE_DOC_GENERATED_FILES ${EXAMPLE_DOC_GENERATED_FILES}
|
|
|
|
CACHE INTERNAL "List of generated files for example docs.")
|
|
|
|
endif()
|
|
|
|
|
|
|
|
add_custom_target(generate-examples ALL
|
|
|
|
DEPENDS ${EXAMPLE_DOC_GENERATED_FILES})
|
2016-03-19 09:59:06 +01:00
|
|
|
|
2017-08-31 21:21:19 +02:00
|
|
|
add_custom_target(check-examples
|
|
|
|
${CMAKE_COMMAND} -D EXAMPLE_DOC_SOURCE_FILES= ${CMAKE_SOURCE_DIR}
|
|
|
|
WORKING_DIRECTORY ${CMAKE_BINARY_DIR})
|
|
|
|
list(APPEND CHECK_TARGETS check-examples)
|
2016-12-31 14:02:14 +01:00
|
|
|
|
|
|
|
# vim: filetype=cmake:expandtab:shiftwidth=4:tabstop=8:softtabstop=4:textwidth=80:foldmethod=marker
|