1 /** 2 * Parser for standalone ".dd" files. 3 * 4 * See_Also: dlang.org/ddoc.html ("Using Ddoc for other Documentation" section). 5 * Copyright: © 2014 Economic Modeling Specialists, Intl. 6 * Authors: Mathias Lang 7 * License: $(LINK2 http://www.boost.org/LICENSE_1_0.txt Boost License 1.0) 8 */ 9 module ddoc.standalone; 10 11 import std.stdio; 12 13 import ddoc.lexer; 14 import ddoc.sections; 15 import ddoc.macros; 16 17 string parseFile(string path, in string[string] context) { 18 import std.conv : to; 19 import std.datetime : Clock; 20 import std.file : readText; 21 import std.path : baseName, stripExtension; 22 23 auto text = readText(path); 24 // Predefined DDOC macros. 25 // The user might want to provide his macros. 26 // It makes sense for some (e.g. TITLE), not really for 27 // DATETIME / YEAR, but the macros file are supposed to 28 // override the predefinition, not the other way around. 29 string[string] macros = context.aaDup; 30 if (("TITLE" in macros) is null) 31 macros["TITLE"] = baseName(path).stripExtension; 32 if (("DATETIME" in macros) is null) 33 macros["DATETIME"] = Clock.currTime.toSimpleString; 34 if (("YEAR" in macros) is null) 35 macros["YEAR"] = to!string(Clock.currTime.year); 36 // parseDDString has to fill up COPYRIGHT 37 if (("DOCFILENAME" in macros) is null) // FIXME ?? 38 macros["DOCFILENAME"] = path.stripExtension~".html"; 39 if (("SRCFILENAME" in macros) is null) 40 macros["SRCFILENAME"] = baseName(path); 41 42 macros["BODY"] = parseDDString(text, macros); 43 return parseDdocBody(macros); 44 } 45 46 string parseDDString(string text, string[string] macros, bool removeUnknown = true) 47 { 48 import ddoc.highlight; 49 import std.string : strip; 50 import std.algorithm : startsWith; 51 import std.array : appender; 52 53 assert(text.startsWith("Ddoc"), "the string should start with 'Ddoc'"); 54 text = text[4 .. $]; 55 56 // The doc is between "Ddoc" (which must be at the beginning of the file) 57 // and the "Macros" sections. So first we need to find the later. 58 // Get macros and expand them. 59 parseMacrosSection(text, macros); 60 61 // Get the copyright section 62 //auto copyright = getSection("Copyright", text, macros).content; 63 //if (copyright !is null) 64 // macros["COPYRIGHT"] = copyright; 65 text = highlight(text); 66 auto lexer = Lexer(text, true); 67 return expand(lexer, macros, removeUnknown); 68 } 69 70 /// 71 unittest { 72 import std.stdio, std.string; 73 74 auto text = `Ddoc 75 This file is a $(unknown standalone) Ddoc file. It can contain any kind of 76 $(MAC macros), defined in the $(MAC 'Macros:' section). 77 78 Macros: 79 MAC=$0 80 _= 81 `; 82 83 auto expected1 = `This file is a Ddoc file. It can contain any kind of 84 macros, defined in the 'Macros:' section.`; 85 auto expected2 = `This file is a $(unknown standalone) Ddoc file. It can contain any kind of 86 macros, defined in the 'Macros:' section.`; 87 88 auto lex = Lexer(text, true); 89 // Whitespace and newline before / after not taken into account. 90 auto res = parseDDString(text, null).strip; 91 assert(res == expected1, res); 92 res = parseDDString(text, null, false).strip; 93 assert(res == expected2, res); 94 } 95 96 // Warning: Does not support embedded code / inlining. 97 private string parseDdocBody(string[string] macros) { 98 auto lexer = Lexer("$(DDOC)", true); 99 return expandMacro(lexer, macros); 100 } 101 102 private void parseMacrosSection(ref string text, ref string[string] macros) { 103 import std.string : indexOf; 104 enum macSection = "\nMacros:\n"; 105 auto idx = text.indexOf(macSection); 106 if (idx >= 0) { 107 auto macroSection = text[idx + macSection.length .. $]; 108 KeyValuePair[] kvp; 109 auto lex = Lexer(macroSection, true); 110 assert(parseKeyValuePair(lex, kvp)); 111 foreach (kv; kvp) macros[kv[0]] = kv[1]; 112 text = text[0..idx]; 113 } 114 } 115 116 // BUG #14148 117 private auto aaDup(in string[string] aa) { 118 string[string] ret; 119 foreach (k, v; aa) 120 ret[k] = v; 121 return ret; 122 } 123 124 version (LIBDDOC_CONFIG_EXE): 125 int main(string[] args) { 126 import std.algorithm; 127 import std.getopt; 128 import std.path; 129 static import file = std.file; 130 131 if (args.length == 1) { 132 stderr.writeln(`Usage: `, args[0], ` [options] [macros.ddoc]* file.dd`); 133 stderr.writeln(); 134 stderr.writeln(`Process standalone documentation files and write them to a file.`); 135 stderr.writeln(`'.ddoc' files are macros definition file. Order matters.`); 136 stderr.writeln(); 137 stderr.writeln(`Options:`); 138 stderr.writeln(`-o|--output-file=path\tOutput a (single) parsed file to 'path';`); 139 stderr.writeln(`-D|--output-dir=dir\tOutput all parsed file(s) to directory 'dir';`); 140 } 141 142 args = args[1..$]; 143 string outDir, outFile; 144 getopt(args, 145 "output-dir|D", &outDir, 146 "output-file|o", &outFile 147 ); 148 auto ddocFiles = args.filter!((f) => f.extension == ".ddoc"); 149 150 if (!args.any!((f) => f.extension == ".dd")) { 151 stderr.writeln("No .dd file provided"); 152 return 1; 153 } 154 155 bool oneFile; 156 auto ctx = parseMacrosFile(ddocFiles); 157 foreach (f; args) { 158 if (f.extension == ".ddoc") 159 continue; 160 assert(f.extension == ".dd", "Don't know what to do with "~f); 161 if (oneFile) { 162 stderr.writeln("Only one .dd file allowed with o|output-file."); 163 return 1; 164 } 165 oneFile = (outFile !is null); 166 writeln("Processing file : ", f); 167 auto data = parseFile(f, ctx); 168 if (outFile !is null) { 169 writeln("Writing ", data.length, " bytes to ", outFile); 170 file.write(outFile, data); 171 } else if (outDir !is null) { 172 auto of = buildPath(outDir, baseName(f.setExtension(".html"))); 173 writeln("Writing ", data.length, " bytes to ", of); 174 file.write(of, data); 175 } else { 176 auto of = baseName(f.setExtension(".html")); 177 writeln("Writing ", data.length, " bytes to ", of); 178 file.write(of, data); 179 } 180 } 181 return 0; 182 }