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
    ```c //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);
}

}


  ```c
  //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.