258 lines
8.4 KiB
Objective-C
258 lines
8.4 KiB
Objective-C
/// === hs.doc.markdown ===
|
|
///
|
|
/// Markdown to HTML and plaintext conversion support used by hs.doc
|
|
///
|
|
/// This module provides Github-Flavored-Markdown conversion support used by hs.doc. This module is a Lua wrapper to the C code portion of the Ruby gem `github-markdown`, available at https://rubygems.org/gems/github-markdown/versions/0.6.9.
|
|
///
|
|
/// The Ruby gem `github-markdown` was chosen as the code base for this module because it is the tool used to generate the official Hammerspoon Dash docset.
|
|
///
|
|
/// The Lua wrapper portion is licensed under the MIT license by the Hammerspoon development team. The C code portion of the Ruby gem is licensed under the MIT license by GitHub, Inc.
|
|
|
|
// #import <Cocoa/Cocoa.h>
|
|
#import <LuaSkin/LuaSkin.h>
|
|
#include "markdown.h"
|
|
#include "html.h"
|
|
#include "plaintext.h"
|
|
#include "houdini.h"
|
|
|
|
static LSRefTable refTable = LUA_NOREF;
|
|
|
|
typedef enum {
|
|
GFM,
|
|
MARKDOWN,
|
|
PLAINTEXT,
|
|
} ModeType;
|
|
|
|
#pragma mark - Support Functions and Classes
|
|
|
|
static struct {
|
|
struct sd_markdown *md;
|
|
struct html_renderopt render_opts;
|
|
} g_markdown, g_GFM, g_plaintext;
|
|
|
|
static void rndr_blockcode_github(struct buf *ob, const struct buf *text, const struct buf *lang, __unused void *opaque) {
|
|
if (ob->size)
|
|
bufputc(ob, '\n');
|
|
|
|
if (!text || !text->size) {
|
|
BUFPUTSL(ob, "<pre><code></code></pre>");
|
|
return;
|
|
}
|
|
|
|
if (lang && lang->size) {
|
|
size_t i = 0, lang_size;
|
|
const char *lang_name = NULL;
|
|
|
|
while (i < lang->size && !isspace(lang->data[i]))
|
|
i++;
|
|
|
|
if (lang->data[0] == '.') {
|
|
lang_name = (const char *)(lang->data + 1);
|
|
lang_size = i - 1;
|
|
} else {
|
|
lang_name = (const char *)lang->data;
|
|
lang_size = i;
|
|
}
|
|
|
|
// if (rb_block_given_p()) {
|
|
// VALUE hilight;
|
|
//
|
|
// hilight = rb_yield_values(2,
|
|
// geefem_str_new(text->data, text->size),
|
|
// geefem_str_new(lang_name, lang_size));
|
|
//
|
|
// if (!NIL_P(hilight)) {
|
|
// Check_Type(hilight, T_STRING);
|
|
// bufput(ob, RSTRING_PTR(hilight), RSTRING_LEN(hilight));
|
|
// return;
|
|
// }
|
|
// }
|
|
|
|
BUFPUTSL(ob, "<pre lang=\"");
|
|
houdini_escape_html0(ob, (const uint8_t *)lang_name, lang_size, 0);
|
|
BUFPUTSL(ob, "\"><code>");
|
|
|
|
} else {
|
|
BUFPUTSL(ob, "<pre><code>");
|
|
}
|
|
|
|
houdini_escape_html0(ob, text->data, text->size, 0);
|
|
BUFPUTSL(ob, "</code></pre>\n");
|
|
}
|
|
|
|
/* Max recursion nesting when parsing Markdown documents */
|
|
static const int GITHUB_MD_NESTING = 32;
|
|
|
|
/* Default flags for all Markdown pipelines:
|
|
*
|
|
* - NO_INTRA_EMPHASIS: disallow emphasis inside of words
|
|
* - LAX_SPACING: Do spacing like in Markdown 1.0.0 (i.e.
|
|
* do not require an empty line between two different
|
|
* blocks in a paragraph)
|
|
* - STRIKETHROUGH: strike out words with `~~`, same semantics
|
|
* as emphasis
|
|
* - TABLES: the tables extension from PHP-Markdown extra
|
|
* - FENCED_CODE: the fenced code blocks extension from
|
|
* PHP-Markdown extra, but working with ``` besides ~~~.
|
|
* - AUTOLINK: Well. That. Link stuff automatically.
|
|
*/
|
|
static const int GITHUB_MD_FLAGS =
|
|
MKDEXT_NO_INTRA_EMPHASIS |
|
|
MKDEXT_LAX_SPACING |
|
|
MKDEXT_STRIKETHROUGH |
|
|
MKDEXT_TABLES |
|
|
MKDEXT_FENCED_CODE |
|
|
MKDEXT_AUTOLINK;
|
|
|
|
/* Init the default pipeline */
|
|
static void ghmd__init_md(void)
|
|
{
|
|
struct sd_callbacks callbacks;
|
|
|
|
/* No extra flags to the Markdown renderer */
|
|
sdhtml_renderer(&callbacks, &g_markdown.render_opts, 0);
|
|
callbacks.blockcode = &rndr_blockcode_github;
|
|
|
|
g_markdown.md = sd_markdown_new(
|
|
GITHUB_MD_FLAGS,
|
|
GITHUB_MD_NESTING,
|
|
&callbacks,
|
|
&g_markdown.render_opts
|
|
);
|
|
}
|
|
|
|
/* Init the GFM pipeline */
|
|
static void ghmd__init_gfm(void)
|
|
{
|
|
struct sd_callbacks callbacks;
|
|
|
|
/*
|
|
* The following extensions to the HTML output are enabled:
|
|
*
|
|
* - HARD_WRAP: line breaks are replaced with <br>
|
|
* entities
|
|
*/
|
|
sdhtml_renderer(&callbacks, &g_GFM.render_opts, HTML_HARD_WRAP);
|
|
callbacks.blockcode = &rndr_blockcode_github;
|
|
|
|
/* The following extensions to the parser are enabled, on top
|
|
* of the common ones:
|
|
*
|
|
* - SPACE_HEADERS: require a space between the `#` and the
|
|
* name of a header (prevents collisions with the Issues
|
|
* filter)
|
|
*/
|
|
g_GFM.md = sd_markdown_new(
|
|
GITHUB_MD_FLAGS | MKDEXT_SPACE_HEADERS,
|
|
GITHUB_MD_NESTING,
|
|
&callbacks,
|
|
&g_GFM.render_opts
|
|
);
|
|
}
|
|
|
|
static void ghmd__init_plaintext(void)
|
|
{
|
|
struct sd_callbacks callbacks;
|
|
|
|
sdtext_renderer(&callbacks);
|
|
g_plaintext.md = sd_markdown_new(
|
|
GITHUB_MD_FLAGS,
|
|
GITHUB_MD_NESTING,
|
|
&callbacks, NULL
|
|
);
|
|
}
|
|
|
|
#pragma mark - Module Functions
|
|
|
|
/// hs.doc.markdown.convert(markdown, [type]) -> output
|
|
/// Function
|
|
/// Converts markdown encoded text to html or plaintext.
|
|
///
|
|
/// Parameters:
|
|
/// * markdown - a string containing the input text encoded using markdown tags
|
|
/// * type - an optional string specifying the conversion options and output type. Defaults to "gfm". The currently recognized types are:
|
|
/// * "markdown" - specfies that the output should be HTML with the standard GitHub/Markdown extensions enabled.
|
|
/// * "gfm" - specifies that the output should be HTML with additional GitHub extensions enabled.
|
|
/// * "plaintext" - specifies that the output should plain text with the standard GitHub/Markdown extensions enabled.
|
|
///
|
|
/// Returns:
|
|
/// * an HTML or plaintext representation of the markdown encoded text provided.
|
|
///
|
|
/// Notes:
|
|
/// * The standard GitHub/Markdown extensions enabled for all conversions are:
|
|
/// * NO_INTRA_EMPHASIS - disallow emphasis inside of words
|
|
/// * LAX_SPACING - supports spacing like in Markdown 1.0.0 (i.e. do not require an empty line between two different blocks in a paragraph)
|
|
/// * STRIKETHROUGH - support strikethrough with double tildes (~)
|
|
/// * TABLES - support Markdown tables
|
|
/// * FENCED_CODE - supports fenced code blocks surround by three back-ticks (`) or three tildes (~)
|
|
/// * AUTOLINK - HTTP URL's are treated as links, even if they aren't marked as such with Markdown tags
|
|
///
|
|
/// * The "gfm" type also includes the following extensions:
|
|
/// * HARD_WRAP - line breaks are replaced with <br> entities
|
|
/// * SPACE_HEADERS - require a space between the `#` and the name of a header (prevents collisions with the Issues filter)
|
|
static int to_html(lua_State *L) {
|
|
LuaSkin *skin = [LuaSkin sharedWithState:L] ;
|
|
[skin checkArgs:LS_TSTRING, LS_TSTRING | LS_TOPTIONAL, LS_TBREAK] ;
|
|
ModeType mode = GFM ;
|
|
if (lua_gettop(L) == 2) {
|
|
NSString *modeString = [skin toNSObjectAtIndex:2] ;
|
|
if ([modeString isEqualToString:@"gfm"]) {
|
|
mode = GFM ;
|
|
} else if ([modeString isEqualToString:@"markdown"] || [modeString isEqualToString:@"readme"]) {
|
|
mode = MARKDOWN ;
|
|
} else if ([modeString isEqualToString:@"plaintext"]) {
|
|
mode = PLAINTEXT;
|
|
} else {
|
|
return luaL_argerror(L, 2, [[NSString stringWithFormat:@"invalide mode, %@, specified", modeString] UTF8String]) ;
|
|
}
|
|
}
|
|
|
|
NSData *textBody = [skin toNSObjectAtIndex:1 withOptions:LS_NSLuaStringAsDataOnly] ;
|
|
|
|
struct buf *output_buf;
|
|
struct sd_markdown *md = NULL;
|
|
|
|
/* check for rendering mode */
|
|
if (mode == MARKDOWN) {
|
|
md = g_markdown.md;
|
|
} else if (mode == GFM) {
|
|
md = g_GFM.md;
|
|
} else if (mode == PLAINTEXT) {
|
|
md = g_plaintext.md;
|
|
} else {
|
|
return luaL_error(L, "Invalid render mode");
|
|
}
|
|
|
|
/* initialize buffers */
|
|
output_buf = bufnew(128);
|
|
|
|
/* render the magic */
|
|
sd_markdown_render(output_buf, [textBody bytes], [textBody length], md);
|
|
|
|
/* build the Lua string */
|
|
NSData *outputData = [NSData dataWithBytes:output_buf->data length:output_buf->size];
|
|
[skin pushNSObject:outputData];
|
|
bufrelease(output_buf);
|
|
|
|
return 1 ;
|
|
}
|
|
|
|
#pragma mark - Hammerspoon/Lua Infrastructure
|
|
|
|
// Functions for returned object when module loads
|
|
static luaL_Reg moduleLib[] = {
|
|
{"convert", to_html},
|
|
{NULL, NULL}
|
|
};
|
|
|
|
int luaopen_hs_libmarkdown(lua_State* L) {
|
|
LuaSkin *skin = [LuaSkin sharedWithState:L] ;
|
|
refTable = [skin registerLibrary:"hs.doc.markdown" functions:moduleLib metaFunctions:nil] ;
|
|
|
|
ghmd__init_md();
|
|
ghmd__init_gfm();
|
|
ghmd__init_plaintext();
|
|
|
|
return 1;
|
|
}
|