changeset 818:49fa22f0b4b9

windows: support for VS2017
author David Demelier <markand@malikania.fr>
date Fri, 23 Nov 2018 21:50:20 +0100
parents ebe561276c33
children d2737ddd7e36
files CMakeLists.txt cmake/IrccdSystem.cmake cmake/function/IrccdBuildHtml.cmake cmake/function/IrccdDefineLibrary.cmake cmake/function/IrccdDefinePlugin.cmake cmake/windows/CMakeLists.txt cmake/windows/vc_redist.x64.exe doc/CMakeLists.txt doc/html/CMakeLists.txt extern/duktape/CMakeLists.txt extern/json/CMakeLists.txt irccd-test/main.cpp irccd/main.cpp irccdctl/cli.hpp libirccd-ctl/irccd/ctl/controller.hpp libirccd-daemon/irccd/daemon/bot.cpp libirccd-daemon/irccd/daemon/command.cpp libirccd-daemon/irccd/daemon/command.hpp libirccd-daemon/irccd/daemon/dynlib_plugin.cpp libirccd-daemon/irccd/daemon/dynlib_plugin.hpp libirccd-daemon/irccd/daemon/irc.cpp libirccd-daemon/irccd/daemon/logger.cpp libirccd-daemon/irccd/daemon/logger.hpp libirccd-daemon/irccd/daemon/plugin.cpp libirccd-daemon/irccd/daemon/plugin_service.cpp libirccd-daemon/irccd/daemon/plugin_service.hpp libirccd-daemon/irccd/daemon/rule.cpp libirccd-daemon/irccd/daemon/rule_service.cpp libirccd-daemon/irccd/daemon/rule_service.hpp libirccd-daemon/irccd/daemon/rule_util.cpp libirccd-daemon/irccd/daemon/rule_util.hpp libirccd-daemon/irccd/daemon/server.hpp libirccd-daemon/irccd/daemon/server_service.cpp libirccd-daemon/irccd/daemon/server_service.hpp libirccd-daemon/irccd/daemon/server_util.cpp libirccd-daemon/irccd/daemon/server_util.hpp libirccd-daemon/irccd/daemon/transport_client.cpp libirccd-daemon/irccd/daemon/transport_client.hpp libirccd-daemon/irccd/daemon/transport_service.hpp libirccd-daemon/irccd/daemon/transport_util.hpp libirccd-js/irccd/js/irccd_js_api.cpp libirccd-js/irccd/js/js_api.cpp libirccd-js/irccd/js/js_api.hpp libirccd-test/irccd/test/cli_fixture.cpp libirccd-test/irccd/test/command_fixture.cpp libirccd-test/irccd/test/js_fixture.cpp libirccd-test/irccd/test/js_plugin_fixture.cpp libirccd/CMakeLists.txt libirccd/irccd/config.hpp libirccd/irccd/ini.cpp libirccd/irccd/ini.hpp libirccd/irccd/json_util.cpp libirccd/irccd/json_util.hpp libirccd/irccd/options.cpp libirccd/irccd/options.hpp libirccd/irccd/string_util.cpp libirccd/irccd/system.cpp libirccd/irccd/system.hpp plugins/CMakeLists.txt plugins/ask/CMakeLists.txt plugins/auth/CMakeLists.txt plugins/hangman/CMakeLists.txt plugins/history/CMakeLists.txt plugins/joke/CMakeLists.txt plugins/logger/CMakeLists.txt plugins/plugin/CMakeLists.txt plugins/roulette/CMakeLists.txt plugins/tictactoe/CMakeLists.txt tests/src/libirccd-daemon/dynlib-plugin/CMakeLists.txt tests/src/libirccd-daemon/dynlib-plugin/main.cpp tests/src/libirccd-daemon/dynlib-plugin/sample_plugin.cpp tests/src/libirccd-daemon/dynlib-plugin/test_plugin.cpp tests/src/libirccd-js/js-plugin/main.cpp
diffstat 73 files changed, 699 insertions(+), 468 deletions(-) [+]
line wrap: on
line diff
--- a/CMakeLists.txt	Mon Nov 19 07:04:42 2018 +0100
+++ b/CMakeLists.txt	Fri Nov 23 21:50:20 2018 +0100
@@ -52,21 +52,27 @@
 cmake_minimum_required(VERSION 3.10)
 project(irccd)
 
+include(GNUInstallDirs)
+
 # Helper to set global internal variables.
 function(setg var value)
 	set("${var}" "${value}" CACHE INTERNAL "")
 endfunction ()
 
-set(CMAKE_MODULE_PATH ${CMAKE_MODULE_PATH} ${irccd_SOURCE_DIR}/cmake/packages)
-set(CMAKE_POSITION_INDEPENDENT_CODE On)
+set_property(GLOBAL PROPERTY USE_FOLDERS On)
+
 set(CMAKE_CXX_STANDARD 17)
 set(CMAKE_CXX_STANDARD_REQUIRED On)
+set(CMAKE_MODULE_PATH ${CMAKE_MODULE_PATH} ${irccd_SOURCE_DIR}/cmake/packages)
+set(CMAKE_POSITION_INDEPENDENT_CODE On)
 set(CMAKE_WINDOWS_EXPORT_ALL_SYMBOLS On)
 
-set_property(GLOBAL PROPERTY USE_FOLDERS On)
+set(CMAKE_RUNTIME_OUTPUT_DIRECTORY ${CMAKE_BINARY_DIR}/bin)
 
-include(CMakeParseArguments)
-include(GNUInstallDirs)
+foreach (cfg ${CMAKE_CONFIGURATION_TYPES})
+	string(TOUPPER CFG ${cfg})
+	set(CMAKE_RUNTIME_OUTPUT_DIRECTORY_${CFG} ${CMAKE_BINARY_DIR}/bin/${cfg})
+endforeach ()
 
 include(cmake/function/IrccdBuildHtml.cmake)
 include(cmake/function/IrccdDefineExecutable.cmake)
@@ -99,12 +105,8 @@
 add_subdirectory(irccdctl)
 add_subdirectory(irccd-test)
 add_subdirectory(systemd)
+add_subdirectory(plugins)
 
-if (IRCCD_HAVE_JS)
-	add_subdirectory(plugins)
-endif ()
-
-# Tests.
 if (IRCCD_WITH_TESTS)
 	include(CTest)
 	add_subdirectory(tests)
@@ -157,16 +159,3 @@
 
 include(cmake/IrccdPackage.cmake)
 include(CPack)
-
-# Metadata files
-add_custom_target(
-	metadata
-	SOURCES
-		${CMAKE_SOURCE_DIR}/CHANGES.md
-		${CMAKE_SOURCE_DIR}/CONTRIBUTE.md
-		${CMAKE_SOURCE_DIR}/CREDITS.md
-		${CMAKE_SOURCE_DIR}/INSTALL.md
-		${CMAKE_SOURCE_DIR}/MIGRATING.md
-		${CMAKE_SOURCE_DIR}/README.md
-		${CMAKE_SOURCE_DIR}/STYLE.md
-)
--- a/cmake/IrccdSystem.cmake	Mon Nov 19 07:04:42 2018 +0100
+++ b/cmake/IrccdSystem.cmake	Fri Nov 23 21:50:20 2018 +0100
@@ -27,14 +27,12 @@
 # -------------------------------------------------------------------
 #
 
-if (MINGW)
-	set(CMAKE_CXX_FLAGS "-D_WIN32_WINNT=0x0600 ${CMAKE_CXX_FLAGS}")
-endif ()
-
-if (CMAKE_SIZEOF_VOID_P MATCHES "8")
-	set(IRCCD_64BITS TRUE)
-else ()
-	set(IRCCD_64BITS FALSE)
+if (CMAKE_SYSTEM_NAME MATCHES "Windows")
+	if (CMAKE_CXX_COMPILER_ID MATCHES "GNU")
+		set(CMAKE_CXX_FLAGS "-DWINVER=0x0601 -D_WIN32_WINNT=0x0601 ${CMAKE_CXX_FLAGS}")
+	elseif (CMAKE_CXX_COMPILER_ID MATCHES "MSVC")
+		set(CMAKE_CXX_FLAGS "/DWINVER=0x0601 /D_WIN32_WINNT=0x0601 /wd4244 /wd4267 /wd4996 ${CMAKE_CXX_FLAGS}")
+	endif ()
 endif ()
 
 #
--- a/cmake/function/IrccdBuildHtml.cmake	Mon Nov 19 07:04:42 2018 +0100
+++ b/cmake/function/IrccdBuildHtml.cmake	Fri Nov 23 21:50:20 2018 +0100
@@ -71,80 +71,78 @@
 endif ()
 
 macro(irccd_build_html)
-	if (NOT IRCCD_HAVE_HTML)
-		return ()
-	endif ()
+	if (IRCCD_HAVE_HTML)
+		set(options "")
+		set(oneValueArgs "COMPONENT;OUTPUT_DIR;OUTPUT_VAR;SOURCE;TEMPLATE")
+		set(multiValueArgs "VARIABLES")
 
-	set(options "")
-	set(oneValueArgs "COMPONENT;OUTPUT_DIR;OUTPUT_VAR;SOURCE;TEMPLATE")
-	set(multiValueArgs "VARIABLES")
-
-	cmake_parse_arguments(HTML "${options}" "${oneValueArgs}" "${multiValueArgs}" ${ARGN})
+		cmake_parse_arguments(HTML "${options}" "${oneValueArgs}" "${multiValueArgs}" ${ARGN})
 
-	if (NOT HTML_SOURCE)
-		message(FATAL_ERROR "Missing SOURCE parameter")
-	endif ()
-	if (NOT HTML_TEMPLATE)
-		message(FATAL_ERROR "Missing TEMPLATE parameter")
-	endif ()
-	if (IS_ABSOLUTE ${HTML_OUTPUT_DIR})
-		message(FATAL_ERROR "OUTPUT_DIR variable must not be absolute")
-	endif ()
+		if (NOT HTML_SOURCE)
+			message(FATAL_ERROR "Missing SOURCE parameter")
+		endif ()
+		if (NOT HTML_TEMPLATE)
+			message(FATAL_ERROR "Missing TEMPLATE parameter")
+		endif ()
+		if (IS_ABSOLUTE ${HTML_OUTPUT_DIR})
+			message(FATAL_ERROR "OUTPUT_DIR variable must not be absolute")
+		endif ()
 
-	#
-	# Get the basename, use string REGEX REPLACE because
-	# get_filename_component will remove all extensions while we only want
-	# to remove md. (e.g. irccd.conf.md becomes irccd.conf).
-	#
-	get_filename_component(filename ${HTML_SOURCE} NAME)
-	string(REGEX REPLACE "^(.*)\\.md$" "\\1" filename ${filename})
+		#
+		# Get the basename, use string REGEX REPLACE because
+		# get_filename_component will remove all extensions while we only want
+		# to remove md. (e.g. irccd.conf.md becomes irccd.conf).
+		#
+		get_filename_component(filename ${HTML_SOURCE} NAME)
+		string(REGEX REPLACE "^(.*)\\.md$" "\\1" filename ${filename})
 
-	# Compute baseurl.
-	file(
-		RELATIVE_PATH
-		baseurl
-		${CMAKE_BINARY_DIR}/html/${HTML_OUTPUT_DIR}
-		${CMAKE_BINARY_DIR}/html
-	)
+		# Compute baseurl.
+		file(
+			RELATIVE_PATH
+			baseurl
+			${CMAKE_BINARY_DIR}/html/${HTML_OUTPUT_DIR}
+			${CMAKE_BINARY_DIR}/html
+		)
 
-	if (baseurl STREQUAL "")
-		set(baseurl "./")
-	endif ()
-	if (NOT HTML_OUTPUT_DIR OR HTML_OUTPUT_DIR STREQUAL "")
-		set(HTML_OUTPUT_DIR ".")
-	endif ()
+		if (baseurl STREQUAL "")
+			set(baseurl "./")
+		endif ()
+		if (NOT HTML_OUTPUT_DIR OR HTML_OUTPUT_DIR STREQUAL "")
+			set(HTML_OUTPUT_DIR ".")
+		endif ()
 
