Port kiwix-serve to docopt.

pull/695/head
Matthieu Gautier 2 years ago
parent 9eab766ff9
commit 76b617817a

@ -20,8 +20,9 @@ libzim_dep = dependency('libzim', version:'>=9.0.0', static:static_linkage)
libzim_dep = dependency('libzim', version:'<10.0.0', static:static_linkage) libzim_dep = dependency('libzim', version:'<10.0.0', static:static_linkage)
kiwixlib_dep = dependency('kiwix', version:'>=13.0.0', static:static_linkage) kiwixlib_dep = dependency('kiwix', version:'>=13.0.0', static:static_linkage)
kiwixlib_dep = dependency('kiwix', version:'<14.0.0', static:static_linkage) kiwixlib_dep = dependency('kiwix', version:'<14.0.0', static:static_linkage)
docopt_dep = dependency('docopt', static:static_linkage)
all_deps = [thread_dep, kiwixlib_dep, libzim_dep] all_deps = [thread_dep, kiwixlib_dep, libzim_dep, docopt_dep]
if static_linkage if static_linkage
librt = compiler.find_library('rt', required:false) librt = compiler.find_library('rt', required:false)

@ -18,7 +18,7 @@
* MA 02110-1301, USA. * MA 02110-1301, USA.
*/ */
#include <getopt.h> #include <docopt/docopt.h>
#include <kiwix/manager.h> #include <kiwix/manager.h>
#include <kiwix/server.h> #include <kiwix/server.h>
#include <kiwix/name_mapper.h> #include <kiwix/name_mapper.h>
@ -41,50 +41,47 @@
#include "../version.h" #include "../version.h"
#define DEFAULT_THREADS 4 #define DEFAULT_THREADS 4
#define LITERAL_AS_STR(A) #A
void usage() #define AS_STR(A) LITERAL_AS_STR(A)
{
std::cout << "Usage:" << std::endl
<< "\tkiwix-serve [OPTIONS] ZIM_PATH+" << std::endl static const char USAGE[] =
<< "\tkiwix-serve --library [OPTIONS] LIBRARY_PATH" << std::endl R"(Deliver ZIM file(s) articles via HTTP
<< std::endl
Usage:
<< "Purpose:" << std::endl kiwix-serve [options] ZIMPATH ...
<< "\tDeliver ZIM file(s) articles via HTTP" kiwix-serve [options] (-l | --library) LIBRARYPATH
<< std::endl << std::endl kiwix-serve -h | --help
kiwix-serve -V | --version
<< "Mandatory arguments:" << std::endl
<< "\tLIBRARY_PATH\t\tXML library file path listing ZIM file to serve. To be used only with the --library argument." Mandatory arguments:
<< std::endl LIBRARYPATH XML library file path listing ZIM file to serve. To be used only with the --library argument."
<< "\tZIM_PATH\t\tZIM file path(s)" ZIMPATH ZIM file path(s)
<< std::endl << std::endl
Options:
<< "Optional arguments:" << std::endl << std::endl -h --help Print this help
<< "\t-h, --help\t\tPrint this help" << std::endl << std::endl -a=<pid> --attachToProcess=<pid> Exit if given process id is not running anymore [default: 0]
<< "\t-a, --attachToProcess\tExit if given process id is not running anymore" << std::endl -d --daemon Detach the HTTP server daemon from the main process
<< "\t-d, --daemon\t\tDetach the HTTP server daemon from the main process" << std::endl -i=<address> --address=<address> Listen only on the specified IP address. Specify 'ipv4', 'ipv6' or 'all' to listen on all IPv4, IPv6 or both types of addresses, respectively [default: all]
<< "\t-i, --address\t\tListen only on the specified IP address. Specify 'ipv4', 'ipv6' or 'all' to listen on all IPv4, IPv6 or both types of addresses, respectively (default: all)." << std::endl -M --monitorLibrary Monitor the XML library file and reload it automatically
<< "\t-M, --monitorLibrary\tMonitor the XML library file and reload it automatically" << std::endl -m --nolibrarybutton Don't print the builtin home button in the builtin top bar overlay
<< "\t-m, --nolibrarybutton\tDon't print the builtin home button in the builtin top bar overlay" << std::endl -n --nosearchbar Don't print the builtin bar overlay on the top of each served page
<< "\t-n, --nosearchbar\tDon't print the builtin bar overlay on the top of each served page" << std::endl -b --blockexternal Prevent users from directly accessing external links
<< "\t-b, --blockexternal\tPrevent users from directly accessing external links" << std::endl -p=<port> --port=<port> Port on which to listen to HTTP requests [default: 80]
<< "\t-p, --port\t\tTCP port on which to listen to HTTP requests (default: 80)" << std::endl -r=<root> --urlRootLocation=<root> URL prefix on which the content should be made available [default: /]
<< "\t-r, --urlRootLocation\tURL prefix on which the content should be made available (default: /)" << std::endl -s=<limit> --searchLimit=<limit> Maximun number of zim in a fulltext multizim search [default: 0]
<< "\t-s, --searchLimit\tMaximun number of zim in a fulltext multizim search (default: No limit)" << std::endl -t=<threads> --threads=<threads> Number of threads to run in parallel [default: )" AS_STR(DEFAULT_THREADS) R"(]
<< "\t-t, --threads\t\tNumber of threads to run in parallel (default: " << DEFAULT_THREADS << ")" << std::endl -v --verbose Print debug log to STDOUT
<< "\t-v, --verbose\t\tPrint debug log to STDOUT" << std::endl -V --version Print software version
<< "\t-V, --version\t\tPrint software version" << std::endl -z --nodatealiases Create URL aliases for each content by removing the date
<< "\t-z, --nodatealiases\tCreate URL aliases for each content by removing the date" << std::endl -c=<path> --customIndex=<path> Add path to custom index.html for welcome page
<< "\t-c, --customIndex\tAdd path to custom index.html for welcome page" << std::endl -L=<limit> --ipConnectionLimit=<limit> Max number of (concurrent) connections per IP [default: 0] (recommended: >= 6)
<< "\t-L, --ipConnectionLimit\tMax number of (concurrent) connections per IP (default: infinite, recommended: >= 6)" << std::endl -k --skipInvalid Startup even when ZIM files are invalid (those will be skipped)
<< "\t-k, --skipInvalid\tStartup even when ZIM files are invalid (those will be skipped)" << std::endl
<< std::endl Documentation:
Source code https://github.com/kiwix/kiwix-tools
<< "Documentation:" << std::endl More info https://wiki.kiwix.org/wiki/Kiwix-serve
<< "\tSource code\t\thttps://github.com/kiwix/kiwix-tools" << std::endl )";
<< "\tMore info\t\thttps://wiki.kiwix.org/wiki/Kiwix-serve" << std::endl
<< std::endl;
}
std::string loadCustomTemplate (std::string customIndexPath) { std::string loadCustomTemplate (std::string customIndexPath) {
customIndexPath = kiwix::isRelativePath(customIndexPath) ? customIndexPath = kiwix::isRelativePath(customIndexPath) ?
@ -191,6 +188,28 @@ bool reloadLibrary(kiwix::Manager& mgr, const std::vector<std::string>& paths)
} }
} }
// docopt::value::isLong() is counting repeated values.
// It doesn't check if the string can be parsed as long.
// (Contrarly to `asLong` which will try to convert string to long)
// See https://github.com/docopt/docopt.cpp/issues/62
// `isLong` is a small helper to get if the value can be parsed as long.
inline bool isLong(const docopt::value& v) {
try {
v.asLong();
return true;
} catch (...) {
return false;
}
}
#define FLAG(NAME, VAR) if (arg.first == NAME) { VAR = arg.second.asBool(); continue; }
#define STRING(NAME, VAR) if (arg.first == NAME && arg.second.isString() ) { VAR = arg.second.asString(); continue; }
#define STRING_LIST(NAME, VAR, ERRORSTR) if (arg.first == NAME) { if (arg.second.isStringList()) { VAR = arg.second.asStringList(); continue; } else { errorString = ERRORSTR; break; } }
#define INT(NAME, VAR, ERRORSTR) if (arg.first == NAME ) { if (isLong(arg.second)) { VAR = arg.second.asLong(); continue; } else { errorString = ERRORSTR; break; } }
// Older version of docopt doesn't declare Options. Let's declare it ourself.
using Options = std::map<std::string, docopt::value>;
int main(int argc, char** argv) int main(int argc, char** argv)
{ {
#ifndef _WIN32 #ifndef _WIN32
@ -207,139 +226,74 @@ int main(int argc, char** argv)
std::string customIndexPath=""; std::string customIndexPath="";
std::string indexTemplateString=""; std::string indexTemplateString="";
int serverPort = 80; int serverPort = 80;
int daemonFlag [[gnu::unused]] = false; bool daemonFlag [[gnu::unused]] = false;
int libraryFlag = false; bool helpFlag = false;
bool noLibraryButtonFlag = false; bool noLibraryButtonFlag = false;
bool noSearchBarFlag = false; bool noSearchBarFlag = false;
bool noDateAliasesFlag = false; bool noDateAliasesFlag = false;
bool blockExternalLinks = false; bool blockExternalLinks = false;
bool isVerboseFlag = false; bool isVerboseFlag = false;
bool monitorLibrary = false; bool monitorLibrary = false;
bool versionFlag = false;
unsigned int PPID = 0; unsigned int PPID = 0;
int ipConnectionLimit = 0; int ipConnectionLimit = 0;
int searchLimit = 0; int searchLimit = 0;
bool skipInvalid = false; bool skipInvalid = false;
static struct option long_options[] std::string errorString;
= {{"daemon", no_argument, 0, 'd'},
{"help", no_argument, 0, 'h'}, Options args;
{"verbose", no_argument, 0, 'v'}, try {
{"version", no_argument, 0, 'V'}, args = docopt::docopt_parse(USAGE, {argv+1, argv+argc}, false, false);
{"library", no_argument, 0, 'l'}, } catch (docopt::DocoptArgumentError const & error) {
{"nolibrarybutton", no_argument, 0, 'm'}, std::cerr << error.what() << std::endl;
{"nodatealiases", no_argument, 0, 'z'}, std::cerr << USAGE << std::endl;
{"nosearchbar", no_argument, 0, 'n'}, return -1;
{"blockexternallinks", no_argument, 0, 'b'}, }
{"attachToProcess", required_argument, 0, 'a'},
{"port", required_argument, 0, 'p'}, for (auto const& arg: args) {
{"address", required_argument, 0, 'i'}, FLAG("--help", helpFlag)
{"threads", required_argument, 0, 't'}, FLAG("--daemon", daemonFlag)
{"urlRootLocation", required_argument, 0, 'r'}, FLAG("--verbose", isVerboseFlag)
{"customIndex", required_argument, 0, 'c'}, FLAG("--nosearchbar", noSearchBarFlag)
{"monitorLibrary", no_argument, 0, 'M'}, FLAG("--blockexternal", blockExternalLinks)
{"ipConnectionLimit", required_argument, 0, 'L'}, FLAG("--nodatealiases", noDateAliasesFlag)
{"searchLimit", required_argument, 0, 's'}, FLAG("--nolibrarybutton",noLibraryButtonFlag)
{"skipInvalid", no_argument, 0, 'k'}, FLAG("--monitorLibrary", monitorLibrary)
{0, 0, 0, 0}}; FLAG("--skipInvalid", skipInvalid)
FLAG("--version", versionFlag)
std::set<int> usedOptions; STRING("LIBRARYPATH", libraryPath)
/* Argument parsing */ INT("--port", serverPort, "Port must be an integer")
while (true) { INT("--attachToProcess", PPID, "Process to attach must be an integer")
int option_index = 0; STRING("--address", address)
int c INT("--threads", nb_threads, "Number of threads must be an integer")
= getopt_long(argc, argv, "hzmnbdvVla:p:f:t:r:i:c:ML:s:", long_options, &option_index); STRING("--urlRootLocation", rootLocation)
STRING("--customIndex", customIndexPath)
if (c != -1) { INT("--ipConnectionLimit", ipConnectionLimit, "IP connection limit must be an integer")
auto insertRes = usedOptions.insert(c); INT("--searchLimit", searchLimit, "Search limit must be an integer")
if (!insertRes.second) { STRING_LIST("ZIMPATH", zimPathes, "ZIMPATH must be a string list")
std::cerr << "Multiple values of same option are not allowed." << std::endl; }
exit(1);
} if (!errorString.empty()) {
switch (c) { std::cerr << errorString << std::endl;
case 'h': std::cerr << USAGE << std::endl;
usage(); return -1;
return 0; }
case 'd':
daemonFlag = true; if (helpFlag) {
break; std::cout << USAGE << std::endl;
case 'v':
isVerboseFlag = true;
break;
case 'V':
version();
return 0; return 0;
case 'l':
libraryFlag = true;
break;
case 'n':
noSearchBarFlag = true;
break;
case 'b':
blockExternalLinks = true;
break;
case 'z':
noDateAliasesFlag = true;
break;
case 'm':
noLibraryButtonFlag = true;
break;
case 'p':
serverPort = atoi(optarg);
break;
case 'a':
PPID = atoi(optarg);
break;
case 'i':
address = std::string(optarg);
break;
case 't':
nb_threads = atoi(optarg);
break;
case 'r':
rootLocation = std::string(optarg);
break;
case 'c':
customIndexPath = std::string(optarg);
break;
case 'M':
monitorLibrary = true;
break;
case 'L':
ipConnectionLimit = atoi(optarg);
break;
case 's':
searchLimit = atoi(optarg);
break;
case 'k':
skipInvalid = true;
break;
case '?':
usage();
return 2;
}
} else {
if (optind < argc) {
if (libraryFlag) {
libraryPath = argv[optind++];
} else {
while (optind < argc)
zimPathes.push_back(std::string(argv[optind++]));
}
}
break;
}
} }
/* Print usage)) if necessary */ if (versionFlag) {
if (zimPathes.empty() && libraryPath.empty()) { version();
usage(); return 0;
exit(1);
} }
/* Setup the library manager and get the list of books */ /* Setup the library manager and get the list of books */
kiwix::Manager manager(library); kiwix::Manager manager(library);
std::vector<std::string> libraryPaths; std::vector<std::string> libraryPaths;
if (libraryFlag) { if (!libraryPath.empty()) {
libraryPaths = kiwix::split(libraryPath, ";"); libraryPaths = kiwix::split(libraryPath, ";");
if ( !reloadLibrary(manager, libraryPaths) ) { if ( !reloadLibrary(manager, libraryPaths) ) {
exit(1); exit(1);

Loading…
Cancel
Save