diff --git a/Makefile b/Makefile index 9c24ab8da..fd4138181 100644 --- a/Makefile +++ b/Makefile @@ -24,20 +24,20 @@ else endif ifeq "$(LIBSASS_VERSION)" "" - ifneq "$(wildcard ./.git/ )" "" - LIBSASS_VERSION ?= $(shell git describe --abbrev=4 --dirty --always --tags) - endif + ifneq "$(wildcard ./.git/ )" "" + LIBSASS_VERSION ?= $(shell git describe --abbrev=4 --dirty --always --tags) + endif endif ifeq "$(LIBSASS_VERSION)" "" - ifneq ("$(wildcard VERSION)","") - LIBSASS_VERSION ?= $(shell $(CAT) VERSION) - endif + ifneq ("$(wildcard VERSION)","") + LIBSASS_VERSION ?= $(shell $(CAT) VERSION) + endif endif ifneq "$(LIBSASS_VERSION)" "" - CFLAGS += -DLIBSASS_VERSION="\"$(LIBSASS_VERSION)\"" - CXXFLAGS += -DLIBSASS_VERSION="\"$(LIBSASS_VERSION)\"" + CFLAGS += -DLIBSASS_VERSION="\"$(LIBSASS_VERSION)\"" + CXXFLAGS += -DLIBSASS_VERSION="\"$(LIBSASS_VERSION)\"" endif # enable mandatory flag @@ -50,18 +50,18 @@ else endif ifneq "$(SASS_LIBSASS_PATH)" "" - CFLAGS += -I $(SASS_LIBSASS_PATH) - CXXFLAGS += -I $(SASS_LIBSASS_PATH) + CFLAGS += -I $(SASS_LIBSASS_PATH) + CXXFLAGS += -I $(SASS_LIBSASS_PATH) endif ifneq "$(EXTRA_CFLAGS)" "" - CFLAGS += $(EXTRA_CFLAGS) + CFLAGS += $(EXTRA_CFLAGS) endif ifneq "$(EXTRA_CXXFLAGS)" "" - CXXFLAGS += $(EXTRA_CXXFLAGS) + CXXFLAGS += $(EXTRA_CXXFLAGS) endif ifneq "$(EXTRA_LDFLAGS)" "" - LDFLAGS += $(EXTRA_LDFLAGS) + LDFLAGS += $(EXTRA_LDFLAGS) endif LDLIBS = -lstdc++ -lm @@ -71,6 +71,11 @@ ifeq ($(UNAME),Darwin) LDFLAGS += -stdlib=libc++ endif +ifneq (MinGW,$(UNAME)) + LDFLAGS += -ldl + LDLIBS += -ldl +endif + ifneq ($(BUILD),shared) BUILD = static endif @@ -117,6 +122,7 @@ SOURCES = \ emitter.cpp \ output.cpp \ parser.cpp \ + plugins.cpp \ position.cpp \ prelexer.cpp \ remove_placeholders.cpp \ diff --git a/Makefile.am b/Makefile.am index 9b8526e2b..2daa4c257 100644 --- a/Makefile.am +++ b/Makefile.am @@ -10,6 +10,7 @@ else AM_CFLAGS += -fPIC AM_CXXFLAGS += -fPIC AM_CXXFLAGS += -std=c++0x + AM_LDFLAGS += -ldl endif AM_CFLAGS += -DLIBSASS_VERSION="\"$(VERSION)\"" @@ -68,6 +69,7 @@ libsass_la_SOURCES = \ emitter.cpp emitter.hpp \ output.cpp output.hpp \ parser.cpp parser.hpp \ + plugins.cpp plugins.hpp \ prelexer.cpp prelexer.hpp \ remove_placeholders.cpp remove_placeholders.hpp \ sass.cpp sass.h \ diff --git a/context.cpp b/context.cpp index b6d5c4edc..d9857a7fe 100644 --- a/context.cpp +++ b/context.cpp @@ -5,7 +5,9 @@ #endif #include "ast.hpp" +#include "sass.h" #include "context.hpp" +#include "plugins.hpp" #include "constants.hpp" #include "parser.hpp" #include "file.hpp" @@ -51,6 +53,7 @@ namespace Sass { mem(Memory_Manager()), source_c_str (initializers.source_c_str()), sources (vector()), + plugin_paths (initializers.plugin_paths()), include_paths (initializers.include_paths()), queue (vector()), style_sheets (map()), @@ -71,8 +74,10 @@ namespace Sass { names_to_colors (map()), colors_to_names (map()), precision (initializers.precision()), + plugins(), subset_map (Subset_Map >()) { + cwd = get_cwd(); // enforce some safe defaults @@ -83,9 +88,19 @@ namespace Sass { include_paths.push_back(cwd); collect_include_paths(initializers.include_paths_c_str()); collect_include_paths(initializers.include_paths_array()); + collect_plugin_paths(initializers.plugin_paths_c_str()); + collect_plugin_paths(initializers.plugin_paths_array()); setup_color_map(); + for (size_t i = 0, S = plugin_paths.size(); i < S; ++i) { + plugins.load_plugins(plugin_paths[i]); + } + + for(auto fn : plugins.get_functions()) { + c_functions.push_back(fn); + } + string entry_point = initializers.entry_point(); if (!entry_point.empty()) { string result(add_file(entry_point)); @@ -155,7 +170,6 @@ namespace Sass { void Context::collect_include_paths(const char** paths_array) { - if (*include_paths.back().rbegin() != '/') include_paths.back() += '/'; if (paths_array) { for (size_t i = 0; paths_array[i]; i++) { collect_include_paths(paths_array[i]); @@ -163,6 +177,39 @@ namespace Sass { } } + void Context::collect_plugin_paths(const char* paths_str) + { + + if (paths_str) { + const char* beg = paths_str; + const char* end = Prelexer::find_first(beg); + + while (end) { + string path(beg, end - beg); + if (!path.empty()) { + if (*path.rbegin() != '/') path += '/'; + plugin_paths.push_back(path); + } + beg = end + 1; + end = Prelexer::find_first(beg); + } + + string path(beg); + if (!path.empty()) { + if (*path.rbegin() != '/') path += '/'; + plugin_paths.push_back(path); + } + } + } + + void Context::collect_plugin_paths(const char** paths_array) + { + if (paths_array) { + for (size_t i = 0; paths_array[i]; i++) { + collect_plugin_paths(paths_array[i]); + } + } + } void Context::add_source(string load_path, string abs_path, const char* contents) { sources.push_back(contents); diff --git a/context.hpp b/context.hpp index 98ee5c7ce..545e7add8 100644 --- a/context.hpp +++ b/context.hpp @@ -15,6 +15,7 @@ #include "source_map.hpp" #include "subset_map.hpp" #include "output.hpp" +#include "plugins.hpp" #include "sass_functions.h" struct Sass_C_Function_Descriptor; @@ -44,6 +45,7 @@ namespace Sass { vector include_links; // vectors above have same size + vector plugin_paths; // relative paths to load plugins vector include_paths; // lookup paths for includes vector queue; // queue of files to be parsed map style_sheets; // map of paths to ASTs @@ -80,8 +82,11 @@ namespace Sass { KWD_ARG(Data, string, indent); KWD_ARG(Data, string, linefeed); KWD_ARG(Data, const char*, include_paths_c_str); + KWD_ARG(Data, const char*, plugin_paths_c_str); KWD_ARG(Data, const char**, include_paths_array); + KWD_ARG(Data, const char**, plugin_paths_array); KWD_ARG(Data, vector, include_paths); + KWD_ARG(Data, vector, plugin_paths); KWD_ARG(Data, bool, source_comments); KWD_ARG(Data, Output_Style, output_style); KWD_ARG(Data, string, source_map_file); @@ -113,11 +118,14 @@ namespace Sass { vector get_included_files(size_t skip = 0); private: + void collect_plugin_paths(const char* paths_str); + void collect_plugin_paths(const char** paths_array); void collect_include_paths(const char* paths_str); void collect_include_paths(const char** paths_array); string format_source_mapping_url(const string& file); string cwd; + Plugins plugins; // void register_built_in_functions(Env* env); // void register_function(Signature sig, Native_Function f, Env* env); diff --git a/plugins.cpp b/plugins.cpp new file mode 100644 index 000000000..a01f8650e --- /dev/null +++ b/plugins.cpp @@ -0,0 +1,155 @@ +#ifdef _WIN32 +#include +#else +#include +#include +#include +#include +#endif + +#include +#include "output.hpp" +#include "plugins.hpp" + +#define npos string::npos + +namespace Sass { + + Plugins::Plugins(void) { } + Plugins::~Plugins(void) { } + + // check if plugin is compatible with this version + // plugins may be linked static against libsass + // we try to be compatible between major versions + inline bool compatibility(const char* their_version) + { +// const char* their_version = "3.1.2"; + // first check if anyone has an unknown version + const char* our_version = libsass_version(); + if (!strcmp(their_version, "[na]")) return false; + if (!strcmp(our_version, "[na]")) return false; + + // find the position of the second dot + size_t pos = string(our_version).find('.', 0); + if (pos != npos) pos = string(our_version).find('.', pos + 1); + + // if we do not have two dots we fallback to compare complete string + if (pos == npos) { return strcmp(their_version, our_version) ? 0 : 1; } + // otherwise only compare up to the second dot (major versions) + else { return strncmp(their_version, our_version, pos) ? 0 : 1; } + + } + + // load one specific plugin + bool Plugins::load_plugin (const string& path) + { + + typedef const char* (*__plugin_version__)(void); + typedef Sass_C_Function_List (*__plugin_load_fns__)(void); + + if (LOAD_LIB(plugin, path)) + { + // try to load initial function to query libsass version suppor + if (LOAD_LIB_FN(__plugin_version__, plugin_version, "libsass_get_version")) + { + // get the libsass version of the plugin + if (!compatibility(plugin_version())) return false; + // try to get import address for "libsass_load_functions" + if (LOAD_LIB_FN(__plugin_load_fns__, plugin_load_functions, "libsass_load_functions")) + { + Sass_C_Function_List fns = plugin_load_functions(); + while (fns && *fns) { functions.push_back(*fns); ++ fns; } + } + // success + return true; + } + else + { + // print debug message to stderr (should not happen) + cerr << "failed loading 'libsass_support' in <" << path << ">" << endl; + if (const char* dlsym_error = dlerror()) cerr << dlsym_error << endl; + CLOSE_LIB(plugin); + } + } + else + { + // print debug message to stderr (should not happen) + cerr << "failed loading plugin <" << path << ">" << endl; + if (const char* dlopen_error = dlerror()) cerr << dlopen_error << endl; + } + + return false; + + } + + size_t Plugins::load_plugins(const string& path) + { + + // count plugins + size_t loaded = 0; + + #ifdef _WIN32 + + try + { + + // use wchar (utf16) + WIN32_FIND_DATAW data; + // trailing slash is guaranteed + string globsrch(path + "*.dll"); + // convert to wide chars (utf16) for system call + wstring wglobsrch(UTF_8::convert_to_utf16(globsrch)); + HANDLE hFile = FindFirstFileW(wglobsrch.c_str(), &data); + // check if system called returned a result + // ToDo: maybe we should print a debug message + if (hFile == INVALID_HANDLE_VALUE) return -1; + + // read directory + while (true) + { + try + { + // the system will report the filenames with wide chars (utf16) + string entry = UTF_8::convert_from_utf16(data.cFileName); + // check if file ending matches exactly + if (!ends_with(entry, ".dll")) continue; + // load the plugin and increase counter + if (load_plugin(path + entry)) ++ loaded; + // check if there should be more entries + if (GetLastError() == ERROR_NO_MORE_FILES) break; + // load next entry (check for return type) + if (!FindNextFileW(hFile, &data)) break; + } + catch (...) + { + // report the error to the console (should not happen) + // seems like we got strange data from the system call? + cerr << "filename in plugin path has invalid utf8?" << endl; + } + } + } + catch (utf8::invalid_utf8) + { + // report the error to the console (should not happen) + // implementors should make sure to provide valid utf8 + cerr << "plugin path contains invalid utf8" << endl; + } + + #else + + DIR *dp; + struct dirent *dirp; + if((dp = opendir(path.c_str())) == NULL) return -1; + while ((dirp = readdir(dp)) != NULL) { + if (!ends_with(dirp->d_name, ".so")) continue; + if (load_plugin(path + dirp->d_name)) ++ loaded; + } + closedir(dp); + + #endif + return loaded; + + } + +} + diff --git a/plugins.hpp b/plugins.hpp new file mode 100644 index 000000000..dee6a0e8d --- /dev/null +++ b/plugins.hpp @@ -0,0 +1,56 @@ +#ifndef SASS_PLUGINS_H +#define SASS_PLUGINS_H + +#include +#include +#include "utf8_string.hpp" +#include "sass_functions.h" + +#ifdef _WIN32 + + #define LOAD_LIB(var, path) HMODULE var = LoadLibraryW(UTF_8::convert_to_utf16(path).c_str()) + #define LOAD_LIB_WCHR(var, path_wide_str) HMODULE var = LoadLibraryW(path_wide_str.c_str()) + #define LOAD_LIB_FN(type, var, name) type var = (type) GetProcAddress(plugin, name) + #define CLOSE_LIB(var) FreeLibrary(var) + + #ifndef dlerror + #define dlerror() 0 + #endif + +#else + + #define LOAD_LIB(var, path) void* var = dlopen(path.c_str(), RTLD_LAZY) + #define LOAD_LIB_FN(type, var, name) type var = (type) dlsym(plugin, name) + #define CLOSE_LIB(var) dlclose(var) + +#endif + +namespace Sass { + + using namespace std; + + class Plugins { + + public: // c-tor + Plugins(void); + ~Plugins(void); + + public: // methods + // load one specific plugin + bool load_plugin(const string& path); + // load all plugins from a directory + size_t load_plugins(const string& path); + + public: // public accessors + // const vector get_importers(void) { return importers; }; + const vector get_functions(void) { return functions; }; + + private: // private vars + // vector importers; + vector functions; + + }; + +} + +#endif \ No newline at end of file diff --git a/sass.h b/sass.h index 95ce1d877..df2e882eb 100644 --- a/sass.h +++ b/sass.h @@ -52,7 +52,6 @@ ADDAPI char* ADDCALL sass_string_unquote (const char *str); // Get compiled libsass version ADDAPI const char* ADDCALL libsass_version(void); - #ifdef __cplusplus } // __cplusplus defined. #endif diff --git a/sass_context.cpp b/sass_context.cpp index 6717295c4..71dd3826e 100644 --- a/sass_context.cpp +++ b/sass_context.cpp @@ -82,9 +82,12 @@ extern "C" { // Semicolon-separated on Windows // Maybe use array interface instead? char* include_path; + char* plugin_path; - // Include path (linked string list) + // Include paths (linked string list) struct string_list* include_paths; + // Plugin paths (linked string list) + struct string_list* plugin_paths; // Path to source map file // Enables source map generation @@ -304,19 +307,35 @@ extern "C" { } // convert include path linked list to static array - struct string_list* cur = c_ctx->include_paths; + struct string_list* inc = c_ctx->include_paths; // very poor loop to get the length of the linked list - size_t length = 0; while (cur) { length ++; cur = cur->next; } + size_t inc_size = 0; while (inc) { inc_size ++; inc = inc->next; } // create char* array to hold all paths plus null terminator - const char** include_paths = (const char**) calloc(length + 1, sizeof(char*)); + const char** include_paths = (const char**) calloc(inc_size + 1, sizeof(char*)); if (include_paths == 0) throw(bad_alloc()); // reset iterator - cur = c_ctx->include_paths; + inc = c_ctx->include_paths; // copy over the paths - for (size_t i = 0; cur; i++) { - include_paths[i] = cur->string; - cur = cur->next; + for (size_t i = 0; inc; i++) { + include_paths[i] = inc->string; + inc = inc->next; } + + // convert plugin path linked list to static array + struct string_list* imp = c_ctx->plugin_paths; + // very poor loop to get the length of the linked list + size_t imp_size = 0; while (imp) { imp_size ++; imp = imp->next; } + // create char* array to hold all paths plus null terminator + const char** plugin_paths = (const char**) calloc(imp_size + 1, sizeof(char*)); + if (plugin_paths == 0) throw(bad_alloc()); + // reset iterator + imp = c_ctx->plugin_paths; + // copy over the paths + for (size_t i = 0; imp; i++) { + plugin_paths[i] = imp->string; + imp = imp->next; + } + // transfer the options to c++ cpp_opt.input_path(input_path) .output_path(output_path) @@ -328,9 +347,12 @@ extern "C" { .source_map_contents(c_ctx->source_map_contents) .omit_source_map_url(c_ctx->omit_source_map_url) .include_paths_c_str(c_ctx->include_path) - .importer(c_ctx->importer) + .plugin_paths_c_str(c_ctx->plugin_path) .include_paths_array(include_paths) + .plugin_paths_array(plugin_paths) .include_paths(vector()) + .plugin_paths(vector()) + .importer(c_ctx->importer) .precision(c_ctx->precision ? c_ctx->precision : 5) .linefeed(c_ctx->linefeed ? c_ctx->linefeed : LFEED) .indent(c_ctx->indent ? c_ctx->indent : " "); @@ -339,6 +361,7 @@ extern "C" { Context* cpp_ctx = new Context(cpp_opt); // free intermediate data free(include_paths); + free(plugin_paths); // register our custom functions if (c_ctx->c_functions) { @@ -585,6 +608,18 @@ extern "C" { } } // Deallocate inc paths + if (options->plugin_paths) { + struct string_list* cur; + struct string_list* next; + cur = options->plugin_paths; + while (cur) { + next = cur->next; + free(cur->string); + free(cur); + cur = next; + } + } + // Deallocate inc paths if (options->include_paths) { struct string_list* cur; struct string_list* next; @@ -603,6 +638,7 @@ extern "C" { // Make it null terminated options->importer = 0; options->c_functions = 0; + options->plugin_paths = 0; options->include_paths = 0; } @@ -675,6 +711,7 @@ extern "C" { IMPLEMENT_SASS_OPTION_ACCESSOR(const char*, linefeed); IMPLEMENT_SASS_OPTION_STRING_ACCESSOR(const char*, input_path); IMPLEMENT_SASS_OPTION_STRING_ACCESSOR(const char*, output_path); + IMPLEMENT_SASS_OPTION_STRING_ACCESSOR(const char*, plugin_path); IMPLEMENT_SASS_OPTION_STRING_ACCESSOR(const char*, include_path); IMPLEMENT_SASS_OPTION_STRING_ACCESSOR(const char*, source_map_file); @@ -713,4 +750,23 @@ extern "C" { } } + + // Push function for plugin paths (no manipulation support for now) + void ADDCALL sass_option_push_plugin_path(struct Sass_Options* options, const char* path) + { + + struct string_list* plugin_path = (struct string_list*) calloc(1, sizeof(struct string_list)); + if (plugin_path == 0) return; + plugin_path->string = path ? copy_c_str(path) : 0; + struct string_list* last = options->plugin_paths; + if (!options->plugin_paths) { + options->plugin_paths = plugin_path; + } else { + while (last->next) + last = last->next; + last->next = plugin_path; + } + + } + } diff --git a/sass_context.h b/sass_context.h index 96c5be80b..5645a620a 100644 --- a/sass_context.h +++ b/sass_context.h @@ -70,6 +70,7 @@ ADDAPI const char* ADDCALL sass_option_get_indent (struct Sass_Options* options) ADDAPI const char* ADDCALL sass_option_get_linefeed (struct Sass_Options* options); ADDAPI const char* ADDCALL sass_option_get_input_path (struct Sass_Options* options); ADDAPI const char* ADDCALL sass_option_get_output_path (struct Sass_Options* options); +ADDAPI const char* ADDCALL sass_option_get_plugin_path (struct Sass_Options* options); ADDAPI const char* ADDCALL sass_option_get_include_path (struct Sass_Options* options); ADDAPI const char* ADDCALL sass_option_get_source_map_file (struct Sass_Options* options); ADDAPI Sass_C_Function_List ADDCALL sass_option_get_c_functions (struct Sass_Options* options); @@ -87,6 +88,7 @@ ADDAPI void ADDCALL sass_option_set_indent (struct Sass_Options* options, const ADDAPI void ADDCALL sass_option_set_linefeed (struct Sass_Options* options, const char* linefeed); ADDAPI void ADDCALL sass_option_set_input_path (struct Sass_Options* options, const char* input_path); ADDAPI void ADDCALL sass_option_set_output_path (struct Sass_Options* options, const char* output_path); +ADDAPI void ADDCALL sass_option_set_plugin_path (struct Sass_Options* options, const char* plugin_path); ADDAPI void ADDCALL sass_option_set_include_path (struct Sass_Options* options, const char* include_path); ADDAPI void ADDCALL sass_option_set_source_map_file (struct Sass_Options* options, const char* source_map_file); ADDAPI void ADDCALL sass_option_set_c_functions (struct Sass_Options* options, Sass_C_Function_List c_functions); @@ -112,6 +114,7 @@ ADDAPI char* ADDCALL sass_context_take_output_string (struct Sass_Context* ctx); ADDAPI char* ADDCALL sass_context_take_source_map_string (struct Sass_Context* ctx); // Push function for include paths (no manipulation support for now) +ADDAPI void ADDCALL sass_option_push_plugin_path (struct Sass_Options* options, const char* path); ADDAPI void ADDCALL sass_option_push_include_path (struct Sass_Options* options, const char* path); diff --git a/sass_interface.cpp b/sass_interface.cpp index 216426c30..9d6a17985 100644 --- a/sass_interface.cpp +++ b/sass_interface.cpp @@ -119,8 +119,11 @@ extern "C" { .source_map_contents(c_ctx->options.source_map_contents) .omit_source_map_url(c_ctx->options.omit_source_map_url) .include_paths_c_str(c_ctx->options.include_paths) + .plugin_paths_c_str(c_ctx->options.plugin_paths) .include_paths_array(0) + .plugin_paths_array(0) .include_paths(vector()) + .plugin_paths(vector()) .precision(c_ctx->options.precision ? c_ctx->options.precision : 5) .indent(c_ctx->options.indent ? c_ctx->options.indent : " ") .linefeed(c_ctx->options.linefeed ? c_ctx->options.linefeed : LFEED) @@ -208,8 +211,11 @@ extern "C" { .source_map_contents(c_ctx->options.source_map_contents) .omit_source_map_url(c_ctx->options.omit_source_map_url) .include_paths_c_str(c_ctx->options.include_paths) + .plugin_paths_c_str(c_ctx->options.plugin_paths) .include_paths_array(0) + .plugin_paths_array(0) .include_paths(vector()) + .plugin_paths(vector()) .precision(c_ctx->options.precision ? c_ctx->options.precision : 5) ); if (c_ctx->c_functions) { diff --git a/sass_interface.h b/sass_interface.h index c8d9457e4..a1fb02571 100644 --- a/sass_interface.h +++ b/sass_interface.h @@ -33,6 +33,7 @@ struct sass_options { // Colon-separated list of paths // Semicolon-separated on Windows const char* include_paths; + const char* plugin_paths; // String to be used for indentation const char* indent; // String to be used to for line feeds diff --git a/utf8_string.hpp b/utf8_string.hpp index 1ab2416f9..c2d7356a9 100644 --- a/utf8_string.hpp +++ b/utf8_string.hpp @@ -2,6 +2,7 @@ #define SASS_UTF8_STRING_H #include +#include "utf8.h" namespace Sass { namespace UTF_8 { diff --git a/win/libsass.filters b/win/libsass.filters index a165dd043..82a3e5c09 100644 --- a/win/libsass.filters +++ b/win/libsass.filters @@ -78,6 +78,9 @@ Source Files + + Source Files + Source Files @@ -233,6 +236,9 @@ Header Files + + Header Files + Header Files diff --git a/win/libsass.vcxproj b/win/libsass.vcxproj index 434f80767..a473ae07a 100644 --- a/win/libsass.vcxproj +++ b/win/libsass.vcxproj @@ -178,6 +178,7 @@ + @@ -228,6 +229,7 @@ +