-	# Filname path to output directory and files.
-	set(outputdir ${CMAKE_BINARY_DIR}/html/${HTML_OUTPUT_DIR})
-	set(output ${outputdir}/${filename}.html)
+		# Filname path to output directory and files.
+		set(outputdir ${CMAKE_BINARY_DIR}/html/${HTML_OUTPUT_DIR})
+		set(output ${outputdir}/${filename}.html)
 
-	# Build arguments.
-	if (HTML_TEMPLATE)
-		set(args -t ${HTML_TEMPLATE} -v baseurl="${baseurl}")
-	endif ()
+		# Build arguments.
+		if (HTML_TEMPLATE)
+			set(args -t ${HTML_TEMPLATE} -v baseurl="${baseurl}")
+		endif ()
 
-	add_custom_command(
-		OUTPUT ${output}
-		COMMAND
-			${CMAKE_COMMAND} -E make_directory ${outputdir}
-		COMMAND
-			$<TARGET_FILE:marker::marker> ${args} ${HTML_VARIABLES}
-			$<TARGET_FILE:marker::libmarker-bulma> ${output} ${HTML_SOURCE}
-		DEPENDS
-			${HTML_SOURCE}
-			${HTML_TEMPLATE}
-	)
+		add_custom_command(
+			OUTPUT ${output}
+			COMMAND
+				${CMAKE_COMMAND} -E make_directory ${outputdir}
+			COMMAND
+				$<TARGET_FILE:marker::marker> ${args} ${HTML_VARIABLES}
+				$<TARGET_FILE:marker::libmarker-bulma> ${output} ${HTML_SOURCE}
+			DEPENDS
+				${HTML_SOURCE}
+				${HTML_TEMPLATE}
+		)
 
-	# Install the documentation file as component if provided.
-	if (HTML_COMPONENT)
-		install(
-			FILES ${output}
-			COMPONENT ${HTML_COMPONENT}
-			DESTINATION ${CMAKE_INSTALL_DOCDIR}/${HTML_OUTPUT_DIR}
-		)
-	endif ()
+		# Install the documentation file as component if provided.
+		if (HTML_COMPONENT)
+			install(
+				FILES ${output}
+				COMPONENT ${HTML_COMPONENT}
+				DESTINATION ${CMAKE_INSTALL_DOCDIR}/${HTML_OUTPUT_DIR}
+			)
+		endif ()
 
-	if (HTML_OUTPUT_VAR)
-		set(${HTML_OUTPUT_VAR} ${output})
+		if (HTML_OUTPUT_VAR)
+			set(${HTML_OUTPUT_VAR} ${output})
+		endif ()
 	endif ()
 endmacro ()
--- a/cmake/function/IrccdDefineLibrary.cmake	Mon Nov 19 07:04:42 2018 +0100
+++ b/cmake/function/IrccdDefineLibrary.cmake	Fri Nov 23 21:50:20 2018 +0100
@@ -47,7 +47,11 @@
 	endif ()
 
 	add_library(${LIB_TARGET} ${LIB_SOURCES} ${LIB_HEADERS})
-	target_include_directories(${LIB_TARGET} PRIVATE ${LIB_LOCAL_INCLUDES} PUBLIC ${LIB_PUBLIC_INCLUDES})
+	target_include_directories(
+		${LIB_TARGET}
+		PRIVATE ${LIB_LOCAL_INCLUDES}
+		PUBLIC ${LIB_PUBLIC_INCLUDES}
+	)
 	target_compile_definitions(
 		${LIB_TARGET}
 		PRIVATE
@@ -61,16 +65,8 @@
 		${LIB_TARGET}
 		PROPERTIES
 			PREFIX ""
-			RUNTIME_OUTPUT_DIRECTORY ${CMAKE_BINARY_DIR}/bin
+			IMPORT_PREFIX ""
 	)
-	foreach (c ${CMAKE_CONFIGURATION_TYPES})
-		string(TOUPPER ${c} cu)
-		set_target_properties(
-			${LIB_TARGET}
-			PROPERTIES
-				RUNTIME_OUTPUT_DIRECTORY_${cu} ${CMAKE_BINARY_DIR}/bin/${c}
-		)
-	endforeach()
 
 	if (${LIB_EXPORT})
 		install(
--- a/cmake/function/IrccdDefinePlugin.cmake	Mon Nov 19 07:04:42 2018 +0100
+++ b/cmake/function/IrccdDefinePlugin.cmake	Fri Nov 23 21:50:20 2018 +0100
@@ -154,6 +154,14 @@
 			message(FATAL_ERROR "Invalid TYPE given, must be JS or NATIVE")
 		endif ()
 
+		# Put under "plugins".
+		set_target_properties(
+			plugin-${PLG_NAME}
+			PROPERTIES
+				FOLDER "plugins"
+				PROJECT_NAME ${PLG_NAME}
+		)
+
 		# Component grouping in installer.
 		setg(CPACK_COMPONENT_${PLG_UPPER_NAME}_GROUP "Plugins")
 		setg(CPACK_COMPONENT_${PLG_UPPER_NAME}_DESCRIPTION "Install ${PLG_NAME} plugin.")
--- a/cmake/windows/CMakeLists.txt	Mon Nov 19 07:04:42 2018 +0100
+++ b/cmake/windows/CMakeLists.txt	Fri Nov 23 21:50:20 2018 +0100
@@ -16,12 +16,6 @@
 # OR IN CONNECTION WITH THE USE OR PERFORMANCE OF THIS SOFTWARE.
 #
 
-#
-# This file consists of installing required .dll files for Windows.
-#
-# It currently only supports MinGW.
-#
-
 project(windows)
 
 #
@@ -30,21 +24,71 @@
 #
 
 if (MINGW)
-	if (IRCCD_64BITS)
-		set(LIBGCC_DLL libgcc_s_seh-1.dll)
-	else ()
-		set(LIBGCC_DLL libgcc_s_sjlj-1.dll)
-	endif ()
+	# debug libraries
+	set(BOOST_CHRONO_DLL_DEBUG libboost_chrono-mt.dll)
+	set(BOOST_DATE_TIME_DLL_DEBUG libboost_date_time-mt.dll)
+	set(BOOST_FILESYSTEM_DLL_DEBUG libboost_filesystem-mt.dll)
+	set(BOOST_SYSTEM_DLL_DEBUG libboost_system-mt.dll)
+	set(BOOST_TIMER_DLL_DEBUG libboost_timer-mt.dll)
+	set(BOOST_UNIT_TEST_FRAMEWORK_DLL_DEBUG libboost_unit_test_framework-mt.dll)
+	set(CRYPTO_DLL_DEBUG libcrypto-1_1-x64.dll)
+	set(SSL_DLL_DEBUG libssl-1_1-x64.dll)
 
-	set(LIBWINPTHREAD_DLL libwinpthread-1.dll)
-	set(LIBSTDCPP_DLL libstdc++-6.dll)
+	# MinGW specific.
+	set(LIBGCC_DLL_DEBUG libgcc_s_seh-1.dll)
+	set(LIBWINPTHREAD_DLL_DEBUG libwinpthread-1.dll)
+	set(LIBSTDCPP_DLL_DEBUG libstdc++-6.dll)
 	list(APPEND DLLS LIBGCC_DLL LIBWINPTHREAD_DLL LIBSTDCPP_DLL)
 
-	if (IRCCD_HAVE_SSL)
-		set(CRYPTO_DLL libcrypto.dll)
-		set(SSL_DLL libssl.dll)
-		list(APPEND DLLS CRYPTO_DLL SSL_DLL)
-	endif ()
+	# release libraries use the same.
+	set(BOOST_CHRONO_DLL_RELEASE ${BOOST_CHRONO_DLL_DEBUG})
+	set(BOOST_DATE_TIME_DLL_RELEASE ${BOOST_DATE_TIME_DLL_DEBUG})
+	set(BOOST_FILESYSTEM_DLL_RELEASE ${BOOST_FILESYSTEM_DLL_DEBUG})
+	set(BOOST_SYSTEM_DLL_RELEASE ${BOOST_SYSTEM_DLL_DEBUG})
+	set(BOOST_TIMER_DLL_RELEASE ${BOOST_TIMER_DLL_DEBUG})
+	set(BOOST_UNIT_TEST_FRAMEWORK_DLL_RELEASE ${BOOST_UNIT_TEST_FRAMEWORK_DLL_DEBUG})
+	set(CRYPTO_DLL_RELEASE ${CRYPTO_DLL_DEBUG})
+	set(SSL_DLL_RELEASE ${SSL_DLL_DEBUG})
+	set(LIBGCC_DLL_RELEASE ${LIBGCC_DLL_DEBUG})
+	set(LIBWINPTHREAD_DLL_RELEASE ${LIBWINPTHREAD_DLL_DEBUG})
+	set(LIBSTDCPP_DLL_RELEASE ${LIBSTDCPP_DLL_DEBUG})
+elseif (MSVC)
+	# debug libraries
+	set(BOOST_CHRONO_DLL_DEBUG boost_chrono-vc141-mt-gd-x64-1_68.dll)
+	set(BOOST_DATE_TIME_DLL_DEBUG boost_date_time-vc141-mt-gd-x64-1_68.dll)
+	set(BOOST_FILESYSTEM_DLL_DEBUG boost_filesystem-vc141-mt-gd-x64-1_68.dll)
+	set(BOOST_SYSTEM_DLL_DEBUG boost_system-vc141-mt-gd-x64-1_68.dll)
+	set(BOOST_TIMER_DLL_DEBUG boost_timer-vc141-mt-gd-x64-1_68.dll)
+	set(BOOST_UNIT_TEST_FRAMEWORK_DLL_DEBUG boost_unit_test_framework-vc141-mt-gd-x64-1_68.dll)
+	set(CRYPTO_DLL_DEBUG libcrypto-1_1-x64.dll)
+	set(SSL_DLL_DEBUG libssl-1_1-x64.dll)
+
+	# release libraries
+	set(BOOST_CHRONO_DLL_RELEASE boost_chrono-vc141-mt-x64-1_68.dll)
+	set(BOOST_DATE_TIME_DLL_RELEASE boost_date_time-vc141-mt-x64-1_68.dll)
+	set(BOOST_FILESYSTEM_DLL_RELEASE boost_filesystem-vc141-mt-x64-1_68.dll)
+	set(BOOST_SYSTEM_DLL_RELEASE boost_system-vc141-mt-x64-1_68.dll)
+	set(BOOST_TIMER_DLL_RELEASE boost_timer-vc141-mt-x64-1_68.dll)
+	set(BOOST_UNIT_TEST_FRAMEWORK_DLL_RELEASE boost_unit_test_framework-vc141-mt-x64-1_68.dll)
+	set(CRYPTO_DLL_RELEASE libcrypto-1_1-x64.dll)
+	set(SSL_DLL_RELEASE libssl-1_1-x64.dll)
+endif ()
+
+list(
+	APPEND
+	DLLS
+	BOOST_CHRONO_DLL
+	BOOST_DATE_TIME_DLL
+	BOOST_FILESYSTEM_DLL
+	BOOST_SYSTEM_DLL
+	BOOST_UNIT_TEST_FRAMEWORK_DLL
+)
+
+if (IRCCD_HAVE_SSL)
+	list(APPEND DLLS CRYPTO_DLL SSL_DLL)
+endif ()
+if (IRCCD_HAVE_JS)
+	list(APPEND DLLS BOOST_TIMER_DLL)
 endif ()
 
 #
@@ -61,26 +105,38 @@
 
 foreach (name ${DLLS})
 	find_program(
-		IRCCD_WITH_${name}
-		NAMES ${${name}}
-		DOC "Path to ${name} DLL"
+		IRCCD_WITH_${name}_DEBUG
+		NAMES ${${name}_DEBUG}
+		DOC "Path to ${name} DLL (Debug)"
+	)
+	find_program(
+		IRCCD_WITH_${name}_RELEASE
+		NAMES ${${name}_RELEASE}
+		DOC "Path to ${name} DLL (Release)"
 	)
 
-	if (EXISTS ${IRCCD_WITH_${name}})
-		file(
-			COPY ${IRCCD_WITH_${name}}
-			DESTINATION ${CMAKE_BINARY_DIR}/bin
-		)
+	if (EXISTS ${IRCCD_WITH_${name}_DEBUG})
 		install(
-			PROGRAMS ${IRCCD_WITH_${name}}
+			PROGRAMS
+				$<$<CONFIG:Debug>:${IRCCD_WITH_${name}_DEBUG}>
 			COMPONENT libirccd
 			DESTINATION bin
 		)
-		list(APPEND FOUND IRCCD_WITH_${name})
+		list(APPEND FOUND IRCCD_WITH_${name}_DEBUG)
 	else ()
-		setg(IRCCD_PACKAGE Off)
-		setg(IRCCD_PACKAGE_MSG "No (some .dll were not found)")
-		list(APPEND NOT_FOUND IRCCD_WITH_${name})
+		list(APPEND NOT_FOUND IRCCD_WITH_${name}_DEBUG)
+	endif ()
+
+	if (EXISTS ${IRCCD_WITH_${name}_RELEASE})
+		install(
+			PROGRAMS
+				$<$<CONFIG:Release>:${IRCCD_WITH_${name}_RELEASE}>
+			COMPONENT libirccd
+			DESTINATION bin
+		)
+		list(APPEND FOUND IRCCD_WITH_${name}_RELEASE)
+	else ()
+		list(APPEND NOT_FOUND IRCCD_WITH_${name}_RELEASE)
 	endif ()
 endforeach ()
 
Binary file cmake/windows/vc_redist.x64.exe has changed
--- a/doc/CMakeLists.txt	Mon Nov 19 07:04:42 2018 +0100
+++ b/doc/CMakeLists.txt	Fri Nov 23 21:50:20 2018 +0100
@@ -18,8 +18,6 @@
 
 project(doc)
 
-add_custom_target(docs ALL COMMENT "Generating documentation")
-
 if (IRCCD_HAVE_DOXYGEN)
 	add_subdirectory(doxygen)
 endif ()
--- a/doc/html/CMakeLists.txt	Mon Nov 19 07:04:42 2018 +0100
+++ b/doc/html/CMakeLists.txt	Fri Nov 23 21:50:20 2018 +0100
@@ -177,7 +177,7 @@
 )
 
 add_custom_target(
-	html ALL
+	doc-html ALL
 	COMMAND
 		${CMAKE_COMMAND} -E make_directory ${CMAKE_BINARY_DIR}/html
 	COMMAND
@@ -188,6 +188,8 @@
 		${html_SOURCE_DIR}/template.html
 )
 
