1 module tools.common.getopt; 2 3 4 public import std.getopt: getopt, config; 5 import std.getopt; 6 import std.stdio; 7 import std.string; 8 import std.algorithm; 9 10 11 void improvedGetoptPrinter(string text, Option[] opt, string footer = null, int width=80){ 12 13 version(Posix){ 14 import core.sys.posix.sys.ioctl; 15 static if(__traits(compiles, winsize, winsize.ws_row, TIOCGWINSZ)){ 16 //pragma(msg, "Terminal width detection"); 17 winsize w; 18 ioctl(stdout.fileno, TIOCGWINSZ, &w); 19 width = w.ws_col; 20 } 21 } 22 23 size_t widthOptLong; 24 bool hasRequiredOpt = false; 25 size_t widthHelpIndentation; 26 foreach(ref o ; opt){ 27 if(o.optLong.length > widthOptLong) 28 widthOptLong = o.optLong.length; 29 if(o.required) 30 hasRequiredOpt = true; 31 } 32 widthHelpIndentation = widthOptLong + (hasRequiredOpt? 8 : 6); 33 auto helpIndent = "".leftJustify(widthHelpIndentation); 34 35 36 // Print text 37 text 38 .splitLines 39 .map!(a => a.smartWrap(width, null, " ").splitLines) 40 .join 41 .each!((a){ 42 writeln(a); 43 }); 44 writeln(); 45 46 if(hasRequiredOpt) 47 writeln("Options with * are required"); 48 49 // Print options 50 foreach(ref o ; opt){ 51 writef(" %s%s %*s ", 52 hasRequiredOpt ? (o.required? "* " : " ") : "", 53 o.optShort !is null? o.optShort : " ", 54 widthOptLong, o.optLong ); 55 56 bool first = true; 57 o.help 58 .splitLines 59 .map!(a => a.smartWrap(width - widthHelpIndentation).splitLines) 60 .join 61 .each!((a){ 62 writeln(first ? "" : helpIndent, a); 63 first = false; 64 }); 65 } 66 67 // Print footer 68 if(footer !is null){ 69 writeln(); 70 71 footer 72 .splitLines 73 .map!((a) { 74 auto l = a.smartWrap(width, null, " ").splitLines; 75 if(l.length == 0) 76 l = [""]; 77 return l; 78 }) 79 .join 80 .each!((a){ 81 writeln(a); 82 }); 83 } 84 } 85 86 87 private string smartWrap(in string text, size_t width = 80, in string firstindent = null, in string secondindent = null, in size_t tabsize = 8){ 88 import std.uni : isWhite; 89 90 return text.splitLines 91 .map!((ref l){ 92 string indent; 93 auto indentLen = l.countUntil!(a => !a.isWhite); 94 if(indentLen > 0) 95 indent = l[0 .. indentLen]; 96 97 return l.wrap(width - indent.length, firstindent, secondindent, tabsize) 98 .splitLines 99 .map!(a => indent ~ a) 100 .join("\n"); 101 }) 102 .join("\n"); 103 } 104 unittest{ 105 assert(" hello".smartWrap() == " hello"); 106 assert(" hello world".smartWrap(8) == " hello\n world"); 107 assert("\n".smartWrap(8) == ""); 108 assert("\n\n".smartWrap(8) == "\n"); 109 assert(" a\n\n b".smartWrap(8) == " a\n\n b"); 110 } 111 112 113 114 template multilineStr(string s){ 115 enum multilineStr = (){ 116 auto lines = s.splitLines(); 117 assert(lines[0].strip == "", "First line must be empty"); 118 assert(lines.length > 1, "Not enough lines"); 119 120 const tabLen = lines[1].length - lines[1].stripLeft.length; 121 const tab = lines[1][0 .. tabLen]; 122 123 return lines[1 .. $] 124 .map!((l){ 125 if(l.strip.length == 0) 126 return ""; 127 assert(l[0 .. tabLen] == tab, "Tab mismatch on line '" ~ l ~ "'"); 128 return l[tabLen .. $]; 129 }) 130 .join("\n"); 131 }(); 132 }