Hash-Safe Script Splinterer: Conveniently embed lua scripts and their hashes in C. Best with Redis.
Clone or download
Fetching latest commit…
Cannot retrieve the latest commit at this time.
Permalink
Type Name Latest commit message Commit time
Failed to load latest commit information.
bin
exe
lib
.gitignore
CODE_OF_CONDUCT.md
Gemfile
LICENSE.txt
README.md
Rakefile
hsss.gemspec

README.md

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.