From 7870e88f0653e6ac93c1f2e123aa9826778be376 Mon Sep 17 00:00:00 2001
From: "Rafael G. Martins" <rafael@rafaelmartins.eng.br>
Date: Mon, 25 Feb 2019 22:43:51 +0100
Subject: make: implemented optional sass support using libsass

pending:

- tests for sass support
- build tests on ci with and without libsass
- documentation
- support sass options in blogcfile
---
 src/blogc-make/ctx.c         |  14 ++++
 src/blogc-make/exec-native.c |  27 +++++---
 src/blogc-make/exec-native.h |   1 +
 src/blogc-make/main.c        |   6 +-
 src/blogc-make/rules.c       |  17 +++++
 src/blogc-make/sass.c        | 148 +++++++++++++++++++++++++++++++++++++++++++
 src/blogc-make/sass.h        |  18 ++++++
 src/blogc-make/settings.c    |   4 ++
 src/blogc-make/settings.h    |   1 +
 9 files changed, 227 insertions(+), 9 deletions(-)
 create mode 100644 src/blogc-make/sass.c
 create mode 100644 src/blogc-make/sass.h

(limited to 'src')

diff --git a/src/blogc-make/ctx.c b/src/blogc-make/ctx.c
index 423b334..408b6f2 100644
--- a/src/blogc-make/ctx.c
+++ b/src/blogc-make/ctx.c
@@ -6,6 +6,10 @@
  * See the file LICENSE.
  */
 
+#ifdef HAVE_CONFIG_H
+#include <config.h>
+#endif /* HAVE_CONFIG_H */
+
 #include <sys/stat.h>
 #include <sys/types.h>
 #include <dirent.h>
@@ -192,6 +196,16 @@ bm_ctx_new(bm_ctx_t *base, const char *settings_file, const char *argv0,
     }
     free(content);
 
+#ifndef USE_LIBSASS
+    if (settings->sass != NULL) {
+        *err = bc_error_new_printf(BLOGC_MAKE_ERROR_SETTINGS,
+            "[sass] section found in %s, but blogc-make was built without "
+            "libsass support", settings_file);
+        bm_settings_free(settings);
+        return NULL;
+    }
+#endif
+
     const char *template_dir = bc_trie_lookup(settings->settings, "template_dir");
     if (template_dir == NULL)
         template_dir = "";
diff --git a/src/blogc-make/exec-native.c b/src/blogc-make/exec-native.c
index 179216a..2417173 100644
--- a/src/blogc-make/exec-native.c
+++ b/src/blogc-make/exec-native.c
@@ -24,15 +24,9 @@
 
 
 int