+set_target_properties(doc-html PROPERTIES FOLDER "doc")
+
 # Install resources files.
 install(
 	FILES ${CSS} ${JS}
--- a/extern/duktape/CMakeLists.txt	Mon Nov 19 07:04:42 2018 +0100
+++ b/extern/duktape/CMakeLists.txt	Fri Nov 23 21:50:20 2018 +0100
@@ -18,13 +18,32 @@
 
 cmake_minimum_required(VERSION 3.0)
 project(duktape)
+
 add_library(libduktape duktape.h duktape.cpp)
+
+target_include_directories(
+	libduktape
+	PUBLIC
+		$<BUILD_INTERFACE:${duktape_SOURCE_DIR}>
+		$<INSTALL_INTERFACE:${CMAKE_INSTALL_INCLUDEDIR}/irccd/extern>
+)
+
+target_compile_definitions(libduktape PUBLIC DUK_OPT_CPP_EXCEPTIONS)
+
+set_target_properties(
+	libduktape
+	PROPERTIES
+		FOLDER "extern"
+		PREFIX ""
+		IMPORT_PREFIX ""
+)
+
 install(
 	TARGETS libduktape
 	EXPORT irccd-targets
-	RUNTIME DESTINATION bin
-	LIBRARY DESTINATION lib
-	ARCHIVE DESTINATION lib
+	RUNTIME DESTINATION ${CMAKE_INSTALL_BINDIR}
+	LIBRARY DESTINATION ${CMAKE_INSTALL_LIBDIR}/irccd
+	ARCHIVE DESTINATION ${CMAKE_INSTALL_LIBDIR}/irccd
 )
 
 install(
@@ -33,11 +52,3 @@
 		duk_config.h
 	DESTINATION ${CMAKE_INSTALL_INCLUDEDIR}/irccd/extern
 )
-target_include_directories(
-	libduktape
-	PUBLIC
-		$<BUILD_INTERFACE:${duktape_SOURCE_DIR}>
-		$<INSTALL_INTERFACE:${CMAKE_INSTALL_INCLUDEDIR}/irccd/extern>
-)
-target_compile_definitions(libduktape PUBLIC DUK_OPT_CPP_EXCEPTIONS)
-set_target_properties(libduktape PROPERTIES OUTPUT_NAME irccd-duktape)
--- a/extern/json/CMakeLists.txt	Mon Nov 19 07:04:42 2018 +0100
+++ b/extern/json/CMakeLists.txt	Fri Nov 23 21:50:20 2018 +0100
@@ -19,10 +19,10 @@
 project(json)
 add_library(libjson INTERFACE)
 target_include_directories(
-    libjson
-    INTERFACE
-        $<BUILD_INTERFACE:${json_SOURCE_DIR}>
-        $<INSTALL_INTERFACE:${CMAKE_INSTALL_INCLUDEDIR}/irccd/extern>
+	libjson
+	INTERFACE
+		$<BUILD_INTERFACE:${json_SOURCE_DIR}>
+		$<INSTALL_INTERFACE:${CMAKE_INSTALL_INCLUDEDIR}/irccd/extern>
 )
 install(TARGETS libjson EXPORT irccd-targets)
 install(FILES json.hpp DESTINATION ${CMAKE_INSTALL_INCLUDEDIR}/irccd/extern)
--- a/irccd-test/main.cpp	Mon Nov 19 07:04:42 2018 +0100
+++ b/irccd-test/main.cpp	Fri Nov 23 21:50:20 2018 +0100
@@ -647,7 +647,7 @@
 #if defined(IRCCD_HAVE_JS)
 	auto loader = std::make_unique<js_plugin_loader>(*daemon);
 
-	for (const auto& f : js_api::registry)
+	for (const auto& f : js_api::registry())
 		loader->get_modules().push_back(f());
 
 	daemon->plugins().add_loader(std::move(loader));
--- a/irccd/main.cpp	Mon Nov 19 07:04:42 2018 +0100
+++ b/irccd/main.cpp	Fri Nov 23 21:50:20 2018 +0100
@@ -178,7 +178,7 @@
 	init(argc, argv);
 
 	// 1. Load commands.
-	for (const auto& f : command::registry)
+	for (const auto& f : command::registry())
 		instance->transports().get_commands().push_back(f());
 
 	// 2. Load plugin loaders.
@@ -187,7 +187,7 @@
 #if defined(IRCCD_HAVE_JS)
 	auto loader = std::make_unique<js::js_plugin_loader>(*instance);
 
-	for (const auto& f : js::js_api::registry)
+	for (const auto& f : js::js_api::registry())
 		loader->get_modules().push_back(f());
 
 	instance->plugins().add_loader(std::move(loader));
--- a/irccdctl/cli.hpp	Mon Nov 19 07:04:42 2018 +0100
+++ b/irccdctl/cli.hpp	Fri Nov 23 21:50:20 2018 +0100
@@ -46,7 +46,7 @@
 	/**
 	 * \brief Command constructor factory.
 	 */
-	using constructor = std::function<auto () -> std::unique_ptr<cli>>;
+	using constructor = std::function<std::unique_ptr<cli> ()>;
 
 	/**
 	 * \brief Registry of all commands.
--- a/libirccd-ctl/irccd/ctl/controller.hpp	Mon Nov 19 07:04:42 2018 +0100
+++ b/libirccd-ctl/irccd/ctl/controller.hpp	Fri Nov 23 21:50:20 2018 +0100
@@ -24,6 +24,8 @@
  * \brief Main irccdctl interface.
  */
 
+#include "sysconfig.hpp"
+
 #include <cassert>
 
 #include <irccd/connector.hpp>
--- a/libirccd-daemon/irccd/daemon/bot.cpp	Mon Nov 19 07:04:42 2018 +0100
+++ b/libirccd-daemon/irccd/daemon/bot.cpp	Fri Nov 23 21:50:20 2018 +0100
@@ -16,6 +16,8 @@
  * OR IN CONNECTION WITH THE USE OR PERFORMANCE OF THIS SOFTWARE.
  */
 
+#include <irccd/sysconfig.hpp>
+
 #include <fstream>
 
 #include <boost/predef/os.h>
--- a/libirccd-daemon/irccd/daemon/command.cpp	Mon Nov 19 07:04:42 2018 +0100
+++ b/libirccd-daemon/irccd/daemon/command.cpp	Fri Nov 23 21:50:20 2018 +0100
@@ -16,6 +16,8 @@
  * OR IN CONNECTION WITH THE USE OR PERFORMANCE OF THIS SOFTWARE.
  */
 
+#include <irccd/sysconfig.hpp>
+
 #include <irccd/string_util.hpp>
 
 #include "bot.hpp"
@@ -50,7 +52,7 @@
 
 	auto config = plugin.get_options();
 
-	config[*var] = *value;
+	config[var->get<std::string>()] = value->get<std::string>();
 	plugin.set_options(config);
 	client.success("plugin-config");
 }
@@ -68,7 +70,7 @@
 
 	/*
 	 * Don't put all variables into the response, put them into a sub
-         * property 'variables' instead.
+	 * property 'variables' instead.
 	 *
 	 * It's easier for the client to iterate over all.
 	 */
@@ -88,35 +90,40 @@
 
 } // !namespace
 
