Project

hsss

0.0
Low commit activity in last 3 years
No release in over a year
Hash-Safe Script Splinterer: Transforms Lua files into structs of C-strings and associated headers. Great for embedding Redis Lua scripts.
2005
2006
2007
2008
2009
2010
2011
2012
2013
2014
2015
2016
2017
2018
2019
2020
2021
2022
2023
2024
2025
 Dependencies

Development

>= 0
>= 0
 Project Readme

Hsss

Hash-Safe Script Splinterer, a Lua Script and hash embedder into C source. Good for putting Redis Lua scripts in your C headers.

Usage: hsss [options] files > output_file.h
        --format [split|whole]       Output as separate or a single struct
        --struct [redis_lua_scripts_t]
                                     C struct name
        --row-struct [redis_lua_script_t]
                                     Hash+name+script struct for 'whole' format.
        --scripts [redis_lua_scripts]
                                     Scripts variable (split or whole format)
        --hashes [redis_lua_hashes]  Hashes variable (split format)
        --no-hashes                  Omit hashes variable (split format)
        --names [redis_lua_script_names]
                                     Script names variable (split format)
        --no-names                   Omit script names variable (split format)
        --count [redis_lua_scripts_count]
                                     integer script count variable
        --no-count                   Omit script count variable
        --each-macro [REDIS_LUA_SCRIPTS_EACH]
                                     Iterator macro
        --no-each                    Omit the iterator macro
        --no-parse                   Skip using luac to check script syntax
        --no-static                  Don't make variables static (file-scoped)
        --prefix PREFIX              Prefix default names with this

Example

Let's say you have two scripts in directory example/:

echo.lua:

--echoes the first argument
redis.call('echo', ARGS[1])

delete.lua

--deletes first key
redis.call('del', KEYS[1])

running hsss --format whole example/*.lua produces

// don't edit this please, it was auto-generated by hsss
// https://github.com/slact/hsss

typedef struct {
  char *name;
  char *hash;
  char *script;
} redis_lua_script_t;

typedef struct {
  //deletes first key
  redis_lua_script_t delete;

  //echoes the first argument
  redis_lua_script_t echo;

} redis_lua_scripts_t;

static redis_lua_scripts_t redis_lua_scripts = {
  {"delete", "c6929c34f10b0fe8eaba42cde275652f32904e03",
   "--deletes first key\n"
   "redis.call('del', KEYS[1])\n"},

  {"echo", "8f8f934c6049ab4d6337cfa53976893417b268bc",
   "--echoes the first argument\n"
   "redis.call('echo', ARGS[1])\n"}
};

const int redis_lua_scripts_count=2;
#define REDIS_LUA_SCRIPTS_EACH(script) \
for((script)=(redis_lua_script_t *)&redis_lua_scripts; (script) < (redis_lua_script_t *)(&redis_lua_scripts + 1); (script)++)

running hsss --format split example/*.lua produces

// don't edit this please, it was auto-generated by hsss
// https://github.com/slact/hsss

typedef struct {
  //deletes first key
  char *delete;

  //echoes the first argument
  char *echo;

} redis_lua_script_t;

static redis_lua_script_t redis_lua_hashes = {
  "c6929c34f10b0fe8eaba42cde275652f32904e03",
  "8f8f934c6049ab4d6337cfa53976893417b268bc"
};

static redis_lua_script_t redis_lua_script_names = {
  "delete",
  "echo",
};

static redis_lua_script_t redis_lua_scripts = {
  //delete
  "--deletes first key\n"
  "redis.call('del', KEYS[1])\n",

  //echo
  "--echoes the first argument\n"
  "redis.call('echo', ARGS[1])\n"
};

const int redis_lua_scripts_count=2;
#define REDIS_LUA_SCRIPTS_EACH(script_src, script_name, script_hash) \
for((script_src)=(char **)&redis_lua_scripts, (script_hash)=(char **)&redis_lua_hashes, (script_name)=(char **)&redis_lua_script_names; (script_src) < (char **)(&redis_lua_scripts + 1); (script_src)++, (script_hash)++, (script_name)++)

Using in your C code

  • EVALSHA:

    //whole format
    redisAsyncCommand(asyncContext, callback, data, "EVALSHA %s 0", redis_lua_scripts.script_name.hash);
    
    //split format
    redisAsyncCommand(asyncContext, callback, data, "EVALSHA %s 0", redis_lua_hashes.script_name);
  • iterator macro, loading scripts

    //split format:
    
    //privdata for error checking callback
    typedef struct {
      char      *name;
      char      *hash;
    } script_hash_and_name_t;
    
    //error checking callback
    static void redisLoadScriptCallback(redisAsyncContext *c, void *r, void *privdata) {
      script_hash_and_name_t *hn = privdata;
      redisReply *reply = r;
      switch(reply->type) {
        case REDIS_REPLY_ERROR:
          printf("nchan: Failed loading redis lua script %s :%s", hn->name, reply->str);
          break;
        case REDIS_REPLY_STRING:
          if(strcmp(reply->str, hn->hash)!=0) { //sha1 hash length is 40 chars
            printf("nchan Redis lua script %s has unexpected hash %s (expected %s)", hn->name, reply->str, hn->hash);
          }
          break;
      }
      free(hn);
    }
    
    static void redisInitScripts(redisAsyncContext *c){
      char **script, **script_name, **script_hash;
      
      REDIS_LUA_SCRIPTS_EACH(script, script_name, script_hash) {
        script_hash_and_name_t *hn = alloc(sizeof(*hn));
        hn->name=*script_name;
        hn->hash=*script_hash;
        redisAsyncCommand(c, redisLoadScriptCallback, hn, "SCRIPT LOAD %s", *script);
      }
    }
    //whole format:
    
    //error checking callback
    static void redisLoadScriptCallback(redisAsyncContext *c, void *r, void *privdata) {
      redis_lua_script_t  *script = privdata;
      redisReply *reply = r;
      if (reply == NULL) return;
      switch(reply->type) {
        case REDIS_REPLY_ERROR:
          printf("Failed loading redis lua script %s :%s", script->name, reply->str);
          break;
        case REDIS_REPLY_STRING:
          if(nstrncmp(reply->str, script->hash, 40)!=0) {
            printf("Redis lua script %s has unexpected hash %s (expected %s)", script->name, reply->str, script->hash);
          }
          break;
      }
    }
    
    static void redisInitScripts(redisAsyncContext *c){
      redis_lua_script_t  *script;
      
      REDIS_LUA_SCRIPTS_EACH(script) {
        redisAsyncCommand(c, redisLoadScriptCallback, script, "SCRIPT LOAD %s", script->script);
      }
    }

Using in your build tooling

Makefile:

lua_scripts.h: scripts/*.lua
	hsss scripts/*.lua > lua_scripts.h

main_build_rule: lua_scripts.h

License

The gem is available as open source under the terms of the MIT License.