Newer
Older
dub_jkp / scripts / man / gen_man.d
@WebFreak001 WebFreak001 on 6 Aug 2022 6 KB improve man page generation
  1. #!/usr/bin/env dub
  2. /+dub.sdl:
  3. dependency "dub" path="../.."
  4. +/
  5.  
  6. import std.algorithm, std.conv, std.format, std.path, std.range, std.stdio;
  7. import dub.commandline;
  8.  
  9. string italic(string w)
  10. {
  11. return `\fI` ~ w ~ `\fR`;
  12. }
  13.  
  14. string bold(string w)
  15. {
  16. return `\fB` ~ w ~ `\fR`;
  17. }
  18.  
  19. string header(string heading)
  20. {
  21. return ".SH " ~ heading;
  22. }
  23.  
  24. string br(string s)
  25. {
  26. return ".BR " ~ s;
  27. }
  28.  
  29. struct Config
  30. {
  31. import std.datetime;
  32. SysTime date;
  33. string[] relatedSubCommands;
  34.  
  35. static Config init(){
  36. import std.process : environment;
  37. Config config;
  38. config.date = Clock.currTime;
  39. auto diffable = environment.get("DIFFABLE", "0");
  40. if (diffable == "1")
  41. config.date = SysTime(DateTime(2018, 01, 01));
  42.  
  43. config.cwd = __FILE_FULL_PATH__.dirName;
  44. return config;
  45. }
  46. string cwd;
  47. }
  48.  
  49. void writeHeader(ref File manFile, string manName, const Config config)
  50. {
  51. static immutable manHeader =
  52. `.TH %s 1 "%s" "The D Language Foundation" "The D Language Foundation"
  53. .SH NAME`;
  54. manFile.writefln(manHeader, manName, config.date.toISOExtString.take(10));
  55. }
  56.  
  57. void writeFooter(ref File manFile, string seeAlso, const Config config)
  58. {
  59. static immutable manFooter =
  60. `.SH FILES
  61. \fIdub\&.sdl\fR, \fIdub\&.json\fR
  62. .SH AUTHOR
  63. Copyright (c) 1999-%s by The D Language Foundation
  64. .SH "ONLINE DOCUMENTATION"
  65. .UR http://code.dlang.org/docs/commandline
  66. http://code.dlang.org/docs/commandline
  67. .UE
  68. .SH "SEE ALSO"
  69. %s`;
  70. manFile.writefln(manFooter, config.date.year, seeAlso);
  71. }
  72.  
  73. void writeMainManFile(CommandArgs args, CommandGroup[] commands,
  74. string fileName, const Config config)
  75. {
  76. auto manFile = File(config.cwd.buildPath(fileName), "w");
  77. manFile.writeHeader("DUB", config);
  78. auto seeAlso = ["dmd(1)", "rdmd(1)"]
  79. .chain(commands.map!(a => a.commands).joiner
  80. .map!(cmd => format("dub-%s(1)", cmd.name)))
  81. .joiner(", ").to!string.bold;
  82. scope(exit) manFile.writeFooter(seeAlso, config);
  83.  
  84. alias writeln = (m) => manFile.writeln(m);
  85. writeln(`dub \- Package and build management system for D`);
  86. writeln("SYNOPSIS".header);
  87. writeln(`.B dub
  88. [\-\-version]
  89. [\fICOMMAND\fR]
  90. [\fIOPTIONS\&.\&.\&.\fR]
  91. [\-\- [\fIAPPLICATION ARGUMENTS\&.\&.\&.\fR]]`);
  92.  
  93. writeln("DESCRIPTION".header);
  94. writeln(`Manages the DUB project in the current directory\&. DUB can serve as a build
  95. system and a package manager, automatically keeping track of project's
  96. dependencies \- both downloading them and linking them into the application.`);
  97.  
  98. writeln(".SH COMMANDS");
  99. foreach (grp; commands) {
  100. foreach (cmd; grp.commands) {
  101. writeln(".TP");
  102. writeln(cmd.name.bold);
  103. writeln(cmd.helpText.joiner("\n"));
  104. }
  105. }
  106.  
  107. writeln("COMMON OPTIONS".header);
  108. args.writeArgs(manFile);
  109. }
  110.  
  111. string highlightArguments(string args)
  112. {
  113. import std.regex : regex, replaceAll;
  114. static auto re = regex("<([^>]*)>");
  115. static const reReplacement = "<%s>".format(`$1`.italic);
  116. return args.replaceAll(re, reReplacement);
  117. }
  118.  
  119. void writeArgs(CommandArgs args, ref File manFile)
  120. {
  121. alias write = (m) => manFile.write(m.replace(`-`, `\-`));
  122. foreach (arg; args.recognizedArgs)
  123. {
  124. auto names = arg.names.split("|");
  125. assert(names.length == 1 || names.length == 2);
  126. string sarg = names[0].length == 1 ? names[0] : null;
  127. string larg = names[0].length > 1 ? names[0] : names.length > 1 ? names[1] : null;
  128. manFile.writeln(".PP");
  129. if (sarg !is null) {
  130. write("-%s".format(sarg).bold);
  131. if (larg !is null)
  132. write(", ");
  133. }
  134. if (larg !is null) {
  135. write("--%s".format(larg).bold);
  136. if (!arg.defaultValue.peek!bool)
  137. write("=VALUE");
  138. }
  139. manFile.writeln;
  140. manFile.writeln(".RS 4");
  141. manFile.writeln(arg.helpText.join("\n"));
  142. manFile.writeln(".RE");
  143. }
  144. }
  145.  
  146. void writeManFile(Command command, const Config config)
  147. {
  148. import std.uni : toUpper;
  149.  
  150. auto args = new CommandArgs(null);
  151. command.prepare(args);
  152. string fileName = format("dub-%s.1", command.name);
  153. auto manFile = File(config.cwd.buildPath(fileName), "w");
  154. auto manName = format("DUB-%s", command.name).toUpper;
  155. manFile.writeHeader(manName, config);
  156.  
  157. string[] extraRelated;
  158. foreach (arg; args.recognizedArgs) {
  159. if (arg.names.canFind("rdmd"))
  160. extraRelated ~= "rdmd(1)";
  161. }
  162. if (command.name == "dustmite")
  163. extraRelated ~= "dustmite(1)";
  164.  
  165. const seeAlso = ["dub(1)"]
  166. .chain(config.relatedSubCommands.map!(s => s.format!"dub-%s(1)"))
  167. .chain(extraRelated)
  168. .map!bold
  169. .joiner(", ")
  170. .to!string;
  171. scope(exit) manFile.writeFooter(seeAlso, config);
  172.  
  173. alias writeln = (m) => manFile.writeln(m);
  174. manFile.writefln(`dub-%s \- %s`, command.name, command.description);
  175.  
  176. writeln("SYNOPSIS".header);
  177. writeln("dub %s".format(command.name).bold);
  178. writeln(command.argumentsPattern.highlightArguments);
  179. writeln(`OPTIONS\&.\&.\&.`.italic);
  180. if (command.acceptsAppArgs)
  181. {
  182. writeln("[-- <%s>]".format("application arguments...".italic));
  183. }
  184.  
  185. writeln("DESCRIPTION".header);
  186. writeln(command.helpText.joiner("\n\n"));
  187. writeln("OPTIONS".header);
  188. args.writeArgs(manFile);
  189.  
  190. static immutable exitStatus =
  191. `.SH EXIT STATUS
  192. .TP
  193. .BR 0
  194. DUB succeeded
  195. .TP
  196. .BR 1
  197. usage errors, unknown command line flags
  198. .TP
  199. .BR 2
  200. package not found, package failed to load, miscellaneous error`;
  201. static immutable exitStatusDustmite =
  202. `.SH EXIT STATUS
  203. Forwards the exit code from ` ~ `dustmite(1)`.bold;
  204. if (command.name == "dustmite")
  205. manFile.writeln(exitStatusDustmite);
  206. else
  207. manFile.writeln(exitStatus);
  208. }
  209.  
  210. void main()
  211. {
  212. Config config = Config.init;
  213. auto commands = getCommands();
  214.  
  215. // main dub.1
  216. {
  217. CommonOptions options;
  218. auto args = new CommandArgs(null);
  219. options.prepare(args);
  220. args.writeMainManFile(commands, "dub.1", config);
  221. }
  222.  
  223. string[][] relatedSubCommands = [
  224. ["run", "build", "test"],
  225. ["test", "dustmite", "lint"],
  226. ["describe", "gemerate"],
  227. ["add", "fetch"],
  228. ["init", "add", "convert"],
  229. ["add-path", "remove-path"],
  230. ["add-local", "remove-local"],
  231. ["list", "search"],
  232. ["add-override", "remove-override", "list-overrides"],
  233. ["clean-caches", "clean", "remove"],
  234. ];
  235.  
  236. // options for each specific command
  237. foreach (cmd; commands.map!(a => a.commands).joiner) {
  238. string[] related;
  239. foreach (relatedList; relatedSubCommands) {
  240. if (relatedList.canFind(cmd.name))
  241. related ~= relatedList;
  242. }
  243. related = related.sort!"a<b".uniq.array;
  244. related = related.remove!(c => c == cmd.name);
  245. config.relatedSubCommands = related;
  246.  
  247. cmd.writeManFile(config);
  248. }
  249. }