-const std::vector<command::constructor> command::registry{
-	bind<plugin_config_command>(),
-	bind<plugin_info_command>(),
-	bind<plugin_list_command>(),
-	bind<plugin_load_command>(),
-	bind<plugin_reload_command>(),
-	bind<plugin_unload_command>(),
-	bind<rule_add_command>(),
-	bind<rule_edit_command>(),
-	bind<rule_info_command>(),
-	bind<rule_info_command>(),
-	bind<rule_list_command>(),
-	bind<rule_move_command>(),
-	bind<rule_remove_command>(),
-	bind<server_connect_command>(),
-	bind<server_disconnect_command>(),
-	bind<server_info_command>(),
-	bind<server_invite_command>(),
-	bind<server_join_command>(),
-	bind<server_kick_command>(),
-	bind<server_list_command>(),
-	bind<server_me_command>(),
-	bind<server_message_command>(),
-	bind<server_mode_command>(),
-	bind<server_nick_command>(),
-	bind<server_notice_command>(),
-	bind<server_part_command>(),
-	bind<server_reconnect_command>(),
-	bind<server_topic_command>(),
+auto command::registry() noexcept -> const std::vector<constructor>&
+{
+	static const std::vector<command::constructor> list{
+		bind<plugin_config_command>(),
+		bind<plugin_info_command>(),
+		bind<plugin_list_command>(),
+		bind<plugin_load_command>(),
+		bind<plugin_reload_command>(),
+		bind<plugin_unload_command>(),
+		bind<rule_add_command>(),
+		bind<rule_edit_command>(),
+		bind<rule_info_command>(),
+		bind<rule_info_command>(),
+		bind<rule_list_command>(),
+		bind<rule_move_command>(),
+		bind<rule_remove_command>(),
+		bind<server_connect_command>(),
+		bind<server_disconnect_command>(),
+		bind<server_info_command>(),
+		bind<server_invite_command>(),
+		bind<server_join_command>(),
+		bind<server_kick_command>(),
+		bind<server_list_command>(),
+		bind<server_me_command>(),
+		bind<server_message_command>(),
+		bind<server_mode_command>(),
+		bind<server_nick_command>(),
+		bind<server_notice_command>(),
+		bind<server_part_command>(),
+		bind<server_reconnect_command>(),
+		bind<server_topic_command>()
+	};
+
+	return list;
 };
 
 // {{{ plugin_config_command
--- a/libirccd-daemon/irccd/daemon/command.hpp	Mon Nov 19 07:04:42 2018 +0100
+++ b/libirccd-daemon/irccd/daemon/command.hpp	Fri Nov 23 21:50:20 2018 +0100
@@ -54,12 +54,12 @@
 	/**
 	 * \brief Command constructor factory.
 	 */
-	using constructor = std::function<auto () -> std::unique_ptr<command>>;
+	using constructor = std::function<std::unique_ptr<command> ()>;
 
 	/**
 	 * \brief Registry of all commands.
 	 */
-	static const std::vector<constructor> registry;
+	static auto registry() noexcept -> const std::vector<constructor>&;
 
 	/**
 	 * Default destructor virtual.
--- a/libirccd-daemon/irccd/daemon/dynlib_plugin.cpp	Mon Nov 19 07:04:42 2018 +0100
+++ b/libirccd-daemon/irccd/daemon/dynlib_plugin.cpp	Fri Nov 23 21:50:20 2018 +0100
@@ -16,6 +16,8 @@
  * OR IN CONNECTION WITH THE USE OR PERFORMANCE OF THIS SOFTWARE.
  */
 
+#include <irccd/sysconfig.hpp>
+
 #include <algorithm>
 
 #include <boost/dll.hpp>
@@ -41,6 +43,9 @@
 
 namespace {
 
+using abisym_func_type = version ();
+using initsym_func_type = std::unique_ptr<plugin> (std::string);
+
 auto symbol(std::string_view path) -> std::pair<std::string, std::string>
 {
 	auto id = boost::filesystem::path(std::string(path)).stem().string();
@@ -72,12 +77,7 @@
 {
 	const std::string idstr(id);
 	const std::string pathstr(path);
-
 	const auto [ abisym, initsym ] = symbol(pathstr);
-
-	using abisym_func_type = version ();
-	using initsym_func_type = std::unique_ptr<plugin> (std::string);
-
 	const auto abi = boost::dll::import_alias<abisym_func_type>(pathstr, abisym);
 	const auto init = boost::dll::import_alias<initsym_func_type>(pathstr, initsym);
 
@@ -96,7 +96,7 @@
 	 * We need to keep a reference to `init' variable for the whole plugin
 	 * lifetime.
 	 */
-	return std::shared_ptr<plugin>(plg.release(), [init] (auto ptr) mutable {
+	return std::shared_ptr<plugin>(plg.release(), [init] (plugin* ptr) mutable {
 		delete ptr;
 	});
 }
--- a/libirccd-daemon/irccd/daemon/dynlib_plugin.hpp	Mon Nov 19 07:04:42 2018 +0100
+++ b/libirccd-daemon/irccd/daemon/dynlib_plugin.hpp	Fri Nov 23 21:50:20 2018 +0100
@@ -24,6 +24,8 @@
  * \brief Native plugin implementation.
  */
 
+#include <irccd/sysconfig.hpp>
+
 #define BOOST_DLL_FORCE_ALIAS_INSTANTIATION
 #include <boost/dll.hpp>
 
--- a/libirccd-daemon/irccd/daemon/irc.cpp	Mon Nov 19 07:04:42 2018 +0100
+++ b/libirccd-daemon/irccd/daemon/irc.cpp	Fri Nov 23 21:50:20 2018 +0100
@@ -16,6 +16,8 @@
  * OR IN CONNECTION WITH THE USE OR PERFORMANCE OF THIS SOFTWARE.
  */
 
+#include <irccd/sysconfig.hpp>
+
 #include <cassert>
 #include <iterator>
 #include <sstream>
--- a/libirccd-daemon/irccd/daemon/logger.cpp	Mon Nov 19 07:04:42 2018 +0100
+++ b/libirccd-daemon/irccd/daemon/logger.cpp	Fri Nov 23 21:50:20 2018 +0100
@@ -16,6 +16,8 @@
  * OR IN CONNECTION WITH THE USE OR PERFORMANCE OF THIS SOFTWARE.
  */
 
+#include <irccd/sysconfig.hpp>
+
 #include <cassert>
 #include <fstream>
 #include <iostream>
--- a/libirccd-daemon/irccd/daemon/logger.hpp	Mon Nov 19 07:04:42 2018 +0100
+++ b/libirccd-daemon/irccd/daemon/logger.hpp	Fri Nov 23 21:50:20 2018 +0100
@@ -276,7 +276,6 @@
  * \ingroup daemon-loggers
  */
 class filter {
-private:
 public:
 	/**
 	 * Virtual destructor defaulted.
--- a/libirccd-daemon/irccd/daemon/plugin.cpp	Mon Nov 19 07:04:42 2018 +0100
+++ b/libirccd-daemon/irccd/daemon/plugin.cpp	Fri Nov 23 21:50:20 2018 +0100
@@ -16,6 +16,8 @@
  * OR IN CONNECTION WITH THE USE OR PERFORMANCE OF THIS SOFTWARE.
  */
 
+#include <irccd/sysconfig.hpp>
+
 #include <cassert>
 #include <sstream>
 
--- a/libirccd-daemon/irccd/daemon/plugin_service.cpp	Mon Nov 19 07:04:42 2018 +0100
+++ b/libirccd-daemon/irccd/daemon/plugin_service.cpp	Fri Nov 23 21:50:20 2018 +0100
@@ -16,6 +16,8 @@
  * OR IN CONNECTION WITH THE USE OR PERFORMANCE OF THIS SOFTWARE.
  */
 
+#include <irccd/sysconfig.hpp>
+
 #include <boost/format.hpp>
 
 #include <irccd/config.hpp>
--- a/libirccd-daemon/irccd/daemon/plugin_service.hpp	Mon Nov 19 07:04:42 2018 +0100
+++ b/libirccd-daemon/irccd/daemon/plugin_service.hpp	Fri Nov 23 21:50:20 2018 +0100
@@ -24,6 +24,8 @@
  * \brief Plugin service.
  */
 
+#include <irccd/sysconfig.hpp>
+
 #include <cassert>
 #include <memory>
 #include <string>
--- a/libirccd-daemon/irccd/daemon/rule.cpp	Mon Nov 19 07:04:42 2018 +0100
+++ b/libirccd-daemon/irccd/daemon/rule.cpp	Fri Nov 23 21:50:20 2018 +0100
@@ -16,6 +16,8 @@
  * OR IN CONNECTION WITH THE USE OR PERFORMANCE OF THIS SOFTWARE.
  */
 
+#include <irccd/sysconfig.hpp>
+
 #include <algorithm>
 #include <cctype>
 
--- a/libirccd-daemon/irccd/daemon/rule_service.cpp	Mon Nov 19 07:04:42 2018 +0100
+++ b/libirccd-daemon/irccd/daemon/rule_service.cpp	Fri Nov 23 21:50:20 2018 +0100
@@ -16,6 +16,8 @@
  * OR IN CONNECTION WITH THE USE OR PERFORMANCE OF THIS SOFTWARE.
  */
 
+#include <irccd/sysconfig.hpp>
+
 #include <stdexcept>
 
 #include <irccd/config.hpp>
--- a/libirccd-daemon/irccd/daemon/rule_service.hpp	Mon Nov 19 07:04:42 2018 +0100
+++ b/libirccd-daemon/irccd/daemon/rule_service.hpp	Fri Nov 23 21:50:20 2018 +0100
@@ -24,6 +24,8 @@
  * \brief Rule service.
  */
 
+#include <irccd/sysconfig.hpp>
+
 #include <vector>
 
 #include <json.hpp>
--- a/libirccd-daemon/irccd/daemon/rule_util.cpp	Mon Nov 19 07:04:42 2018 +0100
+++ b/libirccd-daemon/irccd/daemon/rule_util.cpp	Fri Nov 23 21:50:20 2018 +0100
@@ -16,6 +16,8 @@
  * OR IN CONNECTION WITH THE USE OR PERFORMANCE OF THIS SOFTWARE.
  */
 
+#include <irccd/sysconfig.hpp>
+
 #include <irccd/ini.hpp>
 
 #include "rule.hpp"
--- a/libirccd-daemon/irccd/daemon/rule_util.hpp	Mon Nov 19 07:04:42 2018 +0100
+++ b/libirccd-daemon/irccd/daemon/rule_util.hpp	Fri Nov 23 21:50:20 2018 +0100
@@ -24,6 +24,8 @@
  * \brief Rule utilities.
  */
 
+#include <irccd/sysconfig.hpp>
+
 #include <json.hpp>
 
 namespace irccd {
--- a/libirccd-daemon/irccd/daemon/server.hpp	Mon Nov 19 07:04:42 2018 +0100
+++ b/libirccd-daemon/irccd/daemon/server.hpp	Fri Nov 23 21:50:20 2018 +0100
@@ -871,6 +871,10 @@
 };
 
 /**
+ * \cond IRCCD_HIDDEN_SYMBOLS
+ */
+
+/**
  * Get the server error category singleton.
  *
  * \return the singleton
@@ -887,10 +891,6 @@
 
 } // !irccd::daemon
 
-/**
- * \cond IRCCD_HIDDEN_SYMBOLS
- */
-
 namespace std {
 
 template <>
--- a/libirccd-daemon/irccd/daemon/server_service.cpp	Mon Nov 19 07:04:42 2018 +0100
+++ b/libirccd-daemon/irccd/daemon/server_service.cpp	Fri Nov 23 21:50:20 2018 +0100
@@ -16,6 +16,8 @@
  * OR IN CONNECTION WITH THE USE OR PERFORMANCE OF THIS SOFTWARE.
  */
 
+#include <irccd/sysconfig.hpp>
+
 #include <irccd/json_util.hpp>
 #include <irccd/string_util.hpp>
 
--- a/libirccd-daemon/irccd/daemon/server_service.hpp	Mon Nov 19 07:04:42 2018 +0100
+++ b/libirccd-daemon/irccd/daemon/server_service.hpp	Fri Nov 23 21:50:20 2018 +0100
@@ -24,6 +24,8 @@
  * \brief Server service.
  */
 
+#include <irccd/sysconfig.hpp>
+
 #include <memory>
 #include <system_error>
 #include <vector>
--- a/libirccd-daemon/irccd/daemon/server_util.cpp	Mon Nov 19 07:04:42 2018 +0100
+++ b/libirccd-daemon/irccd/daemon/server_util.cpp	Fri Nov 23 21:50:20 2018 +0100
@@ -16,6 +16,8 @@
  * OR IN CONNECTION WITH THE USE OR PERFORMANCE OF THIS SOFTWARE.
  */
 
+#include <irccd/sysconfig.hpp>
+
 #include <algorithm>
 
 #include <irccd/config.hpp>
--- a/libirccd-daemon/irccd/daemon/server_util.hpp	Mon Nov 19 07:04:42 2018 +0100
+++ b/libirccd-daemon/irccd/daemon/server_util.hpp	Fri Nov 23 21:50:20 2018 +0100
@@ -24,6 +24,8 @@
  * \brief Server utilities.
  */
 
+#include <irccd/sysconfig.hpp>
+
 #include <memory>
 #include <string_view>
 
--- a/libirccd-daemon/irccd/daemon/transport_client.cpp	Mon Nov 19 07:04:42 2018 +0100
+++ b/libirccd-daemon/irccd/daemon/transport_client.cpp	Fri Nov 23 21:50:20 2018 +0100
@@ -16,6 +16,8 @@
  * OR IN CONNECTION WITH THE USE OR PERFORMANCE OF THIS SOFTWARE.
  */
 
+#include <irccd/sysconfig.hpp>
+
 #include <cassert>
 
 #include "transport_client.hpp"
--- a/libirccd-daemon/irccd/daemon/transport_client.hpp	Mon Nov 19 07:04:42 2018 +0100
+++ b/libirccd-daemon/irccd/daemon/transport_client.hpp	Fri Nov 23 21:50:20 2018 +0100
@@ -24,6 +24,8 @@
  * \brief Server side transport clients.
  */
 
+#include <irccd/sysconfig.hpp>
+
 #include <deque>
 #include <memory>
 #include <string_view>
--- a/libirccd-daemon/irccd/daemon/transport_service.hpp	Mon Nov 19 07:04:42 2018 +0100
+++ b/libirccd-daemon/irccd/daemon/transport_service.hpp	Fri Nov 23 21:50:20 2018 +0100
@@ -24,6 +24,8 @@
  * \brief Transport service.
  */
 
+#include <irccd/sysconfig.hpp>
+
 #include <memory>
 #include <vector>
 
--- a/libirccd-daemon/irccd/daemon/transport_util.hpp	Mon Nov 19 07:04:42 2018 +0100
+++ b/libirccd-daemon/irccd/daemon/transport_util.hpp	Fri Nov 23 21:50:20 2018 +0100
@@ -29,6 +29,8 @@
  * \brief Transport utilities.
  */
 
+#include <irccd/sysconfig.hpp>
+
 #include <memory>
 
 #include <boost/asio/io_service.hpp>
--- a/libirccd-js/irccd/js/irccd_js_api.cpp	Mon Nov 19 07:04:42 2018 +0100
+++ b/libirccd-js/irccd/js/irccd_js_api.cpp	Fri Nov 23 21:50:20 2018 +0100
@@ -52,7 +52,7 @@
 
 // {{{ Irccd.SystemError [constructor]
 
-auto constructor(duk_context* ctx) -> duk_ret_t
+auto SystemError_constructor(duk_context* ctx) -> duk_ret_t
 {
 	duk_push_this(ctx);
 	duk_push_int(ctx, duk_require_int(ctx, 0));
@@ -208,7 +208,7 @@
 	duk_put_prop_string(plugin->get_context(), -2, "version");
 
 	// Create the system_error that inherits from Error.
-	duk_push_c_function(plugin->get_context(), constructor, 2);
+	duk_push_c_function(plugin->get_context(), SystemError_constructor, 2);
 
 	// Put errno codes into the irccd.system_error object.
 	for (const auto& [k, v] : errors) {
--- a/libirccd-js/irccd/js/js_api.cpp	Mon Nov 19 07:04:42 2018 +0100
+++ b/libirccd-js/irccd/js/js_api.cpp	Fri Nov 23 21:50:20 2018 +0100
@@ -33,7 +33,7 @@
 namespace {
 
 template <typename T>
-auto bind() noexcept -> js_api::factory
+auto bind() noexcept -> js_api::constructor
 {
 	return [] () noexcept {
 		return std::make_unique<T>();
@@ -42,19 +42,24 @@
 
 } // !namespace
 
-const std::vector<js_api::factory> js_api::registry{
-	// Irccd API must be loaded first.
-	bind<irccd_js_api>(),
-	bind<directory_js_api>(),
-	bind<elapsed_timer_js_api>(),
-	bind<file_js_api>(),
-	bind<logger_js_api>(),
-	bind<plugin_js_api>(),
-	bind<server_js_api>(),
-	bind<system_js_api>(),
-	bind<timer_js_api>(),
-	bind<unicode_js_api>(),
-	bind<util_js_api>()
-};
+auto js_api::registry() noexcept -> const std::vector<constructor>&
+{
+	static const std::vector<constructor> list {
+		// Irccd API must be loaded first.
+		bind<irccd_js_api>(),
+		bind<directory_js_api>(),
+		bind<elapsed_timer_js_api>(),
+		bind<file_js_api>(),
+		bind<logger_js_api>(),
+		bind<plugin_js_api>(),
+		bind<server_js_api>(),
+		bind<system_js_api>(),
+		bind<timer_js_api>(),
+		bind<unicode_js_api>(),
+		bind<util_js_api>()
+	};
+
+	return list;
+}
 
 } // !irccd::js
--- a/libirccd-js/irccd/js/js_api.hpp	Mon Nov 19 07:04:42 2018 +0100
+++ b/libirccd-js/irccd/js/js_api.hpp	Fri Nov 23 21:50:20 2018 +0100
@@ -52,12 +52,12 @@
 	/**
 	 * \brief Command constructor factory.
 	 */
-	using factory = std::function<auto () -> std::unique_ptr<js_api>>;
+	using constructor = std::function<std::unique_ptr<js_api> ()>;
 
 	/**
 	 * \brief Registry of all commands.
 	 */
-	static const std::vector<factory> registry;
+	static auto registry() noexcept -> const std::vector<constructor>&;
 
 	/**
 	 * Default constructor.
--- a/libirccd-test/irccd/test/cli_fixture.cpp	Mon Nov 19 07:04:42 2018 +0100
+++ b/libirccd-test/irccd/test/cli_fixture.cpp	Fri Nov 23 21:50:20 2018 +0100
@@ -64,7 +64,7 @@
 
 	auto acceptor = std::make_unique<ip_acceptor>(bot_.get_service(), std::move(raw_acceptor));
 
-	for (const auto& f : command::registry)
+	for (const auto& f : command::registry())
 		bot_.transports().get_commands().push_back(f());
 
 	bot_.servers().add(server_);
--- a/libirccd-test/irccd/test/command_fixture.cpp	Mon Nov 19 07:04:42 2018 +0100
+++ b/libirccd-test/irccd/test/command_fixture.cpp	Fri Nov 23 21:50:20 2018 +0100
@@ -72,7 +72,7 @@
 	auto connector = std::make_unique<ip_connector>(bot_.get_service(), "127.0.0.1", service, true, false);
 
 	// 1. Add all commands.
-	for (const auto& f : command::registry)
+	for (const auto& f : command::registry())
 		bot_.transports().get_commands().push_back(f());
 
 	// 2. Create controller and transport server.
--- a/libirccd-test/irccd/test/js_fixture.cpp	Mon Nov 19 07:04:42 2018 +0100
+++ b/libirccd-test/irccd/test/js_fixture.cpp	Fri Nov 23 21:50:20 2018 +0100
@@ -23,7 +23,7 @@
 js_fixture::js_fixture(const std::string& path)
 	: plugin_(new js::js_plugin("test", path))
 {
-	for (const auto& f : js::js_api::registry)
+	for (const auto& f : js::js_api::registry())
 		f()->load(bot_, plugin_);
 
 	if (!path.empty())
--- a/libirccd-test/irccd/test/js_plugin_fixture.cpp	Mon Nov 19 07:04:42 2018 +0100
+++ b/libirccd-test/irccd/test/js_plugin_fixture.cpp	Fri Nov 23 21:50:20 2018 +0100
@@ -57,7 +57,7 @@
 	server_->set_nickname("irccd");
 	server_->clear();
 
-	for (const auto& f : js::js_api::registry)
+	for (const auto& f : js::js_api::registry())
 		f()->load(bot_, plugin_);
 
 	plugin_->open();
--- a/libirccd/CMakeLists.txt	Mon Nov 19 07:04:42 2018 +0100
+++ b/libirccd/CMakeLists.txt	Fri Nov 23 21:50:20 2018 +0100
@@ -18,7 +18,7 @@
 
 project(libirccd)
 
-find_package(Boost 1.60 REQUIRED QUIET COMPONENTS filesystem system)
+find_package(Boost 1.60 REQUIRED QUIET COMPONENTS date_time filesystem system)
 
 set(
 	SOURCES
@@ -49,6 +49,9 @@
 	${CMAKE_DL_LIBS}
 	libjson
 	Threads::Threads
+	Boost::disable_autolinking
+	Boost::dynamic_linking
+	Boost::date_time
 	Boost::filesystem
 	Boost::system
 )
--- a/libirccd/irccd/config.hpp	Mon Nov 19 07:04:42 2018 +0100
+++ b/libirccd/irccd/config.hpp	Fri Nov 23 21:50:20 2018 +0100
@@ -24,6 +24,8 @@
  * \brief Read .ini configuration file for irccd
  */
 
+#include "sysconfig.hpp"
+
 #include <optional>
 #include <string_view>
 
--- a/libirccd/irccd/ini.cpp	Mon Nov 19 07:04:42 2018 +0100
+++ b/libirccd/irccd/ini.cpp	Fri Nov 23 21:50:20 2018 +0100
@@ -16,6 +16,8 @@
  * OR IN CONNECTION WITH THE USE OR PERFORMANCE OF THIS SOFTWARE.
  */
 
+#include "sysconfig.hpp"
+
 #include <cassert>
 #include <cctype>
 #include <cstring>
--- a/libirccd/irccd/ini.hpp	Mon Nov 19 07:04:42 2018 +0100
+++ b/libirccd/irccd/ini.hpp	Fri Nov 23 21:50:20 2018 +0100
@@ -105,6 +105,8 @@
  * ````
  */
 
+#include "sysconfig.hpp"
+
 #include <algorithm>
 #include <exception>
 #include <stdexcept>
--- a/libirccd/irccd/json_util.cpp	Mon Nov 19 07:04:42 2018 +0100
+++ b/libirccd/irccd/json_util.cpp	Fri Nov 23 21:50:20 2018 +0100
@@ -131,6 +131,11 @@
 	return value.get<std::uint64_t>();
 }
 
+deserializer::deserializer(const nlohmann::json& obj)
+	: nlohmann::json(obj)
+{
+}
+
 auto pretty(const json& value, int indent) -> std::string
 {
 	switch (value.type()) {
--- a/libirccd/irccd/json_util.hpp	Mon Nov 19 07:04:42 2018 +0100
+++ b/libirccd/irccd/json_util.hpp	Fri Nov 23 21:50:20 2018 +0100
@@ -227,10 +227,7 @@
 	 *
 	 * \param obj the JSON object
 	 */
-	deserializer(const nlohmann::json& obj)
-		: nlohmann::json(obj)
-	{
-	}
+	deserializer(const nlohmann::json& obj);
 
 	/**
 	 * Get a value from the document object.
--- a/libirccd/irccd/options.cpp	Mon Nov 19 07:04:42 2018 +0100
+++ b/libirccd/irccd/options.cpp	Fri Nov 23 21:50:20 2018 +0100
@@ -16,6 +16,8 @@
  * OR IN CONNECTION WITH THE USE OR PERFORMANCE OF THIS SOFTWARE.
  */
 
+#include "sysconfig.hpp"
+
 #include <cassert>
 
 #include "options.hpp"
--- a/libirccd/irccd/options.hpp	Mon Nov 19 07:04:42 2018 +0100
+++ b/libirccd/irccd/options.hpp	Fri Nov 23 21:50:20 2018 +0100
@@ -24,6 +24,8 @@
  * \brief Basic Unix options parser.
  */
 
+#include "sysconfig.hpp"
+
 #include <exception>
 #include <map>
 #include <string>
--- a/libirccd/irccd/string_util.cpp	Mon Nov 19 07:04:42 2018 +0100
+++ b/libirccd/irccd/string_util.cpp	Fri Nov 23 21:50:20 2018 +0100
@@ -16,9 +16,9 @@
  * OR IN CONNECTION WITH THE USE OR PERFORMANCE OF THIS SOFTWARE.
  */
 
-#include <boost/predef/os.h>
+#include "sysconfig.hpp"
 
-#include "sysconfig.hpp"
+#include <boost/predef/os.h>
 
 #if defined(IRCCD_HAVE_POPEN)
 #	include <array>
@@ -382,7 +382,7 @@
 
 auto strip(std::string str) noexcept -> std::string
 {
-	const auto test = [] (auto c) noexcept { return !std::isspace(c); };
+	const auto test = [] (auto c) noexcept { return !std::isspace(c, {}); };
 
 	str.erase(str.begin(), std::find_if(str.begin(), str.end(), test));
 	str.erase(std::find_if(str.rbegin(), str.rend(), test).base(), str.end());
--- a/libirccd/irccd/system.cpp	Mon Nov 19 07:04:42 2018 +0100
+++ b/libirccd/irccd/system.cpp	Fri Nov 23 21:50:20 2018 +0100
@@ -16,6 +16,8 @@
  * OR IN CONNECTION WITH THE USE OR PERFORMANCE OF THIS SOFTWARE.
  */
 
+#include "sysconfig.hpp"
+
 #include <cassert>
 #include <cerrno>
 #include <cstdlib>
@@ -28,8 +30,6 @@
 #include <boost/filesystem.hpp>
 #include <boost/predef/os.h>
 
-#include "sysconfig.hpp"
-
 #if BOOST_OS_WINDOWS
 #	include <sys/timeb.h>
 #	include <shlobj.h>
--- a/libirccd/irccd/system.hpp	Mon Nov 19 07:04:42 2018 +0100
+++ b/libirccd/irccd/system.hpp	Fri Nov 23 21:50:20 2018 +0100
@@ -24,6 +24,8 @@
  * \brief System dependant functions
  */
 
+#include "sysconfig.hpp"
+
 #include <cstdint>
 #include <string>
 #include <string_view>
@@ -31,8 +33,6 @@
 
 #include <boost/filesystem.hpp>
 
-#include "sysconfig.hpp"
-
 /**
  * \brief Namespace for system functions.
  */
--- a/plugins/CMakeLists.txt	Mon Nov 19 07:04:42 2018 +0100
+++ b/plugins/CMakeLists.txt	Fri Nov 23 21:50:20 2018 +0100
@@ -19,37 +19,30 @@
 project(plugins)
 
 set(
-	IRCCD_JAVASCRIPT_PLUGINS
+	IRCCD_PLUGINS
 	ask
 	auth
 	hangman
 	history
 	joke
+	links
 	logger
 	plugin
 	roulette
 	tictactoe
-)
-
-set(
-	IRCCD_NATIVE_PLUGINS
-	links
+	CACHE INTERNAL "All plugins"
 )
 
-set(
-	IRCCD_PLUGINS
-	${IRCCD_JAVASCRIPT_PLUGINS}
-	${IRCCD_NATIVE_PLUGINS}
-	CACHE INTERNAL ""
-)
-
-foreach (plugin ${IRCCD_JAVASCRIPT_PLUGINS})
-	irccd_define_plugin(
-		NAME ${plugin}
-		TYPE JS
-		SCRIPT ${plugins_SOURCE_DIR}/${plugin}/${plugin}.js
-		DOCS ${plugins_SOURCE_DIR}/${plugin}/${plugin}.md
-	)
-endforeach ()
+if (IRCCD_HAVE_JS)
+	add_subdirectory(ask)
+	add_subdirectory(auth)
+	add_subdirectory(hangman)
+	add_subdirectory(history)
+	add_subdirectory(joke)
+	add_subdirectory(logger)
+	add_subdirectory(plugin)
+	add_subdirectory(roulette)
+	add_subdirectory(tictactoe)
+endif ()
 
 add_subdirectory(links)
--- /dev/null	Thu Jan 01 00:00:00 1970 +0000
+++ b/plugins/ask/CMakeLists.txt	Fri Nov 23 21:50:20 2018 +0100
@@ -0,0 +1,26 @@
+#
+# CMakeLists.txt -- CMake build system for irccd
+#
+# Copyright (c) 2013-2018 David Demelier <markand@malikania.fr>
+#
+# Permission to use, copy, modify, and/or distribute this software for any
+# purpose with or without fee is hereby granted, provided that the above
+# copyright notice and this permission notice appear in all copies.
+#
+# THE SOFTWARE IS PROVIDED "AS IS" AND THE AUTHOR DISCLAIMS ALL WARRANTIES
+# WITH REGARD TO THIS SOFTWARE INCLUDING ALL IMPLIED WARRANTIES OF
+# MERCHANTABILITY AND FITNESS. IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR
+# ANY SPECIAL, DIRECT, INDIRECT, OR CONSEQUENTIAL DAMAGES OR ANY DAMAGES
+# WHATSOEVER RESULTING FROM LOSS OF USE, DATA OR PROFITS, WHETHER IN AN
+# ACTION OF CONTRACT, NEGLIGENCE OR OTHER TORTIOUS ACTION, ARISING OUT OF
+# OR IN CONNECTION WITH THE USE OR PERFORMANCE OF THIS SOFTWARE.
+#
+
+project(plugin-ask)
+
+irccd_define_plugin(
+	NAME ask
+	TYPE JS
+	SCRIPT ${plugin-ask_SOURCE_DIR}/ask.js
+	DOCS ${plugin-ask_SOURCE_DIR}/ask.md
+)
\ No newline at end of file
--- /dev/null	Thu Jan 01 00:00:00 1970 +0000
+++ b/plugins/auth/CMakeLists.txt	Fri Nov 23 21:50:20 2018 +0100
@@ -0,0 +1,26 @@
+#
+# CMakeLists.txt -- CMake build system for irccd
+#
+# Copyright (c) 2013-2018 David Demelier <markand@malikania.fr>
+#
+# Permission to use, copy, modify, and/or distribute this software for any
+# purpose with or without fee is hereby granted, provided that the above
+# copyright notice and this permission notice appear in all copies.
+#
+# THE SOFTWARE IS PROVIDED "AS IS" AND THE AUTHOR DISCLAIMS ALL WARRANTIES
+# WITH REGARD TO THIS SOFTWARE INCLUDING ALL IMPLIED WARRANTIES OF
+# MERCHANTABILITY AND FITNESS. IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR
+# ANY SPECIAL, DIRECT, INDIRECT, OR CONSEQUENTIAL DAMAGES OR ANY DAMAGES
+# WHATSOEVER RESULTING FROM LOSS OF USE, DATA OR PROFITS, WHETHER IN AN
+# ACTION OF CONTRACT, NEGLIGENCE OR OTHER TORTIOUS ACTION, ARISING OUT OF
+# OR IN CONNECTION WITH THE USE OR PERFORMANCE OF THIS SOFTWARE.
+#
+
+project(plugin-auth)
+
+irccd_define_plugin(
+	NAME auth
+	TYPE JS
+	SCRIPT ${plugin-auth_SOURCE_DIR}/auth.js
+	DOCS ${plugin-auth_SOURCE_DIR}/auth.md
+)
\ No newline at end of file
--- /dev/null	Thu Jan 01 00:00:00 1970 +0000
+++ b/plugins/hangman/CMakeLists.txt	Fri Nov 23 21:50:20 2018 +0100
@@ -0,0 +1,26 @@
+#
+# CMakeLists.txt -- CMake build system for irccd
+#
+# Copyright (c) 2013-2018 David Demelier <markand@malikania.fr>
+#
+# Permission to use, copy, modify, and/or distribute this software for any
+# purpose with or without fee is hereby granted, provided that the above
+# copyright notice and this permission notice appear in all copies.
+#
+# THE SOFTWARE IS PROVIDED "AS IS" AND THE hangmanOR DISCLAIMS ALL WARRANTIES
+# WITH REGARD TO THIS SOFTWARE INCLUDING ALL IMPLIED WARRANTIES OF
+# MERCHANTABILITY AND FITNESS. IN NO EVENT SHALL THE hangmanOR BE LIABLE FOR
+# ANY SPECIAL, DIRECT, INDIRECT, OR CONSEQUENTIAL DAMAGES OR ANY DAMAGES
+# WHATSOEVER RESULTING FROM LOSS OF USE, DATA OR PROFITS, WHETHER IN AN
+# ACTION OF CONTRACT, NEGLIGENCE OR OTHER TORTIOUS ACTION, ARISING OUT OF
+# OR IN CONNECTION WITH THE USE OR PERFORMANCE OF THIS SOFTWARE.
+#
+
+project(plugin-hangman)
+
+irccd_define_plugin(
+	NAME hangman
+	TYPE JS
+	SCRIPT ${plugin-hangman_SOURCE_DIR}/hangman.js
+	DOCS ${plugin-hangman_SOURCE_DIR}/hangman.md
+)
\ No newline at end of file
--- /dev/null	Thu Jan 01 00:00:00 1970 +0000
+++ b/plugins/history/CMakeLists.txt	Fri Nov 23 21:50:20 2018 +0100
@@ -0,0 +1,26 @@
+#
+# CMakeLists.txt -- CMake build system for irccd
+#
+# Copyright (c) 2013-2018 David Demelier <markand@malikania.fr>
+#
+# Permission to use, copy, modify, and/or distribute this software for any
+# purpose with or without fee is hereby granted, provided that the above
+# copyright notice and this permission notice appear in all copies.
+#
+# THE SOFTWARE IS PROVIDED "AS IS" AND THE historyOR DISCLAIMS ALL WARRANTIES
+# WITH REGARD TO THIS SOFTWARE INCLUDING ALL IMPLIED WARRANTIES OF
+# MERCHANTABILITY AND FITNESS. IN NO EVENT SHALL THE historyOR BE LIABLE FOR
+# ANY SPECIAL, DIRECT, INDIRECT, OR CONSEQUENTIAL DAMAGES OR ANY DAMAGES
+# WHATSOEVER RESULTING FROM LOSS OF USE, DATA OR PROFITS, WHETHER IN AN
+# ACTION OF CONTRACT, NEGLIGENCE OR OTHER TORTIOUS ACTION, ARISING OUT OF
+# OR IN CONNECTION WITH THE USE OR PERFORMANCE OF THIS SOFTWARE.
+#
+
+project(plugin-history)
+
+irccd_define_plugin(
+	NAME history
+	TYPE JS
+	SCRIPT ${plugin-history_SOURCE_DIR}/history.js
+	DOCS ${plugin-history_SOURCE_DIR}/history.md
+)
\ No newline at end of file
--- /dev/null	Thu Jan 01 00:00:00 1970 +0000
+++ b/plugins/joke/CMakeLists.txt	Fri Nov 23 21:50:20 2018 +0100
@@ -0,0 +1,26 @@
+#
+# CMakeLists.txt -- CMake build system for irccd
+#
+# Copyright (c) 2013-2018 David Demelier <markand@malikania.fr>
+#
+# Permission to use, copy, modify, and/or distribute this software for any
+# purpose with or without fee is hereby granted, provided that the above
+# copyright notice and this permission notice appear in all copies.
+#
+# THE SOFTWARE IS PROVIDED "AS IS" AND THE jokeOR DISCLAIMS ALL WARRANTIES
+# WITH REGARD TO THIS SOFTWARE INCLUDING ALL IMPLIED WARRANTIES OF
+# MERCHANTABILITY AND FITNESS. IN NO EVENT SHALL THE jokeOR BE LIABLE FOR
+# ANY SPECIAL, DIRECT, INDIRECT, OR CONSEQUENTIAL DAMAGES OR ANY DAMAGES
+# WHATSOEVER RESULTING FROM LOSS OF USE, DATA OR PROFITS, WHETHER IN AN
+# ACTION OF CONTRACT, NEGLIGENCE OR OTHER TORTIOUS ACTION, ARISING OUT OF
+# OR IN CONNECTION WITH THE USE OR PERFORMANCE OF THIS SOFTWARE.
+#
+
+project(plugin-joke)
+
+irccd_define_plugin(
+	NAME joke
+	TYPE JS
+	SCRIPT ${plugin-joke_SOURCE_DIR}/joke.js
+	DOCS ${plugin-joke_SOURCE_DIR}/joke.md
+)
\ No newline at end of file
--- /dev/null	Thu Jan 01 00:00:00 1970 +0000
+++ b/plugins/logger/CMakeLists.txt	Fri Nov 23 21:50:20 2018 +0100
@@ -0,0 +1,26 @@
+#
+# CMakeLists.txt -- CMake build system for irccd
+#
+# Copyright (c) 2013-2018 David Demelier <markand@malikania.fr>
+#
+# Permission to use, copy, modify, and/or distribute this software for any
+# purpose with or without fee is hereby granted, provided that the above
+# copyright notice and this permission notice appear in all copies.
+#
+# THE SOFTWARE IS PROVIDED "AS IS" AND THE loggerOR DISCLAIMS ALL WARRANTIES
+# WITH REGARD TO THIS SOFTWARE INCLUDING ALL IMPLIED WARRANTIES OF
+# MERCHANTABILITY AND FITNESS. IN NO EVENT SHALL THE loggerOR BE LIABLE FOR
+# ANY SPECIAL, DIRECT, INDIRECT, OR CONSEQUENTIAL DAMAGES OR ANY DAMAGES
+# WHATSOEVER RESULTING FROM LOSS OF USE, DATA OR PROFITS, WHETHER IN AN
+# ACTION OF CONTRACT, NEGLIGENCE OR OTHER TORTIOUS ACTION, ARISING OUT OF
+# OR IN CONNECTION WITH THE USE OR PERFORMANCE OF THIS SOFTWARE.
+#
+
+project(plugin-logger)
+
+irccd_define_plugin(
+	NAME logger
+	TYPE JS
+	SCRIPT ${plugin-logger_SOURCE_DIR}/logger.js
+	DOCS ${plugin-logger_SOURCE_DIR}/logger.md
+)
\ No newline at end of file
--- /dev/null	Thu Jan 01 00:00:00 1970 +0000
+++ b/plugins/plugin/CMakeLists.txt	Fri Nov 23 21:50:20 2018 +0100
@@ -0,0 +1,26 @@
+#
+# CMakeLists.txt -- CMake build system for irccd
+#
+# Copyright (c) 2013-2018 David Demelier <markand@malikania.fr>
+#
+# Permission to use, copy, modify, and/or distribute this software for any
+# purpose with or without fee is hereby granted, provided that the above
+# copyright notice and this permission notice appear in all copies.
+#
+# THE SOFTWARE IS PROVIDED "AS IS" AND THE pluginOR DISCLAIMS ALL WARRANTIES
+# WITH REGARD TO THIS SOFTWARE INCLUDING ALL IMPLIED WARRANTIES OF
+# MERCHANTABILITY AND FITNESS. IN NO EVENT SHALL THE pluginOR BE LIABLE FOR
+# ANY SPECIAL, DIRECT, INDIRECT, OR CONSEQUENTIAL DAMAGES OR ANY DAMAGES
+# WHATSOEVER RESULTING FROM LOSS OF USE, DATA OR PROFITS, WHETHER IN AN
+# ACTION OF CONTRACT, NEGLIGENCE OR OTHER TORTIOUS ACTION, ARISING OUT OF
+# OR IN CONNECTION WITH THE USE OR PERFORMANCE OF THIS SOFTWARE.
+#
+
+project(plugin-plugin)
+
+irccd_define_plugin(
+	NAME plugin
+	TYPE JS
+	SCRIPT ${plugin-plugin_SOURCE_DIR}/plugin.js
+	DOCS ${plugin-plugin_SOURCE_DIR}/plugin.md
+)
\ No newline at end of file
--- /dev/null	Thu Jan 01 00:00:00 1970 +0000
+++ b/plugins/roulette/CMakeLists.txt	Fri Nov 23 21:50:20 2018 +0100
@@ -0,0 +1,26 @@
+#
+# CMakeLists.txt -- CMake build system for irccd
+#
+# Copyright (c) 2013-2018 David Demelier <markand@malikania.fr>
+#
+# Permission to use, copy, modify, and/or distribute this software for any
+# purpose with or without fee is hereby granted, provided that the above
+# copyright notice and this permission notice appear in all copies.
+#
+# THE SOFTWARE IS PROVIDED "AS IS" AND THE pluginOR DISCLAIMS ALL WARRANTIES
+# WITH REGARD TO THIS SOFTWARE INCLUDING ALL IMPLIED WARRANTIES OF
+# MERCHANTABILITY AND FITNESS. IN NO EVENT SHALL THE pluginOR BE LIABLE FOR
+# ANY SPECIAL, DIRECT, INDIRECT, OR CONSEQUENTIAL DAMAGES OR ANY DAMAGES
+# WHATSOEVER RESULTING FROM LOSS OF USE, DATA OR PROFITS, WHETHER IN AN
+# ACTION OF CONTRACT, NEGLIGENCE OR OTHER TORTIOUS ACTION, ARISING OUT OF
+# OR IN CONNECTION WITH THE USE OR PERFORMANCE OF THIS SOFTWARE.
+#
+
+project(plugin-roulette)
+
+irccd_define_plugin(
+	NAME roulette
+	TYPE JS
+	SCRIPT ${plugin-roulette_SOURCE_DIR}/roulette.js
+	DOCS ${plugin-roulette_SOURCE_DIR}/roulette.md
+)
\ No newline at end of file
--- /dev/null	Thu Jan 01 00:00:00 1970 +0000
+++ b/plugins/tictactoe/CMakeLists.txt	Fri Nov 23 21:50:20 2018 +0100
@@ -0,0 +1,26 @@
+#
+# CMakeLists.txt -- CMake build system for irccd
+#
+# Copyright (c) 2013-2018 David Demelier <markand@malikania.fr>
+#
+# Permission to use, copy, modify, and/or distribute this software for any
+# purpose with or without fee is hereby granted, provided that the above
+# copyright notice and this permission notice appear in all copies.
+#
+# THE SOFTWARE IS PROVIDED "AS IS" AND THE pluginOR DISCLAIMS ALL WARRANTIES
+# WITH REGARD TO THIS SOFTWARE INCLUDING ALL IMPLIED WARRANTIES OF
+# MERCHANTABILITY AND FITNESS. IN NO EVENT SHALL THE pluginOR BE LIABLE FOR
+# ANY SPECIAL, DIRECT, INDIRECT, OR CONSEQUENTIAL DAMAGES OR ANY DAMAGES
+# WHATSOEVER RESULTING FROM LOSS OF USE, DATA OR PROFITS, WHETHER IN AN
+# ACTION OF CONTRACT, NEGLIGENCE OR OTHER TORTIOUS ACTION, ARISING OUT OF
+# OR IN CONNECTION WITH THE USE OR PERFORMANCE OF THIS SOFTWARE.
+#
+
+project(plugin-tictactoe)
+
+irccd_define_plugin(
+	NAME tictactoe
+	TYPE JS
+	SCRIPT ${plugin-tictactoe_SOURCE_DIR}/tictactoe.js
+	DOCS ${plugin-tictactoe_SOURCE_DIR}/tictactoe.md
+)
\ No newline at end of file
--- a/tests/src/libirccd-daemon/dynlib-plugin/CMakeLists.txt	Mon Nov 19 07:04:42 2018 +0100
+++ b/tests/src/libirccd-daemon/dynlib-plugin/CMakeLists.txt	Fri Nov 23 21:50:20 2018 +0100
@@ -18,21 +18,25 @@
 
 find_package(Boost REQUIRED QUIET)
 
-add_library(test-plugin MODULE test_plugin.cpp)
-target_link_libraries(test-plugin libirccd-daemon Boost::boost)
+add_library(sample-plugin MODULE sample_plugin.cpp)
+target_link_libraries(sample-plugin libirccd-test Boost::boost)
+
 set_target_properties(
-	test-plugin
+	sample-plugin
 	PROPERTIES
 		PREFIX ""
+		FOLDER "test"
 		RUNTIME_OUTPUT_DIRECTORY ${CMAKE_CURRENT_BINARY_DIR}
+		LIBRARY_OUTPUT_DIRECTORY ${CMAKE_CURRENT_BINARY_DIR}
 )
 
-foreach (c ${CMAKE_CONFIGURATION_TYPES})
-	string(TOUPPER ${c} c)
+foreach (cfg ${CMAKE_CONFIGURATION_TYPES})
+	string(TOUPPER ${cfg} cfg)
 	set_target_properties(
-		test-plugin
+		sample-plugin
 		PROPERTIES
-			RUNTIME_OUTPUT_DIRECTORY_${c} ${CMAKE_CURRENT_BINARY_DIR}
+			RUNTIME_OUTPUT_DIRECTORY_${cfg} ${CMAKE_CURRENT_BINARY_DIR}
+			LIBRARY_OUTPUT_DIRECTORY_${cfg} ${CMAKE_CURRENT_BINARY_DIR}
 	)
 endforeach ()
 
@@ -40,5 +44,5 @@
 	NAME dynlib-plugin
 	SOURCES main.cpp
 	LIBRARIES libirccd libirccd-test
-	DEPENDS test-plugin
+	DEPENDS sample-plugin
 )
--- a/tests/src/libirccd-daemon/dynlib-plugin/main.cpp	Mon Nov 19 07:04:42 2018 +0100
+++ b/tests/src/libirccd-daemon/dynlib-plugin/main.cpp	Fri Nov 23 21:50:20 2018 +0100
@@ -28,9 +28,12 @@
 #include <irccd/daemon/dynlib_plugin.hpp>
 #include <irccd/daemon/server.hpp>
 
+#include <irccd/test/mock_plugin.hpp>
+
 using irccd::daemon::bot;
 using irccd::daemon::dynlib_plugin_loader;
-using irccd::daemon::plugin;
+
+using irccd::test::mock_plugin;
 
 namespace irccd {
 
@@ -39,15 +42,17 @@
 class fixture {
 protected:
 	boost::asio::io_service service_;
-	std::shared_ptr<plugin> plugin_;
+	std::shared_ptr<mock_plugin> plugin_;
 	bot bot_{service_};
 
 	fixture()
 	{
-		plugin_ = dynlib_plugin_loader({CMAKE_CURRENT_BINARY_DIR}).find("test-plugin");
+		auto plugin = dynlib_plugin_loader({CMAKE_CURRENT_BINARY_DIR}).find("sample-plugin");
 
-		if (!plugin_)
-			throw std::runtime_error("test plugin not found");
+		if (!plugin)
+			throw std::runtime_error("sample not found");
+
+		plugin_ = std::dynamic_pointer_cast<mock_plugin>(plugin);
 	}
 };
 
@@ -57,136 +62,119 @@
 {
 	plugin_->handle_command(bot_, {});
 
-	BOOST_TEST(plugin_->get_options().size() == 1U);
-	BOOST_TEST(plugin_->get_options()["command"] == "true");
+	BOOST_TEST(plugin_->find("handle_command").size() == 1U);
 }
 
 BOOST_AUTO_TEST_CASE(handle_connect)
 {
 	plugin_->handle_connect(bot_, {});
 
-	BOOST_TEST(plugin_->get_options().size() == 1U);
-	BOOST_TEST(plugin_->get_options()["connect"] == "true");
+	BOOST_TEST(plugin_->find("handle_connect").size() == 1U);
 }
 
 BOOST_AUTO_TEST_CASE(handle_invite)
 {
 	plugin_->handle_invite(bot_, {});
 
-	BOOST_TEST(plugin_->get_options().size() == 1U);
-	BOOST_TEST(plugin_->get_options()["invite"] == "true");
+	BOOST_TEST(plugin_->find("handle_invite").size() == 1U);
 }
 
 BOOST_AUTO_TEST_CASE(handle_join)
 {
 	plugin_->handle_join(bot_, {});
 
-	BOOST_TEST(plugin_->get_options().size() == 1U);
-	BOOST_TEST(plugin_->get_options()["join"] == "true");
+	BOOST_TEST(plugin_->find("handle_join").size() == 1U);
 }
 
 BOOST_AUTO_TEST_CASE(handle_kick)
 {
 	plugin_->handle_kick(bot_, {});
 
-	BOOST_TEST(plugin_->get_options().size() == 1U);
-	BOOST_TEST(plugin_->get_options()["kick"] == "true");
+	BOOST_TEST(plugin_->find("handle_kick").size() == 1U);
 }
 
 BOOST_AUTO_TEST_CASE(handle_load)
 {
 	plugin_->handle_load(bot_);
 
-	BOOST_TEST(plugin_->get_options().size() == 1U);
-	BOOST_TEST(plugin_->get_options()["load"] == "true");
+	BOOST_TEST(plugin_->find("handle_load").size() == 1U);
 }
 
 BOOST_AUTO_TEST_CASE(handle_message)
 {
 	plugin_->handle_message(bot_, {});
 
-	BOOST_TEST(plugin_->get_options().size() == 1U);
-	BOOST_TEST(plugin_->get_options()["message"] == "true");
+	BOOST_TEST(plugin_->find("handle_message").size() == 1U);
 }
 
 BOOST_AUTO_TEST_CASE(handle_me)
 {
 	plugin_->handle_me(bot_, {});
 
-	BOOST_TEST(plugin_->get_options().size() == 1U);
-	BOOST_TEST(plugin_->get_options()["me"] == "true");
+	BOOST_TEST(plugin_->find("handle_me").size() == 1U);
 }
 
 BOOST_AUTO_TEST_CASE(handle_mode)
 {
 	plugin_->handle_mode(bot_, {});
 
-	BOOST_TEST(plugin_->get_options().size() == 1U);
-	BOOST_TEST(plugin_->get_options()["mode"] == "true");
+	BOOST_TEST(plugin_->find("handle_mode").size() == 1U);
 }
 
 BOOST_AUTO_TEST_CASE(handle_names)
 {
 	plugin_->handle_names(bot_, {});
 
-	BOOST_TEST(plugin_->get_options().size() == 1U);
-	BOOST_TEST(plugin_->get_options()["names"] == "true");
+	BOOST_TEST(plugin_->find("handle_names").size() == 1U);
 }
 
 BOOST_AUTO_TEST_CASE(handle_nick)
 {
 	plugin_->handle_nick(bot_, {});
 
-	BOOST_TEST(plugin_->get_options().size() == 1U);
-	BOOST_TEST(plugin_->get_options()["nick"] == "true");
+	BOOST_TEST(plugin_->find("handle_nick").size() == 1U);
 }
 
 BOOST_AUTO_TEST_CASE(handle_notice)
 {
 	plugin_->handle_notice(bot_, {});
 
-	BOOST_TEST(plugin_->get_options().size() == 1U);
-	BOOST_TEST(plugin_->get_options()["notice"] == "true");
+	BOOST_TEST(plugin_->find("handle_notice").size() == 1U);
 }
 
 BOOST_AUTO_TEST_CASE(handle_part)
 {
 	plugin_->handle_part(bot_, {});
 
-	BOOST_TEST(plugin_->get_options().size() == 1U);
-	BOOST_TEST(plugin_->get_options()["part"] == "true");
+	BOOST_TEST(plugin_->find("handle_part").size() == 1U);
 }
 
 BOOST_AUTO_TEST_CASE(handle_reload)
 {
 	plugin_->handle_reload(bot_);
 
-	BOOST_TEST(plugin_->get_options().size() == 1U);
-	BOOST_TEST(plugin_->get_options()["reload"] == "true");
+	BOOST_TEST(plugin_->find("handle_reload").size() == 1U);
 }
 
 BOOST_AUTO_TEST_CASE(handle_topic)
 {
 	plugin_->handle_topic(bot_, {});
 
-	BOOST_TEST(plugin_->get_options().size() == 1U);
-	BOOST_TEST(plugin_->get_options()["topic"] == "true");
+	BOOST_TEST(plugin_->find("handle_topic").size() == 1U);
 }
 
 BOOST_AUTO_TEST_CASE(handle_unload)
 {
 	plugin_->handle_unload(bot_);
 
-	BOOST_TEST(plugin_->get_options().size() == 1U);
-	BOOST_TEST(plugin_->get_options()["unload"] == "true");
+	BOOST_TEST(plugin_->find("handle_unload").size() == 1U);
 }
 
 BOOST_AUTO_TEST_CASE(handle_whois)
 {
 	plugin_->handle_whois(bot_, {});
 
-	BOOST_TEST(plugin_->get_options().size() == 1U);
-	BOOST_TEST(plugin_->get_options()["whois"] == "true");
+	BOOST_TEST(plugin_->find("handle_whois").size() == 1U);
 }
 
 BOOST_AUTO_TEST_SUITE_END()
--- /dev/null	Thu Jan 01 00:00:00 1970 +0000
+++ b/tests/src/libirccd-daemon/dynlib-plugin/sample_plugin.cpp	Fri Nov 23 21:50:20 2018 +0100
@@ -0,0 +1,48 @@
+/*
+ * sample_plugin.cpp -- basic exported plugin test
+ *
+ * Copyright (c) 2013-2018 David Demelier <markand@malikania.fr>
+ *
+ * Permission to use, copy, modify, and/or distribute this software for any
+ * purpose with or without fee is hereby granted, provided that the above
+ * copyright notice and this permission notice appear in all copies.
+ *
+ * THE SOFTWARE IS PROVIDED "AS IS" AND THE AUTHOR DISCLAIMS ALL WARRANTIES
+ * WITH REGARD TO THIS SOFTWARE INCLUDING ALL IMPLIED WARRANTIES OF
+ * MERCHANTABILITY AND FITNESS. IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR
+ * ANY SPECIAL, DIRECT, INDIRECT, OR CONSEQUENTIAL DAMAGES OR ANY DAMAGES
+ * WHATSOEVER RESULTING FROM LOSS OF USE, DATA OR PROFITS, WHETHER IN AN
+ * ACTION OF CONTRACT, NEGLIGENCE OR OTHER TORTIOUS ACTION, ARISING OUT OF
+ * OR IN CONNECTION WITH THE USE OR PERFORMANCE OF THIS SOFTWARE.
+ */
+
+#include <irccd/daemon/dynlib_plugin.hpp>
+
+#include <irccd/test/mock_plugin.hpp>
+
+using irccd::test::mock_plugin;
+
+namespace irccd {
+
+class sample_plugin : public mock_plugin {
+public:
+	sample_plugin()
+		: mock_plugin("test")
+	{
+	}
+
+	static auto abi() -> version
+	{
+		return version();
+	}
+
+	static auto init(std::string) -> std::unique_ptr<plugin>
+	{
+		return std::make_unique<sample_plugin>();
+	}
+};
+
+BOOST_DLL_ALIAS(sample_plugin::abi, irccd_abi_sample_plugin)
+BOOST_DLL_ALIAS(sample_plugin::init, irccd_init_sample_plugin)
+
+} // !irccd
--- a/tests/src/libirccd-daemon/dynlib-plugin/test_plugin.cpp	Mon Nov 19 07:04:42 2018 +0100
+++ /dev/null	Thu Jan 01 00:00:00 1970 +0000
@@ -1,160 +0,0 @@
-/*
- * test_plugin.cpp -- basic exported plugin test
- *
- * Copyright (c) 2013-2018 David Demelier <markand@malikania.fr>
- *
- * Permission to use, copy, modify, and/or distribute this software for any
- * purpose with or without fee is hereby granted, provided that the above
- * copyright notice and this permission notice appear in all copies.
- *
- * THE SOFTWARE IS PROVIDED "AS IS" AND THE AUTHOR DISCLAIMS ALL WARRANTIES
- * WITH REGARD TO THIS SOFTWARE INCLUDING ALL IMPLIED WARRANTIES OF
- * MERCHANTABILITY AND FITNESS. IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR
- * ANY SPECIAL, DIRECT, INDIRECT, OR CONSEQUENTIAL DAMAGES OR ANY DAMAGES
- * WHATSOEVER RESULTING FROM LOSS OF USE, DATA OR PROFITS, WHETHER IN AN
- * ACTION OF CONTRACT, NEGLIGENCE OR OTHER TORTIOUS ACTION, ARISING OUT OF
- * OR IN CONNECTION WITH THE USE OR PERFORMANCE OF THIS SOFTWARE.
- */
-
-#include <irccd/daemon/dynlib_plugin.hpp>
-
-using irccd::daemon::bot;
-using irccd::daemon::connect_event;
-using irccd::daemon::disconnect_event;
-using irccd::daemon::invite_event;
-using irccd::daemon::join_event;
-using irccd::daemon::kick_event;
-using irccd::daemon::me_event;
-using irccd::daemon::message_event;
-using irccd::daemon::mode_event;
-using irccd::daemon::names_event;
-using irccd::daemon::nick_event;
-using irccd::daemon::notice_event;
-using irccd::daemon::part_event;
-using irccd::daemon::plugin;
-using irccd::daemon::plugin_error;
-using irccd::daemon::topic_event;
-using irccd::daemon::whois_event;
-
-namespace irccd {
-
-class test_plugin : public plugin {
-private:
-	map config_;
-
-public:
-	test_plugin()
-		: plugin("test")
-	{
-	}
-
-	auto get_options() const -> map override
-	{
-		return config_;
-	}
-
-	auto get_name() const noexcept -> std::string_view override
-	{
-		return "test";
-	}
-
-	void handle_command(bot&, const message_event&) override
-	{
-		config_["command"] = "true";
-	}
-
-	void handle_connect(bot&, const connect_event&) override
-	{
-		config_["connect"] = "true";
-	}
-
-	void handle_invite(bot&, const invite_event&) override
-	{
-		config_["invite"] = "true";
-	}
-
-	void handle_join(bot&, const join_event&) override
-	{
-		config_["join"] = "true";
-	}
-
-	void handle_kick(bot&, const kick_event&) override
-	{
-		config_["kick"] = "true";
-	}
-
-	void handle_load(bot&) override
-	{
-		config_["load"] = "true";
-	}
-
-	void handle_message(bot&, const message_event&) override
-	{
-		config_["message"] = "true";
-	}
-
-	void handle_me(bot&, const me_event&) override
-	{
-		config_["me"] = "true";
-	}
-
-	void handle_mode(bot&, const mode_event&) override
-	{
-		config_["mode"] = "true";
-	}
-
-	void handle_names(bot&, const names_event&) override
-	{
-		config_["names"] = "true";
-	}
-
-	void handle_nick(bot&, const nick_event&) override
-	{
-		config_["nick"] = "true";
-	}
-
-	void handle_notice(bot&, const notice_event&) override
-	{
-		config_["notice"] = "true";
-	}
-
-	void handle_part(bot&, const part_event&) override
-	{
-		config_["part"] = "true";
-	}
-
-	void handle_reload(bot&) override
-	{
-		config_["reload"] = "true";
-	}
-
-	void handle_topic(bot&, const topic_event&) override
-	{
-		config_["topic"] = "true";
-	}
-
-	void handle_unload(bot&) override
-	{
-		config_["unload"] = "true";
-	}
-
-	void handle_whois(bot&, const whois_event&) override
-	{
-		config_["whois"] = "true";
-	}
-
-	static auto abi() -> version
-	{
-		return version();
-	}
-
-	static auto init(std::string) -> std::unique_ptr<plugin>
-	{
-		return std::make_unique<test_plugin>();
-	}
-};
-
-BOOST_DLL_ALIAS(test_plugin::abi, irccd_abi_test_plugin)
-BOOST_DLL_ALIAS(test_plugin::init, irccd_init_test_plugin)
-
-} // !irccd
--- a/tests/src/libirccd-js/js-plugin/main.cpp	Mon Nov 19 07:04:42 2018 +0100
+++ b/tests/src/libirccd-js/js-plugin/main.cpp	Fri Nov 23 21:50:20 2018 +0100
@@ -45,7 +45,7 @@
 	{
 		plugin_ = std::make_unique<js_plugin>("test", path);
 
-		for (const auto& f : js_api::registry)
+		for (const auto& f : js_api::registry())
 			f()->load(bot_, plugin_);
 
 		plugin_->open();
@@ -111,7 +111,7 @@
 
 		auto loader = std::make_unique<js::js_plugin_loader>(bot_);
 
-		for (const auto& f : js::js_api::registry)
+		for (const auto& f : js::js_api::registry())
 			loader->get_modules().push_back(f());
 
 		bot_.plugins().add_loader(std::move(loader));