Skip to content

Commit 70a808c

Browse files
committed
Reader command line support
1 parent d878f84 commit 70a808c

File tree

2 files changed

+247
-1
lines changed

2 files changed

+247
-1
lines changed

LICENSE

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1,6 +1,6 @@
11
MIT License
22

3-
Copyright (c) 2019 das Developers
3+
Copyright (c) 2019-2022 Chris Piker
44

55
Permission is hereby granted, free of charge, to any person obtaining a copy
66
of this software and associated documentation files (the "Software"), to deal

das2/cmdline.d

Lines changed: 246 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,246 @@
1+
// Command line program assistance, mostly for das2 readers
2+
module das2.cmdline;
3+
4+
import core.stdc.stdlib: exit;
5+
6+
import std.algorithm: map;
7+
import std.array: appender, join;
8+
import std.conv: ConvException;
9+
import std.format: format;
10+
import std.getopt: getopt, config, GetoptResult, Option;
11+
import std.stdio: File, stderr, stdout;
12+
import std.string: startsWith, strip, wrap;
13+
import std.regex: regex, splitter;
14+
15+
public import std.experimental.logger: Logger, globalLogLevel, LogLevel,
16+
errorf, infof, tracef, warningf;
17+
18+
import dxml.util: encodeText;
19+
20+
// Code from terminal.d by Adam Druppe.
21+
version(Posix){
22+
struct winsize {
23+
ushort ws_row;
24+
ushort ws_col;
25+
ushort ws_xpixel;
26+
ushort ws_ypixel;
27+
}
28+
29+
version(linux){
30+
extern(C) int ioctl(int, int, ...);
31+
enum int TIOCGWINSZ = 0x5413;
32+
}
33+
else version(OSX) {
34+
extern(C) int ioctl(int, ulong, ...);
35+
enum TIOCGWINSZ = 1074295912;
36+
} else static assert(0, "confirm the value of tiocgwinsz");
37+
}
38+
39+
version(Windows){
40+
import core.sys.windows.windows;
41+
}
42+
43+
/** Get the current size of the terminal
44+
* Falls back to 80x24 columns if nothing can be determined
45+
* @return A two element integer array containing [columns, rows].
46+
*/
47+
int[] termSize()
48+
{
49+
version(Windows) {
50+
CONSOLE_SCREEN_BUFFER_INFO info;
51+
GetConsoleScreenBufferInfo( hConsole, &info );
52+
53+
int cols, rows;
54+
55+
cols = (info.srWindow.Right - info.srWindow.Left + 1);
56+
rows = (info.srWindow.Bottom - info.srWindow.Top + 1);
57+
58+
return [cols, rows];
59+
}
60+
else {
61+
winsize w;
62+
ioctl(0, TIOCGWINSZ, &w);
63+
return [w.ws_col, w.ws_row];
64+
}
65+
}
66+
67+
/** Format getopt options for printing in the style of man page output
68+
*
69+
* Params:
70+
* opts = A list of options returned from getopt
71+
* width = The total print width in columns, used for text wrapping
72+
* indent = The number of columns to leave blank before each line
73+
* subIndent = The number of columns to leave blank before the help
74+
* text of an item. This is in addition to the overall indention
75+
* Returns: a string containing formatted option help text
76+
*/
77+
string formatOptions(Output)(
78+
Output output, Option[] aOpt, size_t ccTotal, string sIndent, string sSubInd
79+
){
80+
// cc* - Indicates column count
81+
string sReq = " (Required)";
82+
string sPre;
83+
string sHelp;
84+
85+
size_t ccOptHdr;
86+
foreach(opt; aOpt){
87+
88+
// Assume that the short, long and required strings fit on a line.
89+
auto prefix = appender!(string)();
90+
prefix.put(sIndent);
91+
if(opt.optShort.length > 0){
92+
prefix.put(opt.optShort);
93+
if(opt.optLong.length > 0) prefix.put(",");
94+
}
95+
prefix.put(opt.optLong);
96+
if(opt.required) prefix.put(sReq);
97+
sPre = prefix.data;
98+
99+
// maybe start option help text on the same line, at least one word of
100+
// the help text must fit
101+
if(sPre.length < (sIndent.length + sSubInd.length - 1)){
102+
sPre = format("%*-s ", (sIndent.length + sSubInd.length - 1), sPre);
103+
sHelp = wrap(strip(opt.help), ccTotal, sPre, sIndent ~ sSubInd);
104+
}
105+
else{
106+
string sTmp = sIndent~sSubInd;
107+
sHelp = sPre~"\n"~ wrap(strip(opt.help), ccTotal, sTmp, sTmp);
108+
}
109+
output.put(sHelp);
110+
output.put("\n");
111+
}
112+
113+
return output.data;
114+
}
115+
116+
/+ Applies the wrap function to each substring indicated by a vertical tab '\v' +/
117+
S breakNrap(S)(
118+
S sText, size_t cols = 80, S firstindent = null, S indent = null, size_t tabsize = 2
119+
){
120+
121+
auto reg = regex(`\v`);
122+
123+
string s = sText.splitter(reg).
124+
map!(s => s.wrap(cols, firstindent, indent, tabsize)).
125+
join();
126+
127+
return s;
128+
}
129+
130+
/+ Get reader command line options.
131+
+ Error messages are sent to stardard error for logging and sent as
132+
+ a query error message to any remote clients. All non-machine readable content is
133+
+ always sent to stderr, even when using -h
134+
+/
135+
bool getRdrOpts(T...)(
136+
ref string[] aArgs, string name, string synopsis, string usage, string desc,
137+
string footer, T opts
138+
){
139+
140+
int[] aTermSz = termSize();
141+
int cols = aTermSz[0];
142+
143+
string sind = " "; // Single indent
144+
string dind = " "; // Double indent
145+
146+
GetoptResult rslt;
147+
148+
// For narrow terminals, back off the indent.
149+
if(cols < 60){ sind = " "; dind = " ";}
150+
if(cols < 40){ sind = " "; dind = " ";}
151+
152+
// TODO: Add paragraph split on vertical tab '\v'
153+
string header = "NAME\n" ~
154+
wrap(name ~ " - " ~ synopsis, cols, sind, dind) ~ "\n" ~
155+
"USAGE\n" ~
156+
wrap(usage, cols, sind, sind) ~ "\n" ~
157+
"DESCRIPTION\n" ~
158+
breakNrap(desc, cols, sind, sind) ~ "\n" ~
159+
"OPTIONS\n"; // Deal with commands without options later
160+
161+
if(footer.length > 0) footer = "NOTES\n" ~ wrap(footer, cols, sind, dind);
162+
163+
try{
164+
rslt = getopt(aArgs, config.passThrough, opts);
165+
}
166+
catch(ConvException ex){
167+
string sPkt = "<stream version=\"2.3/basic\" lang=\"en\" />\n";
168+
stdout.writef("|Hs||%d|%s", sPkt.length, sPkt);
169+
170+
string sExcept = "<exception type=\"QueryError\">\n"~
171+
encodeText(ex.msg) ~ "\n</exception>";
172+
stdout.writef("|He||%d|%s", sPkt.length, sPkt);
173+
174+
errorf("Error parsing command line, %s.\nUse -h for more help", ex.msg);
175+
return false;
176+
}
177+
178+
if(rslt.helpWanted){
179+
stdout.write(header);
180+
auto output = appender!(string)();
181+
formatOptions(output, rslt.options, cols, " ", " ");
182+
stderr.write(output.data);
183+
if(footer.length > 0) stderr.write(footer);
184+
exit(0);
185+
}
186+
187+
return true;
188+
}
189+
190+
/+ Default logger for das2 readers. Insures all output goes to
191+
+ stderr and NOT to stdout.
192+
+
193+
+ Output is cleaner then the standard logger.
194+
+ Usage:
195+
+ import std.experimental.logger;
196+
+ sharedLog = new StdErrLogger(log_level_string);
197+
+
198+
+/
199+
class StdErrLogger : Logger
200+
{
201+
protected:
202+
File file_;
203+
string[ubyte] dLvl;
204+
205+
public:
206+
/** Set the logging level given one of the strings:
207+
*
208+
* critical (c)
209+
* error (e)
210+
* warning (w)
211+
* info (i)
212+
* trace (t)
213+
*
214+
* Only the first letter of the string is significant
215+
*/
216+
this(File file, string sLogLvl){
217+
if(sLogLvl.startsWith('c')) globalLogLevel(LogLevel.critical);
218+
else if(sLogLvl.startsWith('e')) globalLogLevel(LogLevel.error);
219+
else if(sLogLvl.startsWith('w')) globalLogLevel(LogLevel.warning);
220+
else if(sLogLvl.startsWith('i')) globalLogLevel(LogLevel.info);
221+
else if(sLogLvl.startsWith('d')) globalLogLevel(LogLevel.trace);
222+
else if(sLogLvl.startsWith('t')) globalLogLevel(LogLevel.trace);
223+
else if(sLogLvl.startsWith('a')) globalLogLevel(LogLevel.all);
224+
else globalLogLevel(LogLevel.fatal);
225+
226+
dLvl = [
227+
LogLevel.all:"ALL", LogLevel.trace:"DEBUG", LogLevel.info:"INFO",
228+
LogLevel.warning:"WARNING", LogLevel.error:"ERROR",
229+
LogLevel.critical:"CRITICAL", LogLevel.fatal:"FATAL",
230+
LogLevel.off:"OFF"
231+
];
232+
233+
super(globalLogLevel());
234+
this.file_ = file;
235+
}
236+
237+
override void writeLogMsg(ref LogEntry entry){
238+
if( entry.logLevel < globalLogLevel()) return;
239+
240+
auto lt = file_.lockingTextWriter();
241+
lt.put(dLvl[entry.logLevel]);
242+
lt.put(": ");
243+
lt.put(entry.msg);
244+
lt.put("\n");
245+
}
246+
}

0 commit comments

Comments
 (0)