1 /*
2  *  Copyright 2003 Maurizio Umberto Puxeddu
3  *  Copyright 2011 Jakob Leben
4  *
5  *  This file is part of SuperCollider.
6  *
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.
11  *
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.
16  *
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
21  *
22  */
23 
24 #include "SC_LanguageConfig.hpp"
25 #include "SC_Filesystem.hpp" // getDirectory
26 
27 #include "SCBase.h" // postfl
28 
29 #include <algorithm> // std::find
30 
31 #include <boost/filesystem/operations.hpp> // exists (, canonical?)
32 #include <boost/filesystem/fstream.hpp> // ofstream
33 #include <yaml-cpp/yaml.h> // YAML
34 
35 SC_LanguageConfig::Path SC_LanguageConfig::gConfigFile;
36 bool SC_LanguageConfig::gPostInlineWarnings = false;
37 
38 SC_LanguageConfig* gLanguageConfig;
39 
40 static const char* INCLUDE_PATHS = "includePaths";
41 static const char* EXCLUDE_PATHS = "excludePaths";
42 static const char* POST_INLINE_WARNINGS = "postInlineWarnings";
43 static const char* CLASS_LIB_DIR_NAME = "SCClassLibrary";
44 const char* SCLANG_YAML_CONFIG_FILENAME = "sclang_conf.yaml";
45 
46 using DirName = SC_Filesystem::DirName;
47 namespace bfs = boost::filesystem;
48 
SC_LanguageConfig(bool optStandalone)49 SC_LanguageConfig::SC_LanguageConfig(bool optStandalone) {
50     if (!optStandalone) {
51         const Path& classLibraryDir = SC_Filesystem::instance().getDirectory(DirName::Resource) / CLASS_LIB_DIR_NAME;
52         addPath(mDefaultClassLibraryDirectories, classLibraryDir);
53 
54         const Path& systemExtensionDir = SC_Filesystem::instance().getDirectory(DirName::SystemExtension);
55         addPath(mDefaultClassLibraryDirectories, systemExtensionDir);
56 
57         const Path& userExtensionDir = SC_Filesystem::instance().getDirectory(DirName::UserExtension);
58         addPath(mDefaultClassLibraryDirectories, userExtensionDir);
59     }
60 }
61 
postExcludedDirectories(void) const62 void SC_LanguageConfig::postExcludedDirectories(void) const {
63     for (auto it : mExcludedDirectories) {
64         post("\texcluding dir: '%s'\n", it.c_str());
65     }
66 }
67 
forEachIncludedDirectory(bool (* func)(const Path &)) const68 bool SC_LanguageConfig::forEachIncludedDirectory(bool (*func)(const Path&)) const {
69     for (const auto& it : mDefaultClassLibraryDirectories) {
70         if (!pathIsExcluded(it)) {
71             if (!func(it))
72                 return false;
73         }
74     }
75 
76     for (const auto& it : mIncludedDirectories) {
77         if (!pathIsExcluded(it)) {
78             if (!func(it))
79                 return false;
80         }
81     }
82 
83     return true;
84 }
85 
pathIsExcluded(const Path & path) const86 bool SC_LanguageConfig::pathIsExcluded(const Path& path) const { return findPath(mExcludedDirectories, path); }
87 
addIncludedDirectory(const Path & path)88 bool SC_LanguageConfig::addIncludedDirectory(const Path& path) { return addPath(mIncludedDirectories, path); }
89 
addExcludedDirectory(const Path & path)90 bool SC_LanguageConfig::addExcludedDirectory(const Path& path) { return addPath(mExcludedDirectories, path); }
91 
removeIncludedDirectory(const Path & path)92 bool SC_LanguageConfig::removeIncludedDirectory(const Path& path) { return removePath(mIncludedDirectories, path); }
93 
removeExcludedDirectory(const Path & path)94 bool SC_LanguageConfig::removeExcludedDirectory(const Path& path) { return removePath(mExcludedDirectories, path); }
95 
readLibraryConfigYAML(const Path & fileName,bool standalone)96 bool SC_LanguageConfig::readLibraryConfigYAML(const Path& fileName, bool standalone) {
97     freeLibraryConfig();
98     gLanguageConfig = new SC_LanguageConfig(standalone);
99 
100     std::string emptyString;
101 
102     using namespace YAML;
103     try {
104         bfs::ifstream fin(fileName);
105         Node doc = Load(fin);
106         if (doc) {
107             const Node& includePaths = doc[INCLUDE_PATHS];
108             if (includePaths && includePaths.IsSequence()) {
109                 for (auto const& pathNode : includePaths) {
110                     const std::string& path = pathNode.as<std::string>(emptyString);
111                     if (!path.empty()) {
112                         const Path& native_path = SC_Codecvt::utf8_str_to_path(path);
113                         gLanguageConfig->addIncludedDirectory(native_path);
114                     }
115                 }
116             }
117 
118             const Node& excludePaths = doc[EXCLUDE_PATHS];
119             if (excludePaths && excludePaths.IsSequence()) {
120                 for (auto const& pathNode : excludePaths) {
121                     const std::string& path = pathNode.as<std::string>(emptyString);
122                     if (!path.empty()) {
123                         const Path& native_path = SC_Codecvt::utf8_str_to_path(path);
124                         gLanguageConfig->addExcludedDirectory(native_path);
125                     }
126                 }
127             }
128 
129             const Node& inlineWarnings = doc[POST_INLINE_WARNINGS];
130             if (inlineWarnings) {
131                 try {
132                     gPostInlineWarnings = inlineWarnings.as<bool>();
133                 } catch (...) {
134                     postfl("WARNING: Cannot parse config file entry \"%s\"\n", POST_INLINE_WARNINGS);
135                 }
136             }
137         }
138         return true;
139     } catch (std::exception& e) {
140         postfl("Exception while parsing YAML config file: %s\n", e.what());
141         freeLibraryConfig();
142         return false;
143     }
144 }
145 
writeLibraryConfigYAML(const Path & fileName)146 bool SC_LanguageConfig::writeLibraryConfigYAML(const Path& fileName) {
147     if (!bfs::exists(fileName.parent_path()))
148         return false;
149 
150     using namespace YAML;
151     Emitter out;
152     out.SetIndent(4);
153     out.SetMapFormat(Block);
154     out.SetSeqFormat(Block);
155     out.SetBoolFormat(TrueFalseBool);
156 
157     out << BeginMap;
158 
159     out << Key << INCLUDE_PATHS;
160     out << Value << BeginSeq;
161     for (const bfs::path& it : gLanguageConfig->mIncludedDirectories)
162         out << SC_Codecvt::path_to_utf8_str(it);
163     out << EndSeq;
164 
165     out << Key << EXCLUDE_PATHS;
166     out << Value << BeginSeq;
167     for (const bfs::path& it : gLanguageConfig->mExcludedDirectories)
168         out << SC_Codecvt::path_to_utf8_str(it);
169     out << EndSeq;
170 
171     out << Key << POST_INLINE_WARNINGS;
172     out << Value << gPostInlineWarnings;
173 
174     out << EndMap;
175 
176     bfs::ofstream fout(fileName);
177     fout << out.c_str();
178     return fout.good();
179 }
180 
defaultLibraryConfig(bool standalone)181 bool SC_LanguageConfig::defaultLibraryConfig(bool standalone) {
182     freeLibraryConfig();
183     gLanguageConfig = new SC_LanguageConfig(standalone);
184 
185     return true;
186 }
187 
readLibraryConfig(bool standalone)188 bool SC_LanguageConfig::readLibraryConfig(bool standalone) {
189     bool configured = false;
190 
191     if (bfs::exists(gConfigFile))
192         configured = readLibraryConfigYAML(gConfigFile, standalone);
193 
194     if (!configured && !standalone) {
195         const Path userYamlConfigFile =
196             SC_Filesystem::instance().getDirectory(DirName::UserConfig) / SCLANG_YAML_CONFIG_FILENAME;
197 
198         if (bfs::exists(userYamlConfigFile))
199             configured = readLibraryConfigYAML(userYamlConfigFile, standalone);
200 
201         if (!configured) {
202             const Path globalYamlConfigFile = Path("/etc") / SCLANG_YAML_CONFIG_FILENAME;
203 
204             if (bfs::exists(globalYamlConfigFile))
205                 configured = readLibraryConfigYAML(globalYamlConfigFile, standalone);
206         }
207     }
208 
209     if (!configured)
210         configured = SC_LanguageConfig::defaultLibraryConfig(standalone);
211 
212     return configured;
213 }
214 
freeLibraryConfig()215 void SC_LanguageConfig::freeLibraryConfig() {
216     if (gLanguageConfig) {
217         delete gLanguageConfig;
218         gLanguageConfig = nullptr;
219     }
220 }
221 
findPath(const DirVector & vec,const Path & path)222 bool SC_LanguageConfig::findPath(const DirVector& vec, const Path& path) {
223     return std::find(vec.begin(), vec.end(), path) != vec.end();
224 }
225 
addPath(DirVector & vec,const Path & path)226 bool SC_LanguageConfig::addPath(DirVector& vec, const Path& path) {
227     if (!findPath(vec, path)) {
228         vec.push_back(path);
229         return true;
230     } else {
231         return false;
232     }
233 }
234 
removePath(DirVector & vec,const Path & path)235 bool SC_LanguageConfig::removePath(DirVector& vec, const Path& path) {
236     const DirVector::iterator& end = std::remove(vec.begin(), vec.end(), path);
237     const bool removed = end != vec.end();
238     vec.erase(end, vec.end());
239     return removed;
240 }
241