Skip to content

Commit

Permalink
Implement automatic help screen functionality, adjust code style and …
Browse files Browse the repository at this point in the history
…move

privilege lowering code to vibe.core.core.

Privilege lowering was broken as it happened before any calls to listenTCP() or
similar functions. It is now executed explicitly using lowerPrivileges(). Also
processCommandLineArgs() is now fixed to at least perform in a reasonably
similar way to how it used to do so that compatibility is not silently broken.

See also #211.
  • Loading branch information
s-ludwig committed Apr 10, 2013
1 parent 6dd2a48 commit add93a1
Show file tree
Hide file tree
Showing 7 changed files with 269 additions and 194 deletions.
13 changes: 10 additions & 3 deletions source/vibe/appmain.d
Original file line number Diff line number Diff line change
Expand Up @@ -19,8 +19,8 @@
*/
module vibe.appmain;

import vibe.core.args : finalizeCommandLineArgs;
import vibe.core.core : runEventLoop;
import vibe.core.args : finalizeCommandLineOptions;
import vibe.core.core : runEventLoop, lowerPrivileges;
import vibe.core.log;

// only include main if VibeCustomMain is not set
Expand All @@ -38,7 +38,14 @@ int main()
logInfo("All unit tests were successful.");
return 0;
} else {
finalizeCommandLineArgs();
try if (!finalizeCommandLineOptions()) return 0;
catch (Exception e) {
logDiagnostic("Error processing command line: %s", e.msg);
return 1;
}

lowerPrivileges();

logInfo("Running event loop...");
debug {
return runEventLoop();
Expand Down
200 changes: 133 additions & 67 deletions source/vibe/core/args.d
Original file line number Diff line number Diff line change
Expand Up @@ -16,6 +16,8 @@ module vibe.core.args;
import vibe.core.log;
import vibe.data.json;

import std.algorithm : any, array, map, sort;
import std.array : replicate;
import std.exception;
import std.file;
import std.getopt;
Expand All @@ -24,69 +26,143 @@ import std.string;

import core.runtime;

/// Deprecated. Currently does nothing - Vibe will parse arguments
/// automatically on startup. Call $(D finalizeCommandLineArgs) from your
/// $(D main()) if you use a custom one, to check for unrecognized options.

/**
Deprecated. Removes any recognized arguments from args leaving any unrecognized options.
Note that vibe.d parses all options on start up and calling this function is not necessary.
It is recommended to use
Currently does nothing - Vibe will parse arguments
automatically on startup. Call $(D finalizeCommandLineArgs) from your
$(D main()) if you use a custom one, to check for unrecognized options.
*/
deprecated void processCommandLineArgs(ref string[] args)
{
finalizeCommandLineArgs();
args = g_args.dup;
}


/**
Finds and reads an option from the configuration file or command line.
Command line options take precedence.
Command line options take precedence over configuration file entries.
Params:
names = Option names. Separate multiple name variants with $(D |),
as with $(D std.getopt).
names = Option names. Separate multiple name variants with "|",
as for $(D std.getopt).
pvalue = Pointer to store the value. Unchanged if value was not found.
Returns:
$(D true) if the value was found, $(D false) otherwise.
*/
bool getOption(T)(string names, T* pvalue)
bool getOption(T)(string names, T* pvalue, string help_text)
{
if (!args) // May happen due to http://d.puremagic.com/issues/show_bug.cgi?id=9881
init();

auto oldLength = args.length;
getopt(args, getoptConfig, names, pvalue);
if (oldLength != args.length) // getopt found it
{
static void removeArg(string names)
{
T v;
getopt(args, getoptConfig, names, &v);
}
argRemovers[names] = &removeArg;
return true;
}
// May happen due to http://d.puremagic.com/issues/show_bug.cgi?id=9881
if (!g_args) init();

OptionInfo info;
info.names = names.split("|").sort!((a, b) => a.length < b.length)().array();
info.hasValue = !is(T == bool);
info.helpText = help_text;
assert(!g_options.any!(o => o.names == info.names)(), "getOption() may only be called once per option name.");
g_options ~= info;

if (haveConfig)
foreach (name; names.split("|"))
if (auto pv = name in config)
{
getopt(g_args, getoptConfig, names, pvalue);

if (g_haveConfig) {
foreach (name; info.names)
if (auto pv = name in g_config) {
*pvalue = pv.get!T;
return true;
}
}

return false;
}

/// Checks for unrecognized options.
/// Called automatically from $(D vibe.appmain).
void finalizeCommandLineArgs()

/**
Prints a help screen consisting of all options encountered in getOption calls.
*/
void printCommandLineHelp()
{
enum dcolumn = 20;
enum ncolumns = 80;

logInfo("Usage: %s <options>\n", g_args[0]);
foreach (opt; g_options) {
string shortopt;
string[] longopts;
if (opt.names[0].length == 1 && !opt.hasValue) {
shortopt = "-"~opt.names[0];
longopts = opt.names[1 .. $];
} else {
shortopt = " ";
longopts = opt.names;
}

string optionString(string name)
{
if (name.length == 1) return "-"~name~(opt.hasValue ? " <value>" : "");
else return "--"~name~(opt.hasValue ? "=<value>" : "");
}

auto optstr = format(" %s %s", shortopt, longopts.map!optionString().join(", "));
if (optstr.length < dcolumn) optstr ~= replicate(" ", dcolumn - optstr.length);

auto indent = replicate(" ", dcolumn+1);
auto desc = wrap(opt.helpText, ncolumns - dcolumn - 2, optstr.length > dcolumn ? indent : "", indent).stripRight();

if (optstr.length > dcolumn)
logInfo("%s\n%s", optstr, desc);
else logInfo("%s %s", optstr, desc);
}
}


/**
Checks for unrecognized command line options and display a help screen.
This function is called automatically from vibe.appmain to check for
correct command line usage. It will print a help screen in case of
unrecognized options.
Returns:
If "--help" was passed, the function returns false. In all other
cases either true is returned or an exception is thrown.
*/
bool finalizeCommandLineOptions()
{
foreach (names, fn; argRemovers)
fn(names);
enforce(args.length<=1, "Unrecognized command-line parameter: " ~ args[1]);
if (g_args.length > 1) {
logError("Unrecognized command line option: %s\n", g_args[1]);
printCommandLineHelp();
throw new Exception("Unrecognized command line option.");
}

if (g_help) {
printCommandLineHelp();
return false;
}

return true;
}

private:

enum configName = "vibe.conf";
private struct OptionInfo {
string[] names;
bool hasValue;
string helpText;
}

private {
__gshared string[] g_args;
__gshared bool g_haveConfig;
__gshared Json g_config;
__gshared OptionInfo[] g_options;
__gshared bool g_help;
}

string[] getConfigPaths()
private string[] getConfigPaths()
{
string[] result = [""];
import std.process : environment;
Expand All @@ -97,42 +173,32 @@ string[] getConfigPaths()
return result;
}

shared static this()
// this is invoked by the first getOption call (at least vibe.core will porform one)
private void init()
{
if (!args)
init();
}

void init()
{
args = Runtime.args;

auto searchPaths = getConfigPaths();
foreach (searchPath; searchPaths)
{
auto configPath = buildPath(searchPath, configName);
if (configPath.exists)
{
scope(failure) logError("Failed to parse config file %s:", configPath);
auto configText = configPath.readText();
config = configText.parseJson();
haveConfig = true;
g_args = Runtime.args.dup;

// TODO: let different config files override induvidual fields
auto searchpaths = getConfigPaths();
foreach (spath; searchpaths) {
auto cpath = buildPath(spath, configName);
if (cpath.exists) {
scope(failure) logError("Failed to parse config file %s.", cpath);
auto text = cpath.readText();
g_config = text.parseJson();
g_haveConfig = true;
break;
}
}

if (!haveConfig)
logDebug("No config file found in %s", searchPaths);
if (!g_haveConfig)
logDiagnostic("No config file found in %s", searchpaths);

getOption("h|help", &g_help, "Prints this help screen.");
}

template ValueTuple(T...) { alias T ValueTuple; }
alias getoptConfig = ValueTuple!(
std.getopt.config.passThrough,
std.getopt.config.bundling,
);
private enum configName = "vibe.conf";

__gshared:
private template ValueTuple(T...) { alias T ValueTuple; }

string[] args;
bool haveConfig;
Json config;
void function(string)[string] argRemovers;
private alias getoptConfig = ValueTuple!(std.getopt.config.passThrough, std.getopt.config.bundling);
Loading

0 comments on commit add93a1

Please sign in to comment.