Initial commit
[yaffs-website] / node_modules / node-sass / src / libsass / src / file.cpp
1 #ifdef _WIN32
2 # ifdef __MINGW32__
3 #  ifndef off64_t
4 #   define off64_t _off64_t    /* Workaround for http://sourceforge.net/p/mingw/bugs/2024/ */
5 #  endif
6 # endif
7 # include <direct.h>
8 # define S_ISDIR(mode) (((mode) & S_IFMT) == S_IFDIR)
9 #else
10 # include <unistd.h>
11 #endif
12 #include "sass.hpp"
13 #include <iostream>
14 #include <fstream>
15 #include <cctype>
16 #include <vector>
17 #include <algorithm>
18 #include <sys/stat.h>
19 #include "file.hpp"
20 #include "context.hpp"
21 #include "prelexer.hpp"
22 #include "utf8_string.hpp"
23 #include "sass_functions.hpp"
24 #include "sass2scss.h"
25
26 #ifdef _WIN32
27 # include <windows.h>
28
29 # ifdef _MSC_VER
30 # include <codecvt>
31 inline static std::string wstring_to_string(const std::wstring& wstr)
32 {
33     std::wstring_convert<std::codecvt_utf8<wchar_t>, wchar_t> wchar_converter;
34     return wchar_converter.to_bytes(wstr);
35 }
36 # else // mingw(/gcc) does not support C++11's codecvt yet.
37 inline static std::string wstring_to_string(const std::wstring &wstr)
38 {
39     int size_needed = WideCharToMultiByte(CP_UTF8, 0, &wstr[0], (int)wstr.size(), NULL, 0, NULL, NULL);
40     std::string strTo(size_needed, 0);
41     WideCharToMultiByte(CP_UTF8, 0, &wstr[0], (int)wstr.size(), &strTo[0], size_needed, NULL, NULL);
42     return strTo;
43 }
44 # endif
45 #endif
46
47 namespace Sass {
48   namespace File {
49
50     // return the current directory
51     // always with forward slashes
52     std::string get_cwd()
53     {
54       const size_t wd_len = 1024;
55       #ifndef _WIN32
56         char wd[wd_len];
57         std::string cwd = getcwd(wd, wd_len);
58       #else
59         wchar_t wd[wd_len];
60         std::string cwd = wstring_to_string(_wgetcwd(wd, wd_len));
61         //convert backslashes to forward slashes
62         replace(cwd.begin(), cwd.end(), '\\', '/');
63       #endif
64       if (cwd[cwd.length() - 1] != '/') cwd += '/';
65       return cwd;
66     }
67
68     // test if path exists and is a file
69     bool file_exists(const std::string& path)
70     {
71       #ifdef _WIN32
72         std::wstring wpath = UTF_8::convert_to_utf16(path);
73         DWORD dwAttrib = GetFileAttributesW(wpath.c_str());
74         return (dwAttrib != INVALID_FILE_ATTRIBUTES &&
75                (!(dwAttrib & FILE_ATTRIBUTE_DIRECTORY)));
76       #else
77         struct stat st_buf;
78         return (stat (path.c_str(), &st_buf) == 0) &&
79                (!S_ISDIR (st_buf.st_mode));
80       #endif
81     }
82
83     // return if given path is absolute
84     // works with *nix and windows paths
85     bool is_absolute_path(const std::string& path)
86     {
87       #ifdef _WIN32
88         if (path.length() >= 2 && isalpha(path[0]) && path[1] == ':') return true;
89       #endif
90       size_t i = 0;
91       // check if we have a protocol
92       if (path[i] && Prelexer::is_alpha(path[i])) {
93         // skip over all alphanumeric characters
94         while (path[i] && Prelexer::is_alnum(path[i])) ++i;
95         i = i && path[i] == ':' ? i + 1 : 0;
96       }
97       return path[i] == '/';
98     }
99
100     // helper function to find the last directory seperator
101     inline size_t find_last_folder_separator(const std::string& path, size_t limit = std::string::npos)
102     {
103       size_t pos = std::string::npos;
104       size_t pos_p = path.find_last_of('/', limit);
105       #ifdef _WIN32
106         size_t pos_w = path.find_last_of('\\', limit);
107       #else
108         size_t pos_w = std::string::npos;
109       #endif
110       if (pos_p != std::string::npos && pos_w != std::string::npos) {
111         pos = std::max(pos_p, pos_w);
112       }
113       else if (pos_p != std::string::npos) {
114         pos = pos_p;
115       }
116       else {
117         pos = pos_w;
118       }
119       return pos;
120     }
121
122     // return only the directory part of path
123     std::string dir_name(const std::string& path)
124     {
125       size_t pos = find_last_folder_separator(path);
126       if (pos == std::string::npos) return "";
127       else return path.substr(0, pos+1);
128     }
129
130     // return only the filename part of path
131     std::string base_name(const std::string& path)
132     {
133       size_t pos = find_last_folder_separator(path);
134       if (pos == std::string::npos) return path;
135       else return path.substr(pos+1);
136     }
137
138     // do a logical clean up of the path
139     // no physical check on the filesystem
140     std::string make_canonical_path (std::string path)
141     {
142
143       // declarations
144       size_t pos;
145
146       #ifdef _WIN32
147         //convert backslashes to forward slashes
148         replace(path.begin(), path.end(), '\\', '/');
149       #endif
150
151       pos = 0; // remove all self references inside the path string
152       while((pos = path.find("/./", pos)) != std::string::npos) path.erase(pos, 2);
153
154       pos = 0; // remove all leading and trailing self references
155       while(path.length() > 1 && path.substr(0, 2) == "./") path.erase(0, 2);
156       while((pos = path.length()) > 1 && path.substr(pos - 2) == "/.") path.erase(pos - 2);
157
158
159       size_t proto = 0;
160       // check if we have a protocol
161       if (path[proto] && Prelexer::is_alpha(path[proto])) {
162         // skip over all alphanumeric characters
163         while (path[proto] && Prelexer::is_alnum(path[proto++])) {}
164         // then skip over the mandatory colon
165         if (proto && path[proto] == ':') ++ proto;
166       }
167
168       // then skip over start slashes
169       while (path[proto++] == '/') {}
170
171       pos = proto; // collapse multiple delimiters into a single one
172       while((pos = path.find("//", pos)) != std::string::npos) path.erase(pos, 1);
173
174       return path;
175
176     }
177
178     // join two path segments cleanly together
179     // but only if right side is not absolute yet
180     std::string join_paths(std::string l, std::string r)
181     {
182
183       #ifdef _WIN32
184         // convert Windows backslashes to URL forward slashes
185         replace(l.begin(), l.end(), '\\', '/');
186         replace(r.begin(), r.end(), '\\', '/');
187       #endif
188
189       if (l.empty()) return r;
190       if (r.empty()) return l;
191
192       if (is_absolute_path(r)) return r;
193       if (l[l.length()-1] != '/') l += '/';
194
195       while ((r.length() > 3) && ((r.substr(0, 3) == "../") || (r.substr(0, 3)) == "..\\")) {
196         size_t L = l.length(), pos = find_last_folder_separator(l, L - 2);
197         bool is_slash = pos + 2 == L && (l[pos+1] == '/' || l[pos+1] == '\\');
198         bool is_self = pos + 3 == L && (l[pos+1] == '.');
199         if (!is_self && !is_slash) r = r.substr(3);
200         else if (pos == std::string::npos) break;
201         l = l.substr(0, pos == std::string::npos ? pos : pos + 1);
202       }
203
204       return l + r;
205     }
206
207     std::string path_for_console(const std::string& rel_path, const std::string& abs_path, const std::string& orig_path)
208     {
209       // magic algorith goes here!!
210
211       // if the file is outside this directory show the absolute path
212       if (rel_path.substr(0, 3) == "../") {
213         return orig_path;
214       }
215       // this seems to work most of the time
216       return abs_path == orig_path ? abs_path : rel_path;
217     }
218
219     // create an absolute path by resolving relative paths with cwd
220     std::string rel2abs(const std::string& path, const std::string& base, const std::string& cwd)
221     {
222       return make_canonical_path(join_paths(join_paths(cwd + "/", base + "/"), path));
223     }
224
225     // create a path that is relative to the given base directory
226     // path and base will first be resolved against cwd to make them absolute
227     std::string abs2rel(const std::string& path, const std::string& base, const std::string& cwd)
228     {
229
230       std::string abs_path = rel2abs(path, cwd);
231       std::string abs_base = rel2abs(base, cwd);
232
233       size_t proto = 0;
234       // check if we have a protocol
235       if (path[proto] && Prelexer::is_alpha(path[proto])) {
236         // skip over all alphanumeric characters
237         while (path[proto] && Prelexer::is_alnum(path[proto++])) {}
238         // then skip over the mandatory colon
239         if (proto && path[proto] == ':') ++ proto;
240       }
241
242       // distinguish between windows absolute paths and valid protocols
243       // we assume that protocols must at least have two chars to be valid
244       if (proto && path[proto++] == '/' && proto > 3) return path;
245
246       #ifdef _WIN32
247         // absolute link must have a drive letter, and we know that we
248         // can only create relative links if both are on the same drive
249         if (abs_base[0] != abs_path[0]) return abs_path;
250       #endif
251
252       std::string stripped_uri = "";
253       std::string stripped_base = "";
254
255       size_t index = 0;
256       size_t minSize = std::min(abs_path.size(), abs_base.size());
257       for (size_t i = 0; i < minSize; ++i) {
258         #ifdef FS_CASE_SENSITIVE
259           if (abs_path[i] != abs_base[i]) break;
260         #else
261           // compare the charactes in a case insensitive manner
262           // windows fs is only case insensitive in ascii ranges
263           if (tolower(abs_path[i]) != tolower(abs_base[i])) break;
264         #endif
265         if (abs_path[i] == '/') index = i + 1;
266       }
267       for (size_t i = index; i < abs_path.size(); ++i) {
268         stripped_uri += abs_path[i];
269       }
270       for (size_t i = index; i < abs_base.size(); ++i) {
271         stripped_base += abs_base[i];
272       }
273
274       size_t left = 0;
275       size_t directories = 0;
276       for (size_t right = 0; right < stripped_base.size(); ++right) {
277         if (stripped_base[right] == '/') {
278           if (stripped_base.substr(left, 2) != "..") {
279             ++directories;
280           }
281           else if (directories > 1) {
282             --directories;
283           }
284           else {
285             directories = 0;
286           }
287           left = right + 1;
288         }
289       }
290
291       std::string result = "";
292       for (size_t i = 0; i < directories; ++i) {
293         result += "../";
294       }
295       result += stripped_uri;
296
297       return result;
298     }
299
300     // Resolution order for ambiguous imports:
301     // (1) filename as given
302     // (2) underscore + given
303     // (3) underscore + given + extension
304     // (4) given + extension
305     std::vector<Include> resolve_includes(const std::string& root, const std::string& file, const std::vector<std::string>& exts)
306     {
307       std::string filename = join_paths(root, file);
308       // split the filename
309       std::string base(dir_name(file));
310       std::string name(base_name(file));
311       std::vector<Include> includes;
312       // create full path (maybe relative)
313       std::string rel_path(join_paths(base, name));
314       std::string abs_path(join_paths(root, rel_path));
315       if (file_exists(abs_path)) includes.push_back({{ rel_path, root }, abs_path });
316       // next test variation with underscore
317       rel_path = join_paths(base, "_" + name);
318       abs_path = join_paths(root, rel_path);
319       if (file_exists(abs_path)) includes.push_back({{ rel_path, root }, abs_path });
320       // next test exts plus underscore
321       for(auto ext : exts) {
322         rel_path = join_paths(base, "_" + name + ext);
323         abs_path = join_paths(root, rel_path);
324         if (file_exists(abs_path)) includes.push_back({{ rel_path, root }, abs_path });
325       }
326       // next test plain name with exts
327       for(auto ext : exts) {
328         rel_path = join_paths(base, name + ext);
329         abs_path = join_paths(root, rel_path);
330         if (file_exists(abs_path)) includes.push_back({{ rel_path, root }, abs_path });
331       }
332       // nothing found
333       return includes;
334     }
335
336     std::vector<std::string> find_files(const std::string& file, const std::vector<std::string> paths)
337     {
338       std::vector<std::string> includes;
339       for (std::string path : paths) {
340         std::string abs_path(join_paths(path, file));
341         if (file_exists(abs_path)) includes.push_back(abs_path);
342       }
343       return includes;
344     }
345
346     std::vector<std::string> find_files(const std::string& file, struct Sass_Compiler* compiler)
347     {
348       // get the last import entry to get current base directory
349       // struct Sass_Options* options = sass_compiler_get_options(compiler);
350       Sass_Import_Entry import = sass_compiler_get_last_import(compiler);
351       const std::vector<std::string>& incs = compiler->cpp_ctx->include_paths;
352       // create the vector with paths to lookup
353       std::vector<std::string> paths(1 + incs.size());
354       paths.push_back(dir_name(import->abs_path));
355       paths.insert(paths.end(), incs.begin(), incs.end());
356       // dispatch to find files in paths
357       return find_files(file, paths);
358     }
359
360     // helper function to search one file in all include paths
361     // this is normally not used internally by libsass (C-API sugar)
362     std::string find_file(const std::string& file, const std::vector<std::string> paths)
363     {
364       if (file.empty()) return file;
365       auto res = find_files(file, paths);
366       return res.empty() ? "" : res.front();
367     }
368
369     // helper function to resolve a filename
370     std::string find_include(const std::string& file, const std::vector<std::string> paths)
371     {
372       // search in every include path for a match
373       for (size_t i = 0, S = paths.size(); i < S; ++i)
374       {
375         std::vector<Include> resolved(resolve_includes(paths[i], file));
376         if (resolved.size()) return resolved[0].abs_path;
377       }
378       // nothing found
379       return std::string("");
380     }
381
382     // try to load the given filename
383     // returned memory must be freed
384     // will auto convert .sass files
385     char* read_file(const std::string& path)
386     {
387       #ifdef _WIN32
388         BYTE* pBuffer;
389         DWORD dwBytes;
390         // windows unicode filepaths are encoded in utf16
391         std::wstring wpath = UTF_8::convert_to_utf16(path);
392         HANDLE hFile = CreateFileW(wpath.c_str(), GENERIC_READ, FILE_SHARE_READ, NULL, OPEN_EXISTING, 0, NULL);
393         if (hFile == INVALID_HANDLE_VALUE) return 0;
394         DWORD dwFileLength = GetFileSize(hFile, NULL);
395         if (dwFileLength == INVALID_FILE_SIZE) return 0;
396         // allocate an extra byte for the null char
397         pBuffer = (BYTE*)malloc((dwFileLength+1)*sizeof(BYTE));
398         ReadFile(hFile, pBuffer, dwFileLength, &dwBytes, NULL);
399         pBuffer[dwFileLength] = '\0';
400         CloseHandle(hFile);
401         // just convert from unsigned char*
402         char* contents = (char*) pBuffer;
403       #else
404         struct stat st;
405         if (stat(path.c_str(), &st) == -1 || S_ISDIR(st.st_mode)) return 0;
406         std::ifstream file(path.c_str(), std::ios::in | std::ios::binary | std::ios::ate);
407         char* contents = 0;
408         if (file.is_open()) {
409           size_t size = file.tellg();
410           // allocate an extra byte for the null char
411           contents = (char*) malloc((size+1)*sizeof(char));
412           file.seekg(0, std::ios::beg);
413           file.read(contents, size);
414           contents[size] = '\0';
415           file.close();
416         }
417       #endif
418       std::string extension;
419       if (path.length() > 5) {
420         extension = path.substr(path.length() - 5, 5);
421       }
422       for(size_t i=0; i<extension.size();++i)
423         extension[i] = tolower(extension[i]);
424       if (extension == ".sass" && contents != 0) {
425         char * converted = sass2scss(contents, SASS2SCSS_PRETTIFY_1 | SASS2SCSS_KEEP_COMMENT);
426         free(contents); // free the indented contents
427         return converted; // should be freed by caller
428       } else {
429         return contents;
430       }
431     }
432
433     // split a path string delimited by semicolons or colons (OS dependent)
434     std::vector<std::string> split_path_list(const char* str)
435     {
436       std::vector<std::string> paths;
437       if (str == NULL) return paths;
438       // find delimiter via prelexer (return zero at end)
439       const char* end = Prelexer::find_first<PATH_SEP>(str);
440       // search until null delimiter
441       while (end) {
442         // add path from current position to delimiter
443         paths.push_back(std::string(str, end - str));
444         str = end + 1; // skip delimiter
445         end = Prelexer::find_first<PATH_SEP>(str);
446       }
447       // add path from current position to end
448       paths.push_back(std::string(str));
449       // return back
450       return paths;
451     }
452
453   }
454 }