From: Fletcher T. Penney Date: Sat, 11 Feb 2017 22:53:46 +0000 (-0500) Subject: ADDED: Add file transclusion X-Git-Tag: 0.3.0a^2~24 X-Git-Url: https://granicus.if.org/sourcecode?a=commitdiff_plain;h=384874cd3ad3181580835b573585e68b45843880;p=multimarkdown ADDED: Add file transclusion --- diff --git a/CMakeLists.txt b/CMakeLists.txt index e3ec4ca..9a1c812 100644 --- a/CMakeLists.txt +++ b/CMakeLists.txt @@ -180,6 +180,7 @@ set(src_files src/stack.c src/token.c src/token_pairs.c + src/transclude.c src/writer.c ) @@ -197,6 +198,7 @@ set(header_files src/stack.h src/token.h src/token_pairs.h + src/transclude.h src/uthash.h src/writer.h ) diff --git a/src/html.c b/src/html.c index c6ac00a..4e46942 100644 --- a/src/html.c +++ b/src/html.c @@ -1017,7 +1017,7 @@ void mmd_export_token_html(DString * out, const char * source, token * t, size_t temp_bool = true; if (t->type == PAIR_BRACKET) { - // This is a locator for subsequent ciation + // This is a locator for subsequent citation temp_char = text_inside_pair(source, t); temp_char2 = label_from_string(temp_char); diff --git a/src/libMultiMarkdown.h b/src/libMultiMarkdown.h index c1bcf3d..6686084 100644 --- a/src/libMultiMarkdown.h +++ b/src/libMultiMarkdown.h @@ -98,6 +98,14 @@ token * mmd_engine_parse_substring(mmd_engine * e, size_t byte_start, size_t byt void mmd_engine_parse_string(mmd_engine * e); +/// Does the text have metadata? +bool mmd_has_metadata(mmd_engine * e, size_t * end); + + +/// Extract desired metadata as string value +char * metavalue_for_key(mmd_engine * e, const char * key); + + void mmd_export_token_tree(DString * out, mmd_engine * e, short format); @@ -287,6 +295,7 @@ enum smart_quotes_language { enum output_format { + FORMAT_MMD, FORMAT_HTML, FORMAT_LATEX, FORMAT_ODF, @@ -314,6 +323,7 @@ enum parser_extensions { EXT_ESCAPED_LINE_BREAKS = 1 << 17, //!< Escaped line break EXT_NO_STRONG = 1 << 18, //!< Don't allow nested \'s EXT_NO_EMPH = 1 << 19, //!< Don't allow nested \'s + EXT_TRANSCLUDE = 1 << 20, //!< Perform transclusion(s) EXT_FAKE = 1 << 31, //!< 31 is highest number allowed }; diff --git a/src/main.c b/src/main.c index b5cd556..cf6f88a 100644 --- a/src/main.c +++ b/src/main.c @@ -54,6 +54,7 @@ */ #include +#include #include #include #include @@ -66,6 +67,7 @@ #include "html.h" #include "mmd.h" #include "token.h" +#include "transclude.h" #include "version.h" #define kBUFFERSIZE 4096 // How many bytes to read at a time @@ -97,7 +99,7 @@ DString * stdin_buffer() { } -DString * scan_file(const char * fname) { +static DString * scan_file(const char * fname) { /* Read from a file and return a GString * `buffer` will need to be freed elsewhere */ @@ -177,7 +179,7 @@ char * mmd_process(DString * buffer, unsigned long extensions, short format, sho int main(int argc, char** argv) { int exitcode = EXIT_SUCCESS; char * binname = "multimarkdown"; - short format = 0; + short format = FORMAT_HTML; short language = LC_EN; // Initialize argtable structs @@ -247,7 +249,7 @@ int main(int argc, char** argv) { // Parse options - unsigned long extensions = EXT_SMART | EXT_NOTES | EXT_CRITIC; + unsigned long extensions = EXT_SMART | EXT_NOTES | EXT_CRITIC | EXT_TRANSCLUDE; if (a_compatibility->count > 0) { // Compatibility mode disables certain features @@ -338,6 +340,15 @@ int main(int argc, char** argv) { break; } + // Perform transclusion(s) + if (extensions & EXT_TRANSCLUDE) { + char * folder = dirname((char *) a_file->filename[i]); + + transclude_source(buffer, folder, format, NULL, NULL); + + free(folder); + } + result = mmd_process(buffer, extensions, format, language); if (!(output_stream = fopen(output_filename, "w"))) { @@ -378,6 +389,15 @@ int main(int argc, char** argv) { buffer = stdin_buffer(); } + if ((extensions & EXT_TRANSCLUDE) && (a_file->count == 1)) { + // Perform transclusion(s) + char * folder = dirname((char *) a_file->filename[0]); + + transclude_source(buffer, folder, format, NULL, NULL); + + free(folder); + } + result = mmd_process(buffer, extensions, format, language); // Where does output go? diff --git a/src/mmd.c b/src/mmd.c index 103baa3..933d44c 100644 --- a/src/mmd.c +++ b/src/mmd.c @@ -729,7 +729,12 @@ void strip_quote_markers_from_block(mmd_engine * e, token * block) { /// Create a token chain from source string -token * mmd_tokenize_string(mmd_engine * e, const char * str, size_t len) { +/// stop_on_empty_line allows us to stop parsing part of the way through +token * mmd_tokenize_string(mmd_engine * e, const char * str, size_t len, bool stop_on_empty_line) { + // Reset metadata flag + e->allow_meta = (e->extensions & EXT_COMPATIBILITY) ? false : true; + + // Create a scanner (for re2c) Scanner s; s.start = str; @@ -793,6 +798,11 @@ token * mmd_tokenize_string(mmd_engine * e, const char * str, size_t len) { mmd_assign_line_type(e, line); token_append_child(root, line); + + if (stop_on_empty_line) { + if (line->type == LINE_EMPTY) + return root; + } line = token_new(0,s.cur - str,0); break; default: @@ -1709,7 +1719,7 @@ token * mmd_engine_parse_substring(mmd_engine * e, size_t byte_start, size_t byt e->definition_stack->size = 0; // Tokenize the string - token * doc = mmd_tokenize_string(e, &e->dstr->str[byte_start], byte_len); + token * doc = mmd_tokenize_string(e, &e->dstr->str[byte_start], byte_len, false); // Parse tokens into blocks mmd_parse_token_chain(e, doc); @@ -1751,3 +1761,64 @@ void mmd_engine_parse_string(mmd_engine * e) { e->root = mmd_engine_parse_substring(e, 0, e->dstr->currentStringLength); } + +bool mmd_has_metadata(mmd_engine * e, size_t * end) { + bool result = false; + + // Free existing parse tree + if (e->root) + token_tree_free(e->root); + +#ifdef kUseObjectPool + // Ensure token pool is available and ready + token_pool_init(); +#endif + + // Tokenize the string (up until first empty line) + token * doc = mmd_tokenize_string(e, &e->dstr->str[0], e->dstr->currentStringLength, true); + + // Parse tokens into blocks + mmd_parse_token_chain(e, doc); + + if (doc) { + if (doc->child && doc->child->type == BLOCK_META) { + result = true; + + if (end != NULL) + *end = doc->child->len; + } + + token_tree_free(doc); + } + + return result; +} + + +/// Grab metadata without processing entire document +/// Returned char * does not need to be freed +char * metavalue_for_key(mmd_engine * e, const char * key) { + if (e->metadata_stack->size == 0) { + // Ensure we have checked for metadata + if (!mmd_has_metadata(e, NULL)) + return NULL; + } + + char * result = NULL; + char * clean = label_from_string(key); + + meta * m; + + for (int i = 0; i < e->metadata_stack->size; ++i) + { + m = stack_peek_index(e->metadata_stack, i); + + if (strcmp(clean, m->key) == 0) { + // We have a match + return m->value; + } + } + + return result; +} + diff --git a/src/parser.c b/src/parser.c index 4ac2a28..2c09820 100644 --- a/src/parser.c +++ b/src/parser.c @@ -1114,6 +1114,7 @@ static void yy_reduce( break; case 2: /* blocks ::= block */ { + engine->root = yymsp[0].minor.yy0; strip_line_tokens_from_block(engine, yymsp[0].minor.yy0); #ifndef NDEBUG fprintf(stderr, "First block %d\n", yymsp[0].minor.yy0->type); diff --git a/src/parser.y b/src/parser.y index cfbc249..60cc871 100644 --- a/src/parser.y +++ b/src/parser.y @@ -84,6 +84,7 @@ blocks(A) ::= blocks(B) block(C). } blocks(A) ::= block(B). { + engine->root = B; // In case the first block is metadata and we just want to know if it exists strip_line_tokens_from_block(engine, B); #ifndef NDEBUG fprintf(stderr, "First block %d\n", B->type); diff --git a/src/token.c b/src/token.c index e998375..ffb8750 100644 --- a/src/token.c +++ b/src/token.c @@ -76,10 +76,6 @@ void token_pool_init(void) { if (token_pool == NULL) { // No pool exists token_pool = pool_new(sizeof(token)); - } else { - // Pool exists, ensure it's drained - // NOTE: This invalidates any tokens currently in use. - token_pool_drain(); } } diff --git a/src/transclude.c b/src/transclude.c new file mode 100644 index 0000000..885e21a --- /dev/null +++ b/src/transclude.c @@ -0,0 +1,440 @@ +/** + + MultiMarkdown 6 -- Lightweight markup processor to produce HTML, LaTeX, and more. + + @file transclude.c + + @brief + + + @author Fletcher T. Penney + @bug + +**/ + +/* + + Copyright © 2016 - 2017 Fletcher T. Penney. + + + The `MultiMarkdown 6` project is released under the MIT License.. + + GLibFacade.c and GLibFacade.h are from the MultiMarkdown v4 project: + + https://github.com/fletcher/MultiMarkdown-4/ + + MMD 4 is released under both the MIT License and GPL. + + + CuTest is released under the zlib/libpng license. See CuTest.c for the text + of the license. + + + ## The MIT License ## + + Permission is hereby granted, free of charge, to any person obtaining a copy + of this software and associated documentation files (the "Software"), to deal + in the Software without restriction, including without limitation the rights + to use, copy, modify, merge, publish, distribute, sublicense, and/or sell + copies of the Software, and to permit persons to whom the Software is + furnished to do so, subject to the following conditions: + + The above copyright notice and this permission notice shall be included in + all copies or substantial portions of the Software. + + THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR + IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, + FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE + AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER + LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, + OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN + THE SOFTWARE. + +*/ + +#include +#include + +#include "d_string.h" +#include "libMultiMarkdown.h" +#include "transclude.h" + +#if defined(__WIN32) +#include +#endif + +#define kBUFFERSIZE 4096 // How many bytes to read at a time + + +/// Windows can use either `\` or `/` as a separator -- thanks to t-beckmann on github +/// for suggesting a fix for this. +bool is_separator(char c) { +#if defined(__WIN32) + return c == '\\' || c == '/'; +#else + return c == '/'; +#endif +} + + +#ifdef TEST +void Test_is_separator(CuTest* tc) { + char * test = "a/\\"; + +#if defined(__WIN32) + CuAssertIntEquals(tc, false, is_separator(test[0])); + CuAssertIntEquals(tc, true, is_separator(test[1])); + CuAssertIntEquals(tc, true, is_separator(test[2])); +#else + CuAssertIntEquals(tc, false, is_separator(test[0])); + CuAssertIntEquals(tc, true, is_separator(test[1])); + CuAssertIntEquals(tc, false, is_separator(test[2])); +#endif +} +#endif + + +void add_trailing_sep(DString * path) { +#if defined(__WIN32) + char sep = '\\'; +#else + char sep = '/'; +#endif + + // Ensure that folder ends in separator + if (!is_separator(path->str[path->currentStringLength - 1])) { + d_string_append_c(path, sep); + } +} + +/// Combine directory and base filename to create a full path */ +char * path_from_dir_base(char * dir, char * base) { + if (!dir && !base) + return NULL; + + + DString * path = NULL; + char * result = NULL; + + if ((base != NULL) && (is_separator(base[0]))) { + // We have an absolute path + path = d_string_new(base); + } else { + // We have a directory and relative path + path = d_string_new(dir); + + // Ensure that folder ends in separator + add_trailing_sep(path); + + // Append filename (if present) + if (base) + d_string_append(path, base); + } + + result = path->str; + d_string_free(path, false); + + return result; +} + + +#ifdef TEST +void Test_path_from_dir_base(CuTest* tc) { + char dir[10] = "/foo"; + char base[10] = "bar"; + + char * path = path_from_dir_base(dir, base); + +#if defined(__WIN32) + CuAssertStrEquals(tc, "/foo\\bar", path); +#else + CuAssertStrEquals(tc, "/foo/bar", path); +#endif + + free(path); + strcpy(base, "/bar"); + + path = path_from_dir_base(dir, base); + + CuAssertStrEquals(tc, "/bar", path); + + free(path); + + path = path_from_dir_base(NULL, NULL); + CuAssertStrEquals(tc, NULL, path); +} +#endif + + +/// Separate filename and directory from a full path +/// +/// See http://stackoverflow.com/questions/1575278/function-to-split-a-filepath-into-path-and-file +void split_path_file(char ** dir, char ** file, char * path) { + char * slash = path, * next; + +#if defined(__WIN32) + const char sep[] = "\\/"; // Windows allows either variant +#else + const char sep[] = "/"; +#endif + + while ((next = strpbrk(slash + 1, sep))) + slash = next; + + if (path != slash) + slash++; + +// *dir = my_strndup(path, slash - path); + *dir = strndup(path, slash - path); + *file = strdup(slash); +} + +#ifdef TEST +void Test_split_path_file(CuTest* tc) { + char * dir, * file; + + char * path = "/foo/bar.txt"; + split_path_file(&dir, &file, path); + + CuAssertStrEquals(tc, "/foo/", dir); + CuAssertStrEquals(tc, "bar.txt", file); + + path = "\\foo\\bar.txt"; + split_path_file(&dir, &file, path); + +#if defined(__WIN32) + CuAssertStrEquals(tc, "\\foo\\", dir); + CuAssertStrEquals(tc, "bar.txt", file); +#else + CuAssertStrEquals(tc, "", dir); + CuAssertStrEquals(tc, "\\foo\\bar.txt", file); +#endif +} +#endif + + +DString * scan_file(const char * fname) { + /* Read from a file and return a DString * + `buffer` will need to be freed elsewhere */ + + char chunk[kBUFFERSIZE]; + size_t bytes; + + FILE * file; + +#if defined(__WIN32) + int wchars_num = MultiByteToWideChar(CP_UTF8, 0, fname, -1, NULL, 0); + wchar_t wstr[wchars_num]; + MultiByteToWideChar(CP_UTF8, 0, fname, -1, wstr, wchars_num); + + if ((file = _wfopen(wstr, L"r")) == NULL) { +#else + if ((file = fopen(fname, "r")) == NULL ) { +#endif + + return NULL; + } + + DString * buffer = d_string_new(""); + + while ((bytes = fread(chunk, 1, kBUFFERSIZE, file)) > 0) { + d_string_append_c_array(buffer, chunk, bytes); + } + + fclose(file); + + return buffer; +} + + +/// Recursively transclude source text, given a search directory. +/// Track files to prevent infinite recursive loops +void transclude_source(DString * source, char * dir, short format, stack * parsed, stack * manifest) { + DString * file_path; + DString * buffer; + + // Ensure folder is tidied up + char * folder = path_from_dir_base(dir, NULL); + + char * start, * stop; + char text[1100]; + + char * temp; + + size_t offset; + + // TODO: Does this source have metadata that overrides the search directory? + mmd_engine * e = mmd_engine_create_with_dstring(source, EXT_TRANSCLUDE); + if (mmd_has_metadata(e, &offset)) { + + temp = metavalue_for_key(e, "transclude base"); + + if (temp) { + free(folder); + + folder = path_from_dir_base(dir, temp); + } + } + + mmd_engine_free(e, false); + + if (folder == NULL) { + // We don't have anywhere to search, so nothing to do + goto exit; + } + + // Make sure we use a parse tree for children + stack * parse_stack = parsed; + + if (parsed == NULL) { + // Create temporary stack + parse_stack = stack_new(0); + } + + // Iterate through source text, looking for `{{foo}}` + + start = strstr(source->str, "{{"); + + while (start != NULL) { + stop = strstr(start, "}}"); + + if (stop == NULL) + break; + + // Where will we start next search? + offset = stop + 2 - source->str; + + // Ensure we have a reasonable match -- cap at 1000 characters + if (stop - start < 1000) { + // Grab text + strncpy(text, start + 2, stop - start - 2); + text[stop - start - 2] = '\0'; + + // Is this just {{TOC}} + if (strcmp("TOC",text) == 0) { + start = strstr(stop, "{{"); + continue; + } + + // Is this an absolute path or relative path? + if (is_separator(text[0])) { + // Absolute path + file_path = d_string_new(text); + } else { + // Relative path + file_path = d_string_new(folder); + + // Ensure that folder ends in separator + add_trailing_sep(file_path); + + d_string_append(file_path, text); + } + + // Adjust file wildcard extension for output format + // e.g. `foo.*` + if (format && strncmp(&text[stop - start - 4], ".*", 2) == 0) { + // Trim '.*' + d_string_erase(file_path, file_path->currentStringLength - 2, 2); + + switch (format) { + case FORMAT_HTML: + d_string_append(file_path, ".html"); + break; + case FORMAT_LATEX: + d_string_append(file_path, ".tex"); + break; + default: + d_string_append(file_path, ".txt"); + break; + + } + } + + // Prevent infinite recursive loops + for (int i = 0; i < parse_stack->size; ++i) + { + temp = stack_peek_index(parse_stack, i); + if (strcmp(file_path->str, temp) == 0) { + // We have parsed this file already, don't recurse infinitely + goto finish_file; + } + } + + // Add this file to stack + stack_push(parse_stack, file_path->str); + + // Add file to the manifest? + if (manifest) { + bool add = true; + + for (int i = 0; i < manifest->size; ++i) + { + temp = stack_peek_index(manifest, i); + if (strcmp(file_path->str, temp) == 0) { + // Already on manifest, don't duplicate + add = false; + } + } + + // Add path to manifest + if (add) + stack_push(manifest, strdup(file_path->str)); + } + + // Read the file + buffer = scan_file(file_path->str); + + // Substitue buffer for transclusion token + if (buffer) { + // Erase transclusion token from current source + d_string_erase(source, start - source->str, 2 + stop - start); + + // Recursively check this file for transclusions + transclude_source(buffer, folder, format, parse_stack, manifest); + + // Strip metadata from buffer now that we have parsed it + e = mmd_engine_create_with_dstring(buffer, EXT_TRANSCLUDE); + + if (mmd_has_metadata(e, &offset)) { + d_string_erase(buffer, 0, offset); + } else { + // Do we need to strip BOM? + if (strncmp(buffer->str, "\xef\xbb\xbf",3) == 0) + d_string_erase(buffer, 0, 3); + } + + mmd_engine_free(e, false); + + // Insert file text + d_string_insert(source, start - source->str, buffer->str); + + // Shift search point + offset = start - source->str + buffer->currentStringLength; + + d_string_free(buffer, true); + } + + // Remove this file from stack + stack_pop(parse_stack); + + finish_file: + d_string_free(file_path, true); + + } else { + // Match was too long to be reasonable file name + } + + start = strstr(source->str + offset, "{{"); + } + + exit: + + if (parsed == NULL) { + // Free temp stack + stack_free(parse_stack); + } + + free(folder); +} + + diff --git a/src/transclude.h b/src/transclude.h new file mode 100644 index 0000000..c53c6a4 --- /dev/null +++ b/src/transclude.h @@ -0,0 +1,74 @@ +/** + + MultiMarkdown 6 -- Lightweight markup processor to produce HTML, LaTeX, and more. + + @file transclude.h + + @brief + + + @author Fletcher T. Penney + @bug + +**/ + +/* + + Copyright © 2016 - 2017 Fletcher T. Penney. + + + The `MultiMarkdown 6` project is released under the MIT License.. + + GLibFacade.c and GLibFacade.h are from the MultiMarkdown v4 project: + + https://github.com/fletcher/MultiMarkdown-4/ + + MMD 4 is released under both the MIT License and GPL. + + + CuTest is released under the zlib/libpng license. See CuTest.c for the text + of the license. + + + ## The MIT License ## + + Permission is hereby granted, free of charge, to any person obtaining a copy + of this software and associated documentation files (the "Software"), to deal + in the Software without restriction, including without limitation the rights + to use, copy, modify, merge, publish, distribute, sublicense, and/or sell + copies of the Software, and to permit persons to whom the Software is + furnished to do so, subject to the following conditions: + + The above copyright notice and this permission notice shall be included in + all copies or substantial portions of the Software. + + THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR + IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, + FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE + AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER + LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, + OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN + THE SOFTWARE. + +*/ + + +#ifndef TRANSCLUDE_MULTIMARKDOWN_6_H +#define TRANSCLUDE_MULTIMARKDOWN_6_H + +#include "stack.h" + +#ifdef TEST +#include "CuTest.h" +#endif + + +/// Combine directory and base filename to create a full path */ +char * path_from_dir_base(char * dir, char * base); + +/// Recursively transclude source text, given a search directory. +/// Track files to prevent infinite recursive loops +void transclude_source(DString * source, char * dir, short format, stack * parsed, stack * manifest); + + +#endif diff --git a/src/writer.h b/src/writer.h index c640b1d..85b92d5 100644 --- a/src/writer.h +++ b/src/writer.h @@ -211,5 +211,7 @@ token * manual_label_from_header(token * h, const char * source); char * label_from_string(const char * str); +char * clean_string(const char * str, bool lowercase); + #endif diff --git a/tests/MMD6Tests/Transclusion.html b/tests/MMD6Tests/Transclusion.html new file mode 100644 index 0000000..acee534 --- /dev/null +++ b/tests/MMD6Tests/Transclusion.html @@ -0,0 +1,15 @@ +

