Merge pull request #506 from andrewcsmith/patch-2
[supercollider.git] / lang / LangSource / SC_LibraryConfig.cpp
blob69407e7b996924cdc82428d2dd8613805c3fb440
1 /*
2 * Copyright 2003 Maurizio Umberto Puxeddu
3 * Copyright 2011 Jakob Leben
5 * This file is part of SuperCollider.
7 * This program is free software; you can redistribute it and/or
8 * modify it under the terms of the GNU General Public License as
9 * published by the Free Software Foundation; either version 2 of the
10 * License, or (at your option) any later version.
12 * This program is distributed in the hope that it will be useful, but
13 * WITHOUT ANY WARRANTY; without even the implied warranty of
14 * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
15 * General Public License for more details.
17 * You should have received a copy of the GNU General Public License
18 * along with this program; if not, write to the Free Software
19 * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301
20 * USA
24 #include "SC_LibraryConfig.h"
25 #include "SCBase.h"
26 #include "SC_StringBuffer.h"
27 #include "SC_DirUtils.h"
29 #include <assert.h>
30 #include <ctype.h>
31 #include <string.h>
32 #include <stdlib.h>
33 #include <string.h>
34 #include <limits.h>
35 #ifdef SC_WIN32
36 # include "SC_Win32Utils.h"
37 #else
38 # include <sys/param.h>
39 # include <unistd.h>
40 # include <libgen.h>
41 #endif
43 #include <fstream>
44 #include "yaml-cpp/yaml.h"
46 using namespace std;
48 SC_LanguageConfig *gLibraryConfig = 0;
50 void SC_LanguageConfig::postExcludedDirectories(void)
52 DirVector &vec = mExcludedDirectories;
53 DirVector::iterator it;
54 for (it=vec.begin(); it!=vec.end(); ++it) {
55 post("\texcluding dir: '%s'\n", it->c_str());
59 bool SC_LanguageConfig::forEachIncludedDirectory(bool (*func)(const char *, int))
61 DirVector &vec = mIncludedDirectories;
62 DirVector::iterator it;
63 for (it=vec.begin(); it!=vec.end(); ++it) {
64 if (!func(it->c_str(), 0)) return false;
66 return true;
69 static bool findPath( SC_LanguageConfig::DirVector & vec, const char * path, bool addIfMissing)
71 char standardPath[PATH_MAX];
72 sc_StandardizePath(path, standardPath);
74 for ( SC_LanguageConfig::DirVector::iterator it = vec.begin(); it != vec.end(); ++it)
75 if (!strcmp(standardPath, it->c_str()))
76 return true;
78 if (addIfMissing)
79 vec.push_back(string(standardPath));
81 return false;
84 bool SC_LanguageConfig::pathIsExcluded(const char *path)
86 return findPath(mExcludedDirectories, path, false);
89 void SC_LanguageConfig::addIncludedDirectory(const char *path)
91 if (path == 0) return;
92 findPath(mIncludedDirectories, path, true);
95 void SC_LanguageConfig::addExcludedDirectory(const char *path)
97 if (path == 0) return;
98 findPath(mExcludedDirectories, path, true);
101 void SC_LanguageConfig::removeIncludedDirectory(const char *path)
103 char standardPath[PATH_MAX];
104 sc_StandardizePath(path, standardPath);
105 string str(standardPath);
106 DirVector::iterator end = std::remove(mIncludedDirectories.begin(), mIncludedDirectories.end(), str);
107 mIncludedDirectories.erase(end, mIncludedDirectories.end());
110 void SC_LanguageConfig::removeExcludedDirectory(const char *path)
112 string str(path);
113 DirVector::iterator end = std::remove(mExcludedDirectories.begin(), mExcludedDirectories.end(), str);
114 mExcludedDirectories.erase(end, mExcludedDirectories.end());
117 bool SC_LanguageConfig::readLibraryConfig(const char* fileName)
119 freeLibraryConfig();
120 gLibraryConfig = new SC_LanguageConfig();
122 SC_LibraryConfigFile file(::post);
123 bool success = file.open(fileName);
124 if (!success)
125 return false;
127 bool error = file.read(fileName, gLibraryConfig);
128 file.close();
130 if (!error)
131 return true;
133 freeLibraryConfig();
134 return false;
137 extern bool gPostInlineWarnings;
138 bool SC_LanguageConfig::readLibraryConfigYAML(const char* fileName)
140 freeLibraryConfig();
141 gLibraryConfig = new SC_LanguageConfig();
143 using namespace YAML;
144 try {
145 std::ifstream fin(fileName);
146 Parser parser(fin);
148 Node doc;
149 while(parser.GetNextDocument(doc)) {
150 const Node * includePaths = doc.FindValue("includePaths");
151 if (includePaths && includePaths->Type() == NodeType::Sequence) {
152 for (Iterator it = includePaths->begin(); it != includePaths->end(); ++it) {
153 Node const & pathNode = *it;
154 if (pathNode.Type() != NodeType::Scalar)
155 continue;
156 string path;
157 pathNode.GetScalar(path);
158 gLibraryConfig->addIncludedDirectory(path.c_str());
162 const Node * excludePaths = doc.FindValue("excludePaths");
163 if (excludePaths && excludePaths->Type() == NodeType::Sequence) {
164 for (Iterator it = excludePaths->begin(); it != excludePaths->end(); ++it) {
165 Node const & pathNode = *it;
166 if (pathNode.Type() != NodeType::Scalar)
167 continue;
168 string path;
169 pathNode.GetScalar(path);
170 gLibraryConfig->addExcludedDirectory(path.c_str());
174 const Node * inlineWarnings = doc.FindValue("postInlineWarnings");
175 if (inlineWarnings) {
176 try {
177 gPostInlineWarnings = inlineWarnings->to<bool>();
178 } catch(...) {
179 postfl("Warning: Cannot parse config file entry \"postInlineWarnings\"\n");
183 return true;
184 } catch (std::exception & e)
186 postfl("Exception when parsing YAML config file: %s\n", e.what());
187 freeLibraryConfig();
188 return false;
192 bool SC_LanguageConfig::writeLibraryConfigYAML(const char* fileName)
194 using namespace YAML;
195 Emitter out;
196 out.SetIndent(4);
197 out.SetMapFormat(Block);
198 out.SetSeqFormat(Block);
199 out.SetBoolFormat(TrueFalseBool);
201 out << BeginMap;
203 out << Key << "includePaths";
204 out << Value << BeginSeq;
205 for (DirVector::iterator it = gLibraryConfig->mIncludedDirectories.begin();
206 it != gLibraryConfig->mIncludedDirectories.end(); ++it)
207 out << *it;
208 out << EndSeq;
210 out << Key << "excludePaths";
211 out << Value << BeginSeq;
212 for (DirVector::iterator it = gLibraryConfig->mExcludedDirectories.begin();
213 it != gLibraryConfig->mExcludedDirectories.end(); ++it)
214 out << *it;
215 out << EndSeq;
217 out << Key << "postInlineWarnings";
218 out << Value << gPostInlineWarnings;
220 out << EndMap;
221 ofstream fout(fileName);
222 fout << out.c_str();
223 return true;
226 bool SC_LanguageConfig::defaultLibraryConfig(void)
228 freeLibraryConfig();
229 gLibraryConfig = new SC_LanguageConfig();
231 char compileDir[MAXPATHLEN];
232 char systemExtensionDir[MAXPATHLEN];
233 char userExtensionDir[MAXPATHLEN];
235 sc_GetResourceDirectory(compileDir, MAXPATHLEN-32);
236 sc_AppendToPath(compileDir, MAXPATHLEN, "SCClassLibrary");
237 gLibraryConfig->addIncludedDirectory(compileDir);
239 if (!sc_IsStandAlone()) {
240 sc_GetSystemExtensionDirectory(systemExtensionDir, MAXPATHLEN);
241 gLibraryConfig->addIncludedDirectory(systemExtensionDir);
243 sc_GetUserExtensionDirectory(userExtensionDir, MAXPATHLEN);
244 gLibraryConfig->addIncludedDirectory(userExtensionDir);
246 return true;
249 static bool file_exists(const char * fileName)
251 FILE * fp = fopen(fileName, "r");
252 if (fp)
253 fclose(fp);
254 return fp != NULL;
257 static bool file_exists(std::string const & fileName)
259 return file_exists(fileName.c_str());
263 bool SC_LanguageConfig::readDefaultLibraryConfig()
265 char config_dir[PATH_MAX];
266 bool configured = false;
267 sc_GetUserConfigDirectory(config_dir, PATH_MAX);
269 std::string user_yaml_config_file = std::string(config_dir) + SC_PATH_DELIMITER + "sclang_conf.yaml";
270 if (file_exists(user_yaml_config_file))
271 configured = readLibraryConfigYAML(user_yaml_config_file.c_str());
273 if (!configured) {
274 char global_yaml_config_file[] = "/etc/sclang_conf.yaml";
275 if (file_exists(global_yaml_config_file))
276 configured = readLibraryConfigYAML(global_yaml_config_file);
279 std::string config_file = std::string(config_dir) + SC_PATH_DELIMITER + "sclang.cfg";
281 // deprecated config files
282 const char* paths[4] = { config_file.c_str(), ".sclang.cfg", "~/.sclang.cfg", "/etc/sclang.cfg"};
284 bool deprecatedConfigFileDetected = false;
285 for (int i=0; i < 4; i++) {
286 const char * ipath = paths[i];
287 char opath[PATH_MAX];
288 if (sc_StandardizePath(ipath, opath)) {
289 if (!configured) {
290 if (file_exists(opath)) {
291 deprecatedConfigFileDetected = true;
292 postfl("reading deprecated config file: %s\n", opath);
293 configured = readLibraryConfig(opath);
299 if (deprecatedConfigFileDetected)
300 postfl("Please migrate your sclang config file to %s.\n", user_yaml_config_file.c_str());
302 if (configured)
303 return true;
305 SC_LanguageConfig::defaultLibraryConfig();
306 return false;
310 void SC_LanguageConfig::freeLibraryConfig()
312 if (gLibraryConfig) {
313 delete gLibraryConfig;
314 gLibraryConfig = 0;
318 // =====================================================================
319 // SC_LibraryConfigFile
320 // =====================================================================
322 SC_LibraryConfigFile::SC_LibraryConfigFile(ErrorFunc errorFunc)
323 : mErrorFunc(errorFunc ? errorFunc : &defaultErrorFunc),
324 mFile(0)
327 bool SC_LibraryConfigFile::open(const char* filePath)
329 close();
330 #ifdef SC_WIN32
331 mFile = fopen(filePath, "rb");
332 #else
333 mFile = fopen(filePath, "r");
334 #endif
335 return mFile != 0;
338 void SC_LibraryConfigFile::close()
340 if (mFile) {
341 fclose(mFile);
342 mFile = 0;
346 bool SC_LibraryConfigFile::read(const char* fileName, SC_LanguageConfig* libConf)
348 return read(0, fileName, libConf);
351 bool SC_LibraryConfigFile::read(int depth, const char* fileName, SC_LanguageConfig* libConf)
353 if (!mFile) return false;
355 bool error = false;
356 size_t lineNumber = 1;
357 SC_StringBuffer line;
359 while (true) {
360 int c = fgetc(mFile);
361 bool eof = c == EOF;
363 if (eof || (c == '\n')) {
364 line.finish();
365 // go on if line parse failed
366 error |= parseLine(depth, fileName, lineNumber, line.getData(), libConf);
367 line.reset();
368 lineNumber++;
369 if (eof) break;
370 } else {
371 line.append(c);
375 return error;
378 bool SC_LibraryConfigFile::parseLine(int depth, const char* fileName, int lineNumber, const char* line, SC_LanguageConfig* libConf)
380 char action = 0;
381 SC_StringBuffer path;
382 SC_StringBuffer envVarName;
383 State state = kBegin;
385 while (true) {
386 // NOTE: in some parser states the character just read is
387 // written back to be consumed by the following state in the
388 // next iteration; this may be slightly inefficient, but makes
389 // control flow more obvious.
391 char c = *line++;
393 if ((c == '\0') || ((c == '#') && (state != kEscape))) {
394 break;
397 switch (state) {
398 case kBegin:
399 if (!isspace(c)) {
400 line--;
401 state = kAction;
403 break;
404 case kAction:
405 if ((c == '+') || (c == '-') || (c == ':')) {
406 action = c;
407 state = kPath;
408 } else {
409 (*mErrorFunc)("%s,%d: invalid action '%c'\n", fileName, lineNumber, c);
410 return false;
412 break;
413 case kPath:
414 if (c == '\\') {
415 state = kEscape;
416 } else if (c == '$') {
417 state = kEnvVar;
418 } else if (isspace(c)) {
419 state = kEnd;
420 } else {
421 path.append(c);
423 break;
424 case kEscape:
425 path.append(c);
426 state = kPath;
427 break;
428 case kEnvVar:
429 if (isalpha(c)) {
430 line--;
431 state = kEnvVarName;
432 envVarName.reset();
433 } else {
434 (*mErrorFunc)("%s,%d: empty variable reference\n", fileName, lineNumber);
435 return false;
437 break;
438 case kEnvVarName:
439 if (isalpha(c) || (c == '_')) {
440 envVarName.append(c);
441 } else {
442 envVarName.finish();
443 char* envVarValue = getenv(envVarName.getData());
444 if (envVarValue) {
445 line--;
446 state = kPath;
447 path.append(envVarValue);
448 } else {
449 (*mErrorFunc)("%s,%d: undefined variable '%s'\n", fileName, lineNumber, envVarName.getData());
450 return false;
453 break;
454 case kEnd:
455 if (!isspace(c)) {
456 (*mErrorFunc)("%s,%d: trailing garbage\n", fileName, lineNumber);
457 return false;
459 break;
460 default:
461 (*mErrorFunc)("%s,%d: [internal error] invalid parser state %d\n", fileName, lineNumber, state);
462 return false;
466 if (!action) return true;
468 if (path.getSize() == 0) {
469 (*mErrorFunc)("%s,%d: empty path\n", fileName, lineNumber);
470 return false;
473 path.finish();
474 char realPath[MAXPATHLEN];
476 if (sc_StandardizePath(path.getData(), realPath) == 0) {
477 (*mErrorFunc)("%s,%d: couldn't resolve path %s\n", fileName, lineNumber, path.getData());
478 return false;
481 if (action == ':') {
482 if (++depth > kMaxIncludeDepth) {
483 (*mErrorFunc)("%s,%d: maximum include depth of %d exceeded\n", fileName, lineNumber, kMaxIncludeDepth);
484 return false;
486 SC_LibraryConfigFile file(mErrorFunc);
487 if (!file.open(realPath)) return true;
488 const char* fileName = basename(realPath);
489 bool success = file.read(depth, fileName, libConf);
490 file.close();
491 return success;
494 if (action == '+') {
495 libConf->addIncludedDirectory(realPath);
496 } else if (action == '-') {
497 libConf->addExcludedDirectory(realPath);
500 return true;
503 void SC_LibraryConfigFile::defaultErrorFunc(const char* fmt, ...)
505 va_list ap;
506 va_start(ap, fmt);
507 vprintf(fmt, ap);
510 // EOF