From d06ef76e58e1fed9d7ec037ee75fad4cba934ccd Mon Sep 17 00:00:00 2001
From: Max Kellermann <max@musicpd.org>
Date: Tue, 17 Jul 2018 20:12:42 +0200
Subject: [PATCH] config/File: implement the "include" directive

This is an experimental draft.  More needs to be done.
---
 NEWS                |  2 ++
 doc/user.xml        |  8 ++++++++
 src/config/File.cxx | 18 ++++++++++++++++--
 3 files changed, 26 insertions(+), 2 deletions(-)

diff --git a/NEWS b/NEWS
index e586519e3..97499a0b3 100644
--- a/NEWS
+++ b/NEWS
@@ -1,4 +1,6 @@
 ver 0.21 (not yet released)
+* configuration
+  - add "include" directive, allows including config files
 * protocol
   - "tagtypes" can be used to hide tags
   - "find" and "search" can sort
diff --git a/doc/user.xml b/doc/user.xml
index dad61f286..a2d7a3d04 100644
--- a/doc/user.xml
+++ b/doc/user.xml
@@ -410,6 +410,14 @@ systemctl start mpd.socket</programlisting>
     device "iec958:CARD=Intel,DEV=0"
     mixer_control "PCM"
 }</programlisting>
+
+      <para>
+        The <varname>include</varname> directive can be used to
+        include settings from another file; the given file name is
+        relative to the current file:
+      </para>
+
+      <programlisting>include "other.conf"</programlisting>
     </section>
 
     <section id="config_music_directory">
diff --git a/src/config/File.cxx b/src/config/File.cxx
index 29c073f3b..7b6838bf2 100644
--- a/src/config/File.cxx
+++ b/src/config/File.cxx
@@ -25,6 +25,7 @@
 #include "Templates.hxx"
 #include "util/Tokenizer.hxx"
 #include "util/StringStrip.hxx"
+#include "util/StringAPI.hxx"
 #include "util/Domain.hxx"
 #include "util/RuntimeError.hxx"
 #include "fs/Path.hxx"
@@ -152,8 +153,11 @@ ReadConfigParam(ConfigData &config_data, BufferedReader &reader,
 					    reader.GetLineNumber()));
 }
 
+/**
+ * @param directory the directory used to resolve relative paths
+ */
 static void
-ReadConfigFile(ConfigData &config_data, BufferedReader &reader)
+ReadConfigFile(ConfigData &config_data, BufferedReader &reader, Path directory)
 {
 	while (true) {
 		char *line = reader.ReadLine();
@@ -171,6 +175,16 @@ ReadConfigFile(ConfigData &config_data, BufferedReader &reader)
 		const char *name = tokenizer.NextWord();
 		assert(name != nullptr);
 
+		if (StringIsEqual(name, "include")) {
+			// TODO: allow absolute path specifications
+			// TODO: detect recusion
+			// TODO: Config{Block,Param} have only line number but no file name
+			// TODO: support wildcards (include "conf.d/*.conf")
+			// TODO: add "include_optional"
+			ReadConfigFile(config_data, directory / AllocatedPath::FromUTF8Throw(ExpectValueAndEnd(tokenizer)));
+			continue;
+		}
+
 		/* get the definition of that option, and check the
 		   "repeatable" flag */
 
@@ -202,7 +216,7 @@ ReadConfigFile(ConfigData &config_data, Path path)
 	BufferedReader reader(file);
 
 	try {
-		ReadConfigFile(config_data, reader);
+		ReadConfigFile(config_data, reader, path.GetDirectoryName());
 	} catch (...) {
 		std::throw_with_nested(FormatRuntimeError("Error in %s line %u",
 							  path_utf8.c_str(),