This text is included in foo.txt.

+ +

This should not be transcluded to avoid an infinite loop – {{foo.txt}}

+ +

This text is included in bar.txt.

+ +

This can be transcluded without causing an infinite loop – {{foo.txt}}

+ +
This is a file with no metadata.
+
+ +
This is a file with no metadata.
+
+ +

This is HTML.

diff --git a/tests/MMD6Tests/Transclusion.htmlc b/tests/MMD6Tests/Transclusion.htmlc new file mode 100644 index 0000000..24ba4fa --- /dev/null +++ b/tests/MMD6Tests/Transclusion.htmlc @@ -0,0 +1,5 @@ +

{{foo.txt}}

+ +

{{bar.txt}}

+ +

{{transclusion/bat.*}}

diff --git a/tests/MMD6Tests/Transclusion.text b/tests/MMD6Tests/Transclusion.text new file mode 100644 index 0000000..3e80114 --- /dev/null +++ b/tests/MMD6Tests/Transclusion.text @@ -0,0 +1,5 @@ +{{foo.txt}} + +{{bar.txt}} + +{{transclusion/bat.*}} diff --git a/tests/MMD6Tests/bar.txt b/tests/MMD6Tests/bar.txt new file mode 100644 index 0000000..bbab1a3 --- /dev/null +++ b/tests/MMD6Tests/bar.txt @@ -0,0 +1,14 @@ +Title: bar +transclude base: transclusion + +This text is included in `bar.txt`. + +This can be transcluded without causing an infinite loop -- {{foo.txt}} + +``` +{{nometa.txt}} +``` + +``` +{{nometa-bom8.txt}} +``` diff --git a/tests/MMD6Tests/foo.txt b/tests/MMD6Tests/foo.txt new file mode 100644 index 0000000..a0d7ec7 --- /dev/null +++ b/tests/MMD6Tests/foo.txt @@ -0,0 +1,5 @@ +Title: foo + +This text is included in `foo.txt`. + +This should not be transcluded to avoid an infinite loop -- {{foo.txt}} diff --git a/tests/MMD6Tests/transclusion/bat.html b/tests/MMD6Tests/transclusion/bat.html new file mode 100644 index 0000000..b96373e --- /dev/null +++ b/tests/MMD6Tests/transclusion/bat.html @@ -0,0 +1 @@ +

This is HTML.

\ No newline at end of file diff --git a/tests/MMD6Tests/transclusion/nometa-bom8.txt b/tests/MMD6Tests/transclusion/nometa-bom8.txt new file mode 100644 index 0000000..bd23b05 --- /dev/null +++ b/tests/MMD6Tests/transclusion/nometa-bom8.txt @@ -0,0 +1 @@ +This is a file with no metadata. \ No newline at end of file diff --git a/tests/MMD6Tests/transclusion/nometa.txt b/tests/MMD6Tests/transclusion/nometa.txt new file mode 100644 index 0000000..6248fd4 --- /dev/null +++ b/tests/MMD6Tests/transclusion/nometa.txt @@ -0,0 +1 @@ +This is a file with no metadata. \ No newline at end of file