-bm_exec_native_cp(bm_filectx_t *source, bm_filectx_t *dest, bool verbose)
+bm_exec_native_mkdir_p(const char *filename)
 {
-    if (verbose)
-        printf("Copying '%s' to '%s'\n", source->path, dest->path);
-    else
-        printf("  COPY     %s\n", dest->short_path);
-    fflush(stdout);
-
-    char *fname = bc_strdup(dest->path);
+    char *fname = bc_strdup(filename);
     for (char *tmp = fname; *tmp != '\0'; tmp++) {
         if (*tmp != '/' && *tmp != '\\')
             continue;
@@ -51,6 +45,23 @@ bm_exec_native_cp(bm_filectx_t *source, bm_filectx_t *dest, bool verbose)
     }
     free(fname);
 
+    return 0;
+}
+
+
+int
+bm_exec_native_cp(bm_filectx_t *source, bm_filectx_t *dest, bool verbose)
+{
+    if (verbose)
+        printf("Copying '%s' to '%s'\n", source->path, dest->path);
+    else
+        printf("  COPY     %s\n", dest->short_path);
+    fflush(stdout);
+
+    int rv = bm_exec_native_mkdir_p(dest->path);
+    if (rv != 0)
+        return rv;
+
     int fd_from = open(source->path, O_RDONLY);
     if (fd_from < 0) {
         fprintf(stderr, "blogc-make: error: failed to open source file to copy "
diff --git a/src/blogc-make/exec-native.h b/src/blogc-make/exec-native.h
index 56a1da1..0dfefcf 100644
--- a/src/blogc-make/exec-native.h
+++ b/src/blogc-make/exec-native.h
@@ -13,6 +13,7 @@
 #include "../common/error.h"
 #include "ctx.h"
 
+int bm_exec_native_mkdir_p(const char *filename);
 int bm_exec_native_cp(bm_filectx_t *source, bm_filectx_t *dest, bool verbose);
 bool bm_exec_native_is_empty_dir(const char *dir, bc_error_t **err);
 int bm_exec_native_rm(const char *output_dir, bm_filectx_t *dest, bool verbose);
diff --git a/src/blogc-make/main.c b/src/blogc-make/main.c
index 5b4a030..f18b9df 100644
--- a/src/blogc-make/main.c
+++ b/src/blogc-make/main.c
@@ -75,7 +75,11 @@ main(int argc, char **argv)
                     print_help();
                     goto cleanup;
                 case 'v':
-                    printf("%s\n", PACKAGE_STRING);
+                    printf(PACKAGE_STRING
+#ifdef USE_LIBSASS
+                           "+libsass"
+#endif
+                           "\n");
                     goto cleanup;
                 case 'D':
                     dev = true;
diff --git a/src/blogc-make/rules.c b/src/blogc-make/rules.c
index 06223c0..41e363d 100644
--- a/src/blogc-make/rules.c
+++ b/src/blogc-make/rules.c
@@ -6,6 +6,10 @@
  * See the file LICENSE.
  */
 
+#ifdef HAVE_CONFIG_H
+#include <config.h>
+#endif /* HAVE_CONFIG_H */
+
 #include <stdbool.h>
 #include <stdio.h>
 #include <string.h>
@@ -22,6 +26,10 @@
 #include "utils.h"
 #include "rules.h"
 
+#ifdef USE_LIBSASS
+#include "./sass.h"
+#endif
+
 
 static void
 posts_ordering(bm_ctx_t *ctx, bc_trie_t *variables, const char *variable)
@@ -850,6 +858,15 @@ const bm_rule_t rules[] = {
         .exec_func = copy_exec,
         .generate_files = true,
     },
+#ifdef USE_LIBSASS
+    {
+        .name = "sass",
+        .help = "build CSS stylesheets from Sass (.scss) sources",
+        .outputlist_func = bm_sass_outputlist,
+        .exec_func = bm_sass_exec,
+        .generate_files = true,
+    },
+#endif
     {
         .name = "clean",
         .help = "clean built files and empty directories in output directory",
diff --git a/src/blogc-make/sass.c b/src/blogc-make/sass.c
new file mode 100644
index 0000000..da4b8b6
--- /dev/null
+++ b/src/blogc-make/sass.c
@@ -0,0 +1,148 @@
+/*
+ * blogc: A blog compiler.
+ * Copyright (C) 2014-2019 Rafael G. Martins <rafael@rafaelmartins.eng.br>
+ *
+ * This program can be distributed under the terms of the BSD License.
+ * See the file LICENSE.
+ */
+
+#include <sass.h>
+#include <errno.h>
+#include <libgen.h>
+#include <stdlib.h>
+#include <stdio.h>
+#include <string.h>
+#include "ctx.h"
+#include "exec-native.h"
+#include "rules.h"
+#include "./sass.h"
+#include "../common/utils.h"
+
+
+bc_slist_t*
+bm_sass_outputlist(bm_ctx_t *ctx)
+{
+    if (ctx == NULL || ctx->settings == NULL || ctx->settings->sass == NULL)
+        return NULL;
+
+    bc_slist_t *rv = NULL;
+
+    for (size_t i = 0; ctx->settings->sass[i] != NULL; i++) {
+        char *t = bc_strdup(ctx->settings->sass[i]);
+        for (int i = strlen(t); i >= 0 ; i--) {
+            if (t[i] == '.') {
+                t[i] = '\0';
+                break;
+            }
+        }
+
+        char *f = bc_strdup_printf("%s/%s.css", ctx->short_output_dir, t);
+        free(t);
+        rv = bc_slist_append(rv, bm_filectx_new(ctx, f, NULL, NULL));
+        free(f);
+    }
+
+    return rv;
+}
+
+
+int
+bm_sass_exec(bm_ctx_t *ctx, bc_slist_t *outputs, bc_trie_t *args)
+{
+    if (ctx == NULL || ctx->settings == NULL || ctx->settings->sass == NULL)
+        return 0;
+
+    int rv = 0;
+
+    size_t i = 0;
+    bc_slist_t *o = outputs;
+
+    for (; ctx->settings->sass[i] != NULL, o != NULL; i++, o = o->next) {
+        bm_filectx_t *o_fctx = o->data;
+        if (o_fctx == NULL)
+            continue;
+
+        if (bc_str_starts_with(basename(ctx->settings->sass[i]), "_")) {
+            fprintf(stderr, "blogc-make: error: sass: Mixins must be included "
+                "by other files (%s)\n", ctx->settings->sass[i]);
+            return 3;
+        }
+        if (!bc_str_ends_with(ctx->settings->sass[i], ".scss")) {
+            fprintf(stderr, "blogc-make: error: sass: Only .scss Sass sources "
+                "are supported (%s)\n", ctx->settings->sass[i]);
+            return 3;
+        }
+
+        bm_filectx_t *f = bm_filectx_new(ctx, ctx->settings->sass[i], NULL, NULL);
+
+        struct Sass_File_Context *fctx = sass_make_file_context(f->path);
+        struct Sass_Context *sctx = sass_file_context_get_context(fctx);
+        struct Sass_Compiler *comp = sass_make_file_compiler(fctx);
+        struct Sass_Options *opts = sass_context_get_options(sctx);
+        sass_option_set_output_style(opts, SASS_STYLE_COMPRESSED);
+        char *sass_dir = bc_strdup_printf("%s/sass", ctx->root_dir);
+        sass_option_push_include_path(opts, sass_dir);
+        free(sass_dir);
+        sass_dir = bc_strdup_printf("%s/_sass", ctx->root_dir);
+        sass_option_push_include_path(opts, sass_dir);
+        free(sass_dir);
+
+        bc_slist_t *sources = NULL;
+
+        rv = sass_compiler_parse(comp);
+        if (rv != 0 || 0 != sass_context_get_error_status(sctx)) {
+            fprintf(stderr, "blogc-make: error: sass: %s\n%s",
+                o_fctx->path, sass_context_get_error_message(sctx));
+            rv = 3;
+            goto clean;
+        }
+
+        char **inc = sass_context_get_included_files(sctx);
+        for (size_t i = 0; inc != NULL && inc[i] != NULL; i++) {
+            sources = bc_slist_append(sources, bm_filectx_new(ctx, inc[i], NULL, NULL));
+        }
+
+        if (!bm_rule_need_rebuild(sources, ctx->settings_fctx, NULL, NULL, o_fctx, false))
+            goto clean;
+
+        if (ctx->verbose)
+            printf("Compiling '%s' to '%s'\n", f->path, o_fctx->path);
+        else
+            printf("  SASS     %s\n", o_fctx->short_path);
+        fflush(stdout);
+
+        rv = sass_compiler_execute(comp);
+        if (rv != 0 || 0 != sass_context_get_error_status(sctx)) {
+            fprintf(stderr, "blogc-make: error: sass: %s\n%s",
+                o_fctx->path, sass_context_get_error_message(sctx));
+            rv = 3;
+            goto clean;
+        }
+
+        rv = bm_exec_native_mkdir_p(o_fctx->path);
+        if (rv != 0)
+            goto clean;
+
+        FILE *fp = fopen(o_fctx->path, "w");
+        if (fp == NULL) {
+            fprintf(stderr, "blogc-make: error: sass: failed to open output "
+                "file (%s): %s\n", o_fctx->path, strerror(errno));
+            rv = 3;
+            goto clean;
+        }
+
+        fputs(sass_context_get_output_string(sctx), fp);
+        fclose(fp);
+
+clean:
+        bm_filectx_free(f);
+        bc_slist_free_full(sources, (bc_free_func_t) bm_filectx_free);
+        sass_delete_compiler(comp);
+        sass_delete_file_context(fctx);
+
+        if (rv != 0)
+            return rv;
+    }
+
+    return 0;
+}
diff --git a/src/blogc-make/sass.h b/src/blogc-make/sass.h
new file mode 100644
index 0000000..351b736
--- /dev/null
+++ b/src/blogc-make/sass.h
@@ -0,0 +1,18 @@
+/*
+ * blogc: A blog compiler.
+ * Copyright (C) 2014-2019 Rafael G. Martins <rafael@rafaelmartins.eng.br>
+ *
+ * This program can be distributed under the terms of the BSD License.
+ * See the file LICENSE.
+ */
+
+#ifndef _MAKE_SASS_H
+#define _MAKE_SASS_H
+
+#include "ctx.h"
+#include "../common/utils.h"
+
+bc_slist_t* bm_sass_outputlist(bm_ctx_t *ctx);
+int bm_sass_exec(bm_ctx_t *ctx, bc_slist_t *outputs, bc_trie_t *args);
+
+#endif /* _MAKE_SASS_H */
diff --git a/src/blogc-make/settings.c b/src/blogc-make/settings.c
index be976e3..f13d5b2 100644
--- a/src/blogc-make/settings.c
+++ b/src/blogc-make/settings.c
@@ -72,6 +72,7 @@ static const char* list_sections[] = {
     "copy",
     "copy_files",  // backward compatibility
     "tags",
+    "sass",
     NULL,
 };
 
@@ -97,6 +98,7 @@ bm_settings_parse(const char *content, size_t content_len, bc_error_t **err)
     rv->pages = NULL;
     rv->copy = NULL;
     rv->tags = NULL;
+    rv->sass = NULL;
 
     // this is some code for compatibility with the [environment] section,
     // even if I never released a version with it, but some people is using
@@ -159,6 +161,7 @@ bm_settings_parse(const char *content, size_t content_len, bc_error_t **err)
     rv->posts = bc_config_get_list(config, "posts");
     rv->pages = bc_config_get_list(config, "pages");
     rv->tags = bc_config_get_list(config, "tags");
+    rv->sass = bc_config_get_list(config, "sass");
 
     // this is for backward compatibility too.
     rv->copy = bc_config_get_list(config, "copy");
@@ -184,5 +187,6 @@ bm_settings_free(bm_settings_t *settings)
     bc_strv_free(settings->pages);
     bc_strv_free(settings->copy);
     bc_strv_free(settings->tags);
+    bc_strv_free(settings->sass);
     free(settings);
 }
diff --git a/src/blogc-make/settings.h b/src/blogc-make/settings.h
index 69fdbb6..8060963 100644
--- a/src/blogc-make/settings.h
+++ b/src/blogc-make/settings.h
@@ -20,6 +20,7 @@ typedef struct {
     char **pages;
     char **copy;
     char **tags;
+    char **sass;
 } bm_settings_t;
 
 bm_settings_t* bm_settings_parse(const char *content, size_t content_len,
-- 
cgit v1.2.3-18-g5258