feat: allow optional arguments in tr_getopt() (#7510)

* feat: allow optional arguments in `tr_getopt()`

* test: new tests for optional arg

* code review: `using Arg = tr_option::Arg`

* refactor: static assert option array size

* test: add new tests for missing arguments in the middle

* test: static auto constexpr
This commit is contained in:
Yat Ho
2025-10-16 01:08:11 +08:00
committed by GitHub
parent a7a5bc38ad
commit 9c14fa58d8
10 changed files with 536 additions and 402 deletions

View File

@@ -48,36 +48,41 @@ sig_atomic_t manualUpdate = false;
char const* torrentPath = nullptr;
auto constexpr Options = std::array<tr_option, 20>{
{ { 'b', "blocklist", "Enable peer blocklists", "b", false, nullptr },
{ 'B', "no-blocklist", "Disable peer blocklists", "B", false, nullptr },
{ 'd', "downlimit", "Set max download speed in " SPEED_K_STR, "d", true, "<speed>" },
{ 'D', "no-downlimit", "Don't limit the download speed", "D", false, nullptr },
{ 910, "encryption-required", "Encrypt all peer connections", "er", false, nullptr },
{ 911, "encryption-preferred", "Prefer encrypted peer connections", "ep", false, nullptr },
{ 912, "encryption-tolerated", "Prefer unencrypted peer connections", "et", false, nullptr },
{ 'f', "finish", "Run a script when the torrent finishes", "f", true, "<script>" },
{ 'g', "config-dir", "Where to find configuration files", "g", true, "<path>" },
{ 'm', "portmap", "Enable portmapping via NAT-PMP or UPnP", "m", false, nullptr },
{ 'M', "no-portmap", "Disable portmapping", "M", false, nullptr },
{ 'p', "port", "Port for incoming peers (Default: " TR_DEFAULT_PEER_PORT_STR ")", "p", true, "<port>" },
{ 't',
"tos",
"Peer socket DSCP / ToS setting (number, or a DSCP string, e.g. 'af11' or 'cs0', default=" TR_DEFAULT_PEER_SOCKET_TOS_STR
")",
"t",
true,
"<dscp-or-tos>" },
{ 'u', "uplimit", "Set max upload speed in " SPEED_K_STR, "u", true, "<speed>" },
{ 'U', "no-uplimit", "Don't limit the upload speed", "U", false, nullptr },
{ 'v', "verify", "Verify the specified torrent", "v", false, nullptr },
{ 'V', "version", "Show version number and exit", "V", false, nullptr },
{ 'w', "download-dir", "Where to save downloaded data", "w", true, "<path>" },
{ 500, "sequential-download", "Download pieces sequentially", "seq", false, nullptr },
using Arg = tr_option::Arg;
auto constexpr Options = std::array<tr_option, 20>{ {
{ 'b', "blocklist", "Enable peer blocklists", "b", Arg::None, nullptr },
{ 'B', "no-blocklist", "Disable peer blocklists", "B", Arg::None, nullptr },
{ 'd', "downlimit", "Set max download speed in " SPEED_K_STR, "d", Arg::Required, "<speed>" },
{ 'D', "no-downlimit", "Don't limit the download speed", "D", Arg::None, nullptr },
{ 910, "encryption-required", "Encrypt all peer connections", "er", Arg::None, nullptr },
{ 911, "encryption-preferred", "Prefer encrypted peer connections", "ep", Arg::None, nullptr },
{ 912, "encryption-tolerated", "Prefer unencrypted peer connections", "et", Arg::None, nullptr },
{ 'f', "finish", "Run a script when the torrent finishes", "f", Arg::Required, "<script>" },
{ 'g', "config-dir", "Where to find configuration files", "g", Arg::Required, "<path>" },
{ 'm', "portmap", "Enable portmapping via NAT-PMP or UPnP", "m", Arg::None, nullptr },
{ 'M', "no-portmap", "Disable portmapping", "M", Arg::None, nullptr },
{ 'p', "port", "Port for incoming peers (Default: " TR_DEFAULT_PEER_PORT_STR ")", "p", Arg::Required, "<port>" },
{ 't',
"tos",
"Peer socket DSCP / ToS setting (number, or a DSCP string, e.g. 'af11' or 'cs0', default=" TR_DEFAULT_PEER_SOCKET_TOS_STR
")",
"t",
Arg::Required,
"<dscp-or-tos>" },
{ 'u', "uplimit", "Set max upload speed in " SPEED_K_STR, "u", Arg::Required, "<speed>" },
{ 'U', "no-uplimit", "Don't limit the upload speed", "U", Arg::None, nullptr },
{ 'v', "verify", "Verify the specified torrent", "v", Arg::None, nullptr },
{ 'V', "version", "Show version number and exit", "V", Arg::None, nullptr },
{ 'w', "download-dir", "Where to save downloaded data", "w", Arg::Required, "<path>" },
{ 500, "sequential-download", "Download pieces sequentially", "seq", Arg::None, nullptr },
{ 0, nullptr, nullptr, nullptr, false, nullptr } }
};
{ 0, nullptr, nullptr, nullptr, Arg::None, nullptr },
} };
static_assert(Options[std::size(Options) - 2].val != 0);
} // namespace
namespace
{
int parseCommandLine(tr_variant*, int argc, char const** argv);
void sigHandler(int signal);

View File

@@ -75,79 +75,86 @@ char constexpr Usage[] = "Transmission " LONG_VERSION_STRING
"\n"
"Usage: transmission-daemon [options]";
// --- Config File
auto constexpr Options = std::array<tr_option, 47>{
{ { 'a', "allowed", "Allowed IP addresses. (Default: " TR_DEFAULT_RPC_WHITELIST ")", "a", true, "<list>" },
{ 'b', "blocklist", "Enable peer blocklists", "b", false, nullptr },
{ 'B', "no-blocklist", "Disable peer blocklists", "B", false, nullptr },
{ 'c', "watch-dir", "Where to watch for new torrent files", "c", true, "<directory>" },
{ 'C', "no-watch-dir", "Disable the watch-dir", "C", false, nullptr },
{ 941, "incomplete-dir", "Where to store new torrents until they're complete", nullptr, true, "<directory>" },
{ 942, "no-incomplete-dir", "Don't store incomplete torrents in a different location", nullptr, false, nullptr },
{ 'd', "dump-settings", "Dump the settings and exit", "d", false, nullptr },
{ 943, "default-trackers", "Trackers for public torrents to use automatically", nullptr, true, "<list>" },
{ 'e', "logfile", "Dump the log messages to this filename", "e", true, "<filename>" },
{ 'f', "foreground", "Run in the foreground instead of daemonizing", "f", false, nullptr },
{ 'g', "config-dir", "Where to look for configuration files", "g", true, "<path>" },
{ 'p', "port", "RPC port (Default: " TR_DEFAULT_RPC_PORT_STR ")", "p", true, "<port>" },
{ 't', "auth", "Require authentication", "t", false, nullptr },
{ 'T', "no-auth", "Don't require authentication", "T", false, nullptr },
{ 'u', "username", "Set username for authentication", "u", true, "<username>" },
{ 'v', "password", "Set password for authentication", "v", true, "<password>" },
{ 'V', "version", "Show version number and exit", "V", false, nullptr },
{ 810, "log-level", "Must be 'critical', 'error', 'warn', 'info', 'debug', or 'trace'.", nullptr, true, "<level>" },
{ 811, "log-error", "Deprecated. Use --log-level=error", nullptr, false, nullptr },
{ 812, "log-info", "Deprecated. Use --log-level=info", nullptr, false, nullptr },
{ 813, "log-debug", "Deprecated. Use --log-level=debug", nullptr, false, nullptr },
{ 'w', "download-dir", "Where to save downloaded data", "w", true, "<path>" },
{ 800, "paused", "Pause all torrents on startup", nullptr, false, nullptr },
{ 'o', "dht", "Enable distributed hash tables (DHT)", "o", false, nullptr },
{ 'O', "no-dht", "Disable distributed hash tables (DHT)", "O", false, nullptr },
{ 'y', "lpd", "Enable local peer discovery (LPD)", "y", false, nullptr },
{ 'Y', "no-lpd", "Disable local peer discovery (LPD)", "Y", false, nullptr },
{ 830, "utp", "Enable µTP for peer connections", nullptr, false, nullptr },
{ 831, "no-utp", "Disable µTP for peer connections", nullptr, false, nullptr },
{ 'P', "peerport", "Port for incoming peers (Default: " TR_DEFAULT_PEER_PORT_STR ")", "P", true, "<port>" },
{ 'm', "portmap", "Enable portmapping via NAT-PMP or UPnP", "m", false, nullptr },
{ 'M', "no-portmap", "Disable portmapping", "M", false, nullptr },
{ 'L',
"peerlimit-global",
"Maximum overall number of peers (Default: " TR_DEFAULT_PEER_LIMIT_GLOBAL_STR ")",
"L",
true,
"<limit>" },
{ 'l',
"peerlimit-torrent",
"Maximum number of peers per torrent (Default: " TR_DEFAULT_PEER_LIMIT_TORRENT_STR ")",
"l",
true,
"<limit>" },
{ 910, "encryption-required", "Encrypt all peer connections", "er", false, nullptr },
{ 911, "encryption-preferred", "Prefer encrypted peer connections", "ep", false, nullptr },
{ 912, "encryption-tolerated", "Prefer unencrypted peer connections", "et", false, nullptr },
{ 'i', "bind-address-ipv4", "Where to listen for peer connections", "i", true, "<ipv4 addr>" },
{ 'I', "bind-address-ipv6", "Where to listen for peer connections", "I", true, "<ipv6 addr>" },
{ 'r', "rpc-bind-address", "Where to listen for RPC connections", "r", true, "<ip addr>" },
{ 953,
"global-seedratio",
"All torrents, unless overridden by a per-torrent setting, should seed until a specific ratio",
"gsr",
true,
"ratio" },
{ 954,
"no-global-seedratio",
"All torrents, unless overridden by a per-torrent setting, should seed regardless of ratio",
"GSR",
false,
nullptr },
{ 994, "sequential-download", "Enable sequential download by default", "seq", false, nullptr },
{ 995, "no-sequential-download", "Disable sequential download by default", "SEQ", false, nullptr },
{ 'x', "pid-file", "Enable PID file", "x", true, "<pid-file>" },
{ 0, nullptr, nullptr, nullptr, false, nullptr } }
};
using Arg = tr_option::Arg;
auto constexpr Options = std::array<tr_option, 47>{ {
{ 'a', "allowed", "Allowed IP addresses. (Default: " TR_DEFAULT_RPC_WHITELIST ")", "a", Arg::Required, "<list>" },
{ 'b', "blocklist", "Enable peer blocklists", "b", Arg::None, nullptr },
{ 'B', "no-blocklist", "Disable peer blocklists", "B", Arg::None, nullptr },
{ 'c', "watch-dir", "Where to watch for new torrent files", "c", Arg::Required, "<directory>" },
{ 'C', "no-watch-dir", "Disable the watch-dir", "C", Arg::None, nullptr },
{ 941, "incomplete-dir", "Where to store new torrents until they're complete", nullptr, Arg::Required, "<directory>" },
{ 942, "no-incomplete-dir", "Don't store incomplete torrents in a different location", nullptr, Arg::None, nullptr },
{ 'd', "dump-settings", "Dump the settings and exit", "d", Arg::None, nullptr },
{ 943, "default-trackers", "Trackers for public torrents to use automatically", nullptr, Arg::Required, "<list>" },
{ 'e', "logfile", "Dump the log messages to this filename", "e", Arg::Required, "<filename>" },
{ 'f', "foreground", "Run in the foreground instead of daemonizing", "f", Arg::None, nullptr },
{ 'g', "config-dir", "Where to look for configuration files", "g", Arg::Required, "<path>" },
{ 'p', "port", "RPC port (Default: " TR_DEFAULT_RPC_PORT_STR ")", "p", Arg::Required, "<port>" },
{ 't', "auth", "Require authentication", "t", Arg::None, nullptr },
{ 'T', "no-auth", "Don't require authentication", "T", Arg::None, nullptr },
{ 'u', "username", "Set username for authentication", "u", Arg::Required, "<username>" },
{ 'v', "password", "Set password for authentication", "v", Arg::Required, "<password>" },
{ 'V', "version", "Show version number and exit", "V", Arg::None, nullptr },
{ 810,
"log-level",
"Must be 'critical', 'error', 'warn', 'info', 'debug', or 'trace'.",
nullptr,
Arg::Required,
"<level>" },
{ 811, "log-error", "Deprecated. Use --log-level=error", nullptr, Arg::None, nullptr },
{ 812, "log-info", "Deprecated. Use --log-level=info", nullptr, Arg::None, nullptr },
{ 813, "log-debug", "Deprecated. Use --log-level=debug", nullptr, Arg::None, nullptr },
{ 'w', "download-dir", "Where to save downloaded data", "w", Arg::Required, "<path>" },
{ 800, "paused", "Pause all torrents on startup", nullptr, Arg::None, nullptr },
{ 'o', "dht", "Enable distributed hash tables (DHT)", "o", Arg::None, nullptr },
{ 'O', "no-dht", "Disable distributed hash tables (DHT)", "O", Arg::None, nullptr },
{ 'y', "lpd", "Enable local peer discovery (LPD)", "y", Arg::None, nullptr },
{ 'Y', "no-lpd", "Disable local peer discovery (LPD)", "Y", Arg::None, nullptr },
{ 830, "utp", "Enable µTP for peer connections", nullptr, Arg::None, nullptr },
{ 831, "no-utp", "Disable µTP for peer connections", nullptr, Arg::None, nullptr },
{ 'P', "peerport", "Port for incoming peers (Default: " TR_DEFAULT_PEER_PORT_STR ")", "P", Arg::Required, "<port>" },
{ 'm', "portmap", "Enable portmapping via NAT-PMP or UPnP", "m", Arg::None, nullptr },
{ 'M', "no-portmap", "Disable portmapping", "M", Arg::None, nullptr },
{ 'L',
"peerlimit-global",
"Maximum overall number of peers (Default: " TR_DEFAULT_PEER_LIMIT_GLOBAL_STR ")",
"L",
Arg::Required,
"<limit>" },
{ 'l',
"peerlimit-torrent",
"Maximum number of peers per torrent (Default: " TR_DEFAULT_PEER_LIMIT_TORRENT_STR ")",
"l",
Arg::Required,
"<limit>" },
{ 910, "encryption-required", "Encrypt all peer connections", "er", Arg::None, nullptr },
{ 911, "encryption-preferred", "Prefer encrypted peer connections", "ep", Arg::None, nullptr },
{ 912, "encryption-tolerated", "Prefer unencrypted peer connections", "et", Arg::None, nullptr },
{ 'i', "bind-address-ipv4", "Where to listen for peer connections", "i", Arg::Required, "<ipv4 addr>" },
{ 'I', "bind-address-ipv6", "Where to listen for peer connections", "I", Arg::Required, "<ipv6 addr>" },
{ 'r', "rpc-bind-address", "Where to listen for RPC connections", "r", Arg::Required, "<ip addr>" },
{ 953,
"global-seedratio",
"All torrents, unless overridden by a per-torrent setting, should seed until a specific ratio",
"gsr",
Arg::Required,
"ratio" },
{ 954,
"no-global-seedratio",
"All torrents, unless overridden by a per-torrent setting, should seed regardless of ratio",
"GSR",
Arg::None,
nullptr },
{ 994, "sequential-download", "Enable sequential download by default", "seq", Arg::None, nullptr },
{ 995, "no-sequential-download", "Disable sequential download by default", "SEQ", Arg::None, nullptr },
{ 'x', "pid-file", "Enable PID file", "x", Arg::Required, "<pid-file>" },
{ 0, nullptr, nullptr, nullptr, Arg::None, nullptr },
} };
static_assert(Options[std::size(Options) - 2].val != 0);
} // namespace
namespace
{
[[nodiscard]] std::string getConfigDir(int argc, char const* const* argv)
{
int c;

View File

@@ -21,19 +21,18 @@ int tr_optind = 1;
namespace
{
[[nodiscard]] constexpr std::string_view getArgName(tr_option const* opt)
[[nodiscard]] std::string getArgName(tr_option const* opt)
{
if (!opt->has_arg)
auto const* const arg_name = opt->argName != nullptr ? opt->argName : "<args>";
switch (opt->arg)
{
return ""sv;
case tr_option::Arg::None:
return ""s;
case tr_option::Arg::Optional:
return fmt::format("[{}]", arg_name);
default: // tr_option::Arg::Required
return arg_name;
}
if (opt->argName != nullptr)
{
return opt->argName;
}
return "<args>"sv;
}
[[nodiscard]] constexpr size_t get_next_line_len(std::string_view description, size_t maxlen)
@@ -113,7 +112,7 @@ tr_option const* findOption(tr_option const* opts, char const* str, char const**
size_t len = o->longName != nullptr ? strlen(o->longName) : 0;
if (matchlen < len && str[0] == '-' && str[1] == '-' && strncmp(str + 2, o->longName, len) == 0 &&
(str[len + 2] == '\0' || (o->has_arg && str[len + 2] == '=')))
(str[len + 2] == '\0' || (o->has_arg() && str[len + 2] == '=')))
{
matchlen = len;
match = o;
@@ -122,7 +121,8 @@ tr_option const* findOption(tr_option const* opts, char const* str, char const**
len = o->shortName != nullptr ? strlen(o->shortName) : 0;
if (matchlen < len && str[0] == '-' && strncmp(str + 1, o->shortName, len) == 0 && (str[len + 1] == '\0' || o->has_arg))
if (matchlen < len && str[0] == '-' && strncmp(str + 1, o->shortName, len) == 0 &&
(str[len + 1] == '\0' || o->has_arg()))
{
matchlen = len;
match = o;
@@ -165,7 +165,7 @@ void tr_getopt_usage(char const* app_name, char const* description, struct tr_op
maxWidth(o, long_width, short_width, arg_width);
}
auto const help = tr_option{ -1, "help", "Display this help page and exit", "h", false, nullptr };
auto const help = tr_option{ -1, "help", "Display this help page and exit", "h", tr_option::Arg::None, nullptr };
maxWidth(&help, long_width, short_width, arg_width);
if (description == nullptr)
@@ -215,7 +215,7 @@ int tr_getopt(char const* usage, int argc, char const* const* argv, tr_option co
return TR_OPT_UNK;
}
if (!o->has_arg)
if (!o->has_arg())
{
/* no argument needed for this option, so we're done */
if (arg != nullptr)
@@ -223,28 +223,28 @@ int tr_getopt(char const* usage, int argc, char const* const* argv, tr_option co
return TR_OPT_ERR;
}
*setme_optarg = nullptr;
++tr_optind;
return o->val;
}
/* option needed an argument, and it was embedded in this string */
++tr_optind;
/* option allows an argument, and it was embedded in this string */
if (arg != nullptr)
{
*setme_optarg = arg;
++tr_optind;
return o->val;
}
/* throw an error if the option needed an argument but didn't get one */
if (++tr_optind >= argc)
if (tr_optind >= argc || findOption(opts, argv[tr_optind], nullptr) != nullptr)
{
return TR_OPT_ERR;
}
/* throw an error if the option needed an argument but didn't get one */
if (o->arg == tr_option::Arg::Required)
{
return TR_OPT_ERR;
}
if (findOption(opts, argv[tr_optind], nullptr) != nullptr)
{
return TR_OPT_ERR;
return o->val;
}
*setme_optarg = argv[tr_optind++];

View File

@@ -17,11 +17,23 @@ extern int tr_optind;
struct tr_option
{
enum class Arg : uint8_t
{
None,
Optional,
Required
};
[[nodiscard]] constexpr bool has_arg() const noexcept
{
return arg >= Arg::Optional;
}
int val; /* the value to return from tr_getopt() */
char const* longName; /* --long-form */
char const* description; /* option's description for tr_getopt_usage() */
char const* shortName; /* short form */
bool has_arg; /* 0 for no argument, 1 for argument */
Arg arg; /* See enum class Arg */
char const* argName; /* argument's description for tr_getopt_usage() */
};

View File

@@ -29,17 +29,22 @@ char const* const DisplayName = "transmission-qt";
auto constexpr FileArgsSeparator = "--"sv;
auto constexpr QtArgsSeparator = "---"sv;
std::array<tr_option, 8> const Opts = {
tr_option{ 'g', "config-dir", "Where to look for configuration files", "g", true, "<path>" },
{ 'm', "minimized", "Start minimized in system tray", "m", false, nullptr },
{ 'p', "port", "Port to use when connecting to an existing session", "p", true, "<port>" },
{ 'r', "remote", "Connect to an existing session at the specified hostname", "r", true, "<host>" },
{ 'u', "username", "Username to use when connecting to an existing session", "u", true, "<username>" },
{ 'v', "version", "Show version number and exit", "v", false, nullptr },
{ 'w', "password", "Password to use when connecting to an existing session", "w", true, "<password>" },
{ 0, nullptr, nullptr, nullptr, false, nullptr }
};
using Arg = tr_option::Arg;
auto constexpr Opts = std::array<tr_option, 8>{ {
{ 'g', "config-dir", "Where to look for configuration files", "g", Arg::Required, "<path>" },
{ 'm', "minimized", "Start minimized in system tray", "m", Arg::None, nullptr },
{ 'p', "port", "Port to use when connecting to an existing session", "p", Arg::Required, "<port>" },
{ 'r', "remote", "Connect to an existing session at the specified hostname", "r", Arg::Required, "<host>" },
{ 'u', "username", "Username to use when connecting to an existing session", "u", Arg::Required, "<username>" },
{ 'v', "version", "Show version number and exit", "v", Arg::None, nullptr },
{ 'w', "password", "Password to use when connecting to an existing session", "w", Arg::Required, "<password>" },
{ 0, nullptr, nullptr, nullptr, Arg::None, nullptr },
} };
static_assert(Opts[std::size(Opts) - 2].val != 0);
} // namespace
namespace
{
char const* getUsage()
{
return "Usage:\n"

View File

@@ -11,18 +11,24 @@
namespace
{
auto const Options = std::array<tr_option, 8>{
tr_option{ 'p', "private", "Allow this torrent to only be used with the specified tracker(s)", "p", false, nullptr },
{ 'o', "outfile", "Save the generated .torrent to this filename", "o", true, "<file>" },
{ 's', "piecesize", "Set how many KiB each piece should be, overriding the preferred default", "s", true, "<size in KiB>" },
{ 'c', "comment", "Add a comment", "c", true, "<comment>" },
{ 't', "tracker", "Add a tracker's announce URL", "t", true, "<url>" },
{ 'q', "pooka", "Pooka", "pk", false, nullptr },
{ 'V', "version", "Show version number and exit", "V", false, nullptr },
{ 0, nullptr, nullptr, nullptr, false, nullptr }
};
using Arg = tr_option::Arg;
auto constexpr Options = std::array<tr_option, 9>{ {
{ 'p', "private", "Allow this torrent to only be used with the specified tracker(s)", "p", Arg::None, nullptr },
{ 'o', "outfile", "Save the generated .torrent to this filename", "o", Arg::Required, "<file>" },
{ 's',
"piecesize",
"Set how many KiB each piece should be, overriding the preferred default",
"s",
Arg::Required,
"<size in KiB>" },
{ 'c', "comment", "Add a comment", "c", Arg::Required, "<comment>" },
{ 't', "tracker", "Add a tracker's announce URL", "t", Arg::Required, "<url>" },
{ 'q', "pooka", "Pooka", "pk", Arg::None, nullptr },
{ 'V', "version", "Show version number and exit", "V", Arg::None, nullptr },
{ 994, "sequential-download", "Download the torrent sequentially", "seq", Arg::Optional, "<piece>" },
{ 0, nullptr, nullptr, nullptr, Arg::None, nullptr },
} };
static_assert(Options[std::size(Options) - 2].val != 0);
} // namespace
class GetoptTest : public ::testing::Test
@@ -54,102 +60,183 @@ protected:
TEST_F(GetoptTest, noOptions)
{
auto const args = std::array<char const*, 1>{ "/some/path/tr-getopt-test" };
auto constexpr ExpectedN = 0;
auto const expected_c = std::array<int, ExpectedN>{};
auto const expected_opt_arg = std::array<char const*, ExpectedN>{};
runTest(args.size(), args.data(), ExpectedN, expected_c.data(), expected_opt_arg.data());
static auto constexpr Args = std::array<char const*, 1>{ "/some/path/tr-getopt-test" };
static auto constexpr ExpectedN = 0;
static auto constexpr ExpectedC = std::array<int, ExpectedN>{};
static auto constexpr ExpectedOptArg = std::array<char const*, ExpectedN>{};
runTest(Args.size(), Args.data(), ExpectedN, ExpectedC.data(), ExpectedOptArg.data());
}
TEST_F(GetoptTest, shortNoarg)
{
auto const args = std::array<char const*, 2>{ "/some/path/tr-getopt-test", "-p" };
auto constexpr ExpectedN = 1;
auto const expected_c = std::array<int, ExpectedN>{ 'p' };
auto const expected_opt_arg = std::array<char const*, ExpectedN>{ nullptr };
runTest(args.size(), args.data(), ExpectedN, expected_c.data(), expected_opt_arg.data());
static auto constexpr Args = std::array<char const*, 2>{ "/some/path/tr-getopt-test", "-p" };
static auto constexpr ExpectedN = 1;
static auto constexpr ExpectedC = std::array<int, ExpectedN>{ 'p' };
static auto constexpr ExpectedOptArg = std::array<char const*, ExpectedN>{ nullptr };
runTest(Args.size(), Args.data(), ExpectedN, ExpectedC.data(), ExpectedOptArg.data());
}
TEST_F(GetoptTest, longNoarg)
{
auto const args = std::array<char const*, 2>{ "/some/path/tr-getopt-test", "--private" };
auto constexpr ExpectedN = 1;
auto const expected_c = std::array<int, ExpectedN>{ 'p' };
auto const expected_opt_arg = std::array<char const*, ExpectedN>{ nullptr };
runTest(args.size(), args.data(), ExpectedN, expected_c.data(), expected_opt_arg.data());
static auto constexpr Args = std::array<char const*, 2>{ "/some/path/tr-getopt-test", "--private" };
static auto constexpr ExpectedN = 1;
static auto constexpr ExpectedC = std::array<int, ExpectedN>{ 'p' };
static auto constexpr ExpectedOptArg = std::array<char const*, ExpectedN>{ nullptr };
runTest(Args.size(), Args.data(), ExpectedN, ExpectedC.data(), ExpectedOptArg.data());
}
TEST_F(GetoptTest, shortWithArg)
TEST_F(GetoptTest, shortWithRequiredArg)
{
auto const args = std::array<char const*, 3>{ "/some/path/tr-getopt-test", "-o", "/tmp/outfile" };
static auto constexpr Args = std::array<char const*, 3>{ "/some/path/tr-getopt-test", "-o", "/tmp/outfile" };
auto constexpr ExpectedN = 1;
auto const expected_c = std::array<int, ExpectedN>{ 'o' };
auto const expected_opt_arg = std::array<char const*, ExpectedN>{ "/tmp/outfile" };
runTest(args.size(), args.data(), ExpectedN, expected_c.data(), expected_opt_arg.data());
static auto constexpr ExpectedC = std::array<int, ExpectedN>{ 'o' };
static auto constexpr ExpectedOptArg = std::array<char const*, ExpectedN>{ "/tmp/outfile" };
runTest(Args.size(), Args.data(), ExpectedN, ExpectedC.data(), ExpectedOptArg.data());
}
TEST_F(GetoptTest, longWithArg)
TEST_F(GetoptTest, longWithRequiredArg)
{
auto const args = std::array<char const*, 3>{ "/some/path/tr-getopt-test", "--outfile", "/tmp/outfile" };
auto constexpr ExpectedN = 1;
auto const expected_c = std::array<int, ExpectedN>{ 'o' };
auto const expected_opt_arg = std::array<char const*, ExpectedN>{ "/tmp/outfile" };
runTest(args.size(), args.data(), ExpectedN, expected_c.data(), expected_opt_arg.data());
static auto constexpr Args = std::array<char const*, 3>{ "/some/path/tr-getopt-test", "--outfile", "/tmp/outfile" };
static auto constexpr ExpectedN = 1;
static auto constexpr ExpectedC = std::array<int, ExpectedN>{ 'o' };
static auto constexpr ExpectedOptArg = std::array<char const*, ExpectedN>{ "/tmp/outfile" };
runTest(Args.size(), Args.data(), ExpectedN, ExpectedC.data(), ExpectedOptArg.data());
}
TEST_F(GetoptTest, shortWithArgAfterEq)
TEST_F(GetoptTest, shortWithRequiredArgAfterEq)
{
auto const args = std::array<char const*, 2>{ "/some/path/tr-getopt-test", "-o=/tmp/outfile" };
auto constexpr ExpectedN = 1;
auto const expected_c = std::array<int, ExpectedN>{ 'o' };
auto const expected_opt_arg = std::array<char const*, ExpectedN>{ "/tmp/outfile" };
runTest(args.size(), args.data(), ExpectedN, expected_c.data(), expected_opt_arg.data());
static auto constexpr Args = std::array<char const*, 2>{ "/some/path/tr-getopt-test", "-o=/tmp/outfile" };
static auto constexpr ExpectedN = 1;
static auto constexpr ExpectedC = std::array<int, ExpectedN>{ 'o' };
static auto constexpr ExpectedOptArg = std::array<char const*, ExpectedN>{ "/tmp/outfile" };
runTest(Args.size(), Args.data(), ExpectedN, ExpectedC.data(), ExpectedOptArg.data());
}
TEST_F(GetoptTest, longWithArgAfterEq)
TEST_F(GetoptTest, longWithRequiredArgAfterEq)
{
auto const args = std::array<char const*, 2>{ "/some/path/tr-getopt-test", "--outfile=/tmp/outfile" };
auto constexpr ExpectedN = 1;
auto const expected_c = std::array<int, ExpectedN>{ 'o' };
auto const expected_opt_arg = std::array<char const*, ExpectedN>{ "/tmp/outfile" };
runTest(args.size(), args.data(), ExpectedN, expected_c.data(), expected_opt_arg.data());
static auto constexpr Args = std::array<char const*, 2>{ "/some/path/tr-getopt-test", "--outfile=/tmp/outfile" };
static auto constexpr ExpectedN = 1;
static auto constexpr ExpectedC = std::array<int, ExpectedN>{ 'o' };
static auto constexpr ExpectedOptArg = std::array<char const*, ExpectedN>{ "/tmp/outfile" };
runTest(Args.size(), Args.data(), ExpectedN, ExpectedC.data(), ExpectedOptArg.data());
}
TEST_F(GetoptTest, unknownOption)
{
auto const args = std::array<char const*, 2>{ "/some/path/tr-getopt-test", "-z" };
auto constexpr ExpectedN = 1;
auto const expected_c = std::array<int, ExpectedN>{ TR_OPT_UNK };
auto const expected_opt_arg = std::array<char const*, ExpectedN>{ "-z" };
runTest(args.size(), args.data(), ExpectedN, expected_c.data(), expected_opt_arg.data());
static auto constexpr Args = std::array<char const*, 2>{ "/some/path/tr-getopt-test", "-z" };
static auto constexpr ExpectedN = 1;
static auto constexpr ExpectedC = std::array<int, ExpectedN>{ TR_OPT_UNK };
static auto constexpr ExpectedOptArg = std::array<char const*, ExpectedN>{ "-z" };
runTest(Args.size(), Args.data(), ExpectedN, ExpectedC.data(), ExpectedOptArg.data());
}
TEST_F(GetoptTest, missingArg)
TEST_F(GetoptTest, missingArgEnd)
{
auto const args = std::array<char const*, 2>{ "/some/path/tr-getopt-test", "-o" };
auto constexpr ExpectedN = 1;
auto const expected_c = std::array<int, ExpectedN>{ TR_OPT_ERR };
auto const expected_opt_arg = std::array<char const*, ExpectedN>{ nullptr };
runTest(args.size(), args.data(), ExpectedN, expected_c.data(), expected_opt_arg.data());
static auto constexpr Args = std::array<char const*, 2>{ "/some/path/tr-getopt-test", "-o" };
static auto constexpr ExpectedN = 1;
static auto constexpr ExpectedC = std::array<int, ExpectedN>{ TR_OPT_ERR };
static auto constexpr ExpectedOptArg = std::array<char const*, ExpectedN>{ nullptr };
runTest(Args.size(), Args.data(), ExpectedN, ExpectedC.data(), ExpectedOptArg.data());
}
TEST_F(GetoptTest, missingArgMiddle)
{
static auto constexpr Args = std::array<char const*, 3>{ "/some/path/tr-getopt-test", "-o", "-p" };
static auto constexpr ExpectedN = 2;
static auto constexpr ExpectedC = std::array<int, ExpectedN>{ TR_OPT_ERR, 'p' };
static auto constexpr ExpectedOptArg = std::array<char const*, ExpectedN>{ nullptr, nullptr };
runTest(Args.size(), Args.data(), ExpectedN, ExpectedC.data(), ExpectedOptArg.data());
}
TEST_F(GetoptTest, lotsOfOptions)
{
auto const args = std::array<char const*, 6>{
static auto constexpr Args = std::array<char const*, 6>{
"/some/path/tr-getopt-test", "--piecesize=4", "-c", "hello world", "-p", "--tracker=foo"
};
auto constexpr ExpectedN = 4;
auto const expected_c = std::array<int, ExpectedN>{ 's', 'c', 'p', 't' };
auto const expected_opt_arg = std::array<char const*, ExpectedN>{ "4", "hello world", nullptr, "foo" };
runTest(args.size(), args.data(), ExpectedN, expected_c.data(), expected_opt_arg.data());
static auto constexpr ExpectedN = 4;
static auto constexpr ExpectedC = std::array<int, ExpectedN>{ 's', 'c', 'p', 't' };
static auto constexpr ExpectedOptArg = std::array<char const*, ExpectedN>{ "4", "hello world", nullptr, "foo" };
runTest(Args.size(), Args.data(), ExpectedN, ExpectedC.data(), ExpectedOptArg.data());
}
TEST_F(GetoptTest, matchLongerKey)
{
// confirm that this resolves to 'q' and not 'p'
auto const args = std::array<char const*, 2>{ "/some/path/tr-getopt-test", "-pk" };
auto constexpr ExpectedN = 1;
auto const expected_c = std::array<int, ExpectedN>{ 'q' };
auto const expected_opt_arg = std::array<char const*, ExpectedN>{ nullptr };
runTest(args.size(), args.data(), ExpectedN, expected_c.data(), expected_opt_arg.data());
static auto constexpr Args = std::array<char const*, 2>{ "/some/path/tr-getopt-test", "-pk" };
static auto constexpr ExpectedN = 1;
static auto constexpr ExpectedC = std::array<int, ExpectedN>{ 'q' };
static auto constexpr ExpectedOptArg = std::array<char const*, ExpectedN>{ nullptr };
runTest(Args.size(), Args.data(), ExpectedN, ExpectedC.data(), ExpectedOptArg.data());
}
TEST_F(GetoptTest, shortWithOptionalArg)
{
static auto constexpr Args = std::array<char const*, 3>{ "/some/path/tr-getopt-test", "-seq", "12" };
static auto constexpr ExpectedN = 1;
static auto constexpr ExpectedC = std::array<int, ExpectedN>{ 994 };
static auto constexpr ExpectedOptArg = std::array<char const*, ExpectedN>{ "12" };
runTest(Args.size(), Args.data(), ExpectedN, ExpectedC.data(), ExpectedOptArg.data());
}
TEST_F(GetoptTest, longWithOptionalArg)
{
static auto constexpr Args = std::array<char const*, 3>{ "/some/path/tr-getopt-test", "--sequential-download", "12" };
static auto constexpr ExpectedN = 1;
static auto constexpr ExpectedC = std::array<int, ExpectedN>{ 994 };
static auto constexpr ExpectedOptArg = std::array<char const*, ExpectedN>{ "12" };
runTest(Args.size(), Args.data(), ExpectedN, ExpectedC.data(), ExpectedOptArg.data());
}
TEST_F(GetoptTest, shortWithOptionalArgAfterEq)
{
static auto constexpr Args = std::array<char const*, 2>{ "/some/path/tr-getopt-test", "-seq=12" };
static auto constexpr ExpectedN = 1;
static auto constexpr ExpectedC = std::array<int, ExpectedN>{ 994 };
static auto constexpr ExpectedOptArg = std::array<char const*, ExpectedN>{ "12" };
runTest(Args.size(), Args.data(), ExpectedN, ExpectedC.data(), ExpectedOptArg.data());
}
TEST_F(GetoptTest, longWithOptionalArgAfterEq)
{
static auto constexpr Args = std::array<char const*, 2>{ "/some/path/tr-getopt-test", "--sequential-download=12" };
static auto constexpr ExpectedN = 1;
static auto constexpr ExpectedC = std::array<int, ExpectedN>{ 994 };
static auto constexpr ExpectedOptArg = std::array<char const*, ExpectedN>{ "12" };
runTest(Args.size(), Args.data(), ExpectedN, ExpectedC.data(), ExpectedOptArg.data());
}
TEST_F(GetoptTest, shortWithoutOptionalArgEnd)
{
static auto constexpr Args = std::array<char const*, 2>{ "/some/path/tr-getopt-test", "-seq" };
static auto constexpr ExpectedN = 1;
static auto constexpr ExpectedC = std::array<int, ExpectedN>{ 994 };
static auto constexpr ExpectedOptArg = std::array<char const*, ExpectedN>{ nullptr };
runTest(Args.size(), Args.data(), ExpectedN, ExpectedC.data(), ExpectedOptArg.data());
}
TEST_F(GetoptTest, longWithoutOptionalArgEnd)
{
static auto constexpr Args = std::array<char const*, 2>{ "/some/path/tr-getopt-test", "--sequential-download" };
static auto constexpr ExpectedN = 1;
static auto constexpr ExpectedC = std::array<int, ExpectedN>{ 994 };
static auto constexpr ExpectedOptArg = std::array<char const*, ExpectedN>{ nullptr };
runTest(Args.size(), Args.data(), ExpectedN, ExpectedC.data(), ExpectedOptArg.data());
}
TEST_F(GetoptTest, shortWithoutOptionalArgMiddle)
{
static auto constexpr Args = std::array<char const*, 3>{ "/some/path/tr-getopt-test", "-seq", "-p" };
static auto constexpr ExpectedN = 2;
static auto constexpr ExpectedC = std::array<int, ExpectedN>{ 994, 'p' };
static auto constexpr ExpectedOptArg = std::array<char const*, ExpectedN>{ nullptr, nullptr };
runTest(Args.size(), Args.data(), ExpectedN, ExpectedC.data(), ExpectedOptArg.data());
}
TEST_F(GetoptTest, longWithoutOptionalArgMiddle)
{
static auto constexpr Args = std::array<char const*, 3>{ "/some/path/tr-getopt-test", "--sequential-download", "-p" };
static auto constexpr ExpectedN = 2;
static auto constexpr ExpectedC = std::array<int, ExpectedN>{ 994, 'p' };
static auto constexpr ExpectedOptArg = std::array<char const*, ExpectedN>{ nullptr, nullptr };
runTest(Args.size(), Args.data(), ExpectedN, ExpectedC.data(), ExpectedOptArg.data());
}

View File

@@ -42,19 +42,24 @@ char constexpr Usage[] = "Usage: transmission-create [options] <file|directory>"
uint32_t constexpr KiB = 1024;
auto constexpr Options = std::array<tr_option, 10>{
{ { 'p', "private", "Allow this torrent to only be used with the specified tracker(s)", "p", false, nullptr },
{ 'r', "source", "Set the source for private trackers", "r", true, "<source>" },
{ 'o', "outfile", "Save the generated .torrent to this filename", "o", true, "<file>" },
{ 's', "piecesize", "Set the piece size in KiB, overriding the preferred default", "s", true, "<KiB>" },
{ 'c', "comment", "Add a comment", "c", true, "<comment>" },
{ 't', "tracker", "Add a tracker's announce URL", "t", true, "<url>" },
{ 'w', "webseed", "Add a webseed URL", "w", true, "<url>" },
{ 'x', "anonymize", R"(Omit "Creation date" and "Created by" info)", nullptr, false, nullptr },
{ 'V', "version", "Show version number and exit", "V", false, nullptr },
{ 0, nullptr, nullptr, nullptr, false, nullptr } }
};
using Arg = tr_option::Arg;
auto constexpr Options = std::array<tr_option, 10>{ {
{ 'p', "private", "Allow this torrent to only be used with the specified tracker(s)", "p", Arg::None, nullptr },
{ 'r', "source", "Set the source for private trackers", "r", Arg::Required, "<source>" },
{ 'o', "outfile", "Save the generated .torrent to this filename", "o", Arg::Required, "<file>" },
{ 's', "piecesize", "Set the piece size in KiB, overriding the preferred default", "s", Arg::Required, "<KiB>" },
{ 'c', "comment", "Add a comment", "c", Arg::Required, "<comment>" },
{ 't', "tracker", "Add a tracker's announce URL", "t", Arg::Required, "<url>" },
{ 'w', "webseed", "Add a webseed URL", "w", Arg::Required, "<url>" },
{ 'x', "anonymize", R"(Omit "Creation date" and "Created by" info)", nullptr, Arg::None, nullptr },
{ 'V', "version", "Show version number and exit", "V", Arg::None, nullptr },
{ 0, nullptr, nullptr, nullptr, Arg::None, nullptr },
} };
static_assert(Options[std::size(Options) - 2].val != 0);
} // namespace
namespace
{
struct app_options
{
tr_announce_list trackers;

View File

@@ -35,15 +35,20 @@ struct app_options
bool show_version = false;
};
auto constexpr Options = std::array<tr_option, 6>{
{ { 'a', "add", "Add a tracker's announce URL", "a", true, "<url>" },
{ 'd', "delete", "Delete a tracker's announce URL", "d", true, "<url>" },
{ 'r', "replace", "Search and replace a substring in the announce URLs", "r", true, "<old> <new>" },
{ 's', "source", "Set the source", "s", true, "<source>" },
{ 'V', "version", "Show version number and exit", "V", false, nullptr },
{ 0, nullptr, nullptr, nullptr, false, nullptr } }
};
using Arg = tr_option::Arg;
auto constexpr Options = std::array<tr_option, 6>{ {
{ 'a', "add", "Add a tracker's announce URL", "a", Arg::Required, "<url>" },
{ 'd', "delete", "Delete a tracker's announce URL", "d", Arg::Required, "<url>" },
{ 'r', "replace", "Search and replace a substring in the announce URLs", "r", Arg::Required, "<old> <new>" },
{ 's', "source", "Set the source", "s", Arg::Required, "<source>" },
{ 'V', "version", "Show version number and exit", "V", Arg::None, nullptr },
{ 0, nullptr, nullptr, nullptr, Arg::None, nullptr },
} };
static_assert(Options[std::size(Options) - 2].val != 0);
} // namespace
namespace
{
int parseCommandLine(app_options& opts, int argc, char const* const* argv)
{
int c;

View File

@@ -211,166 +211,170 @@ enum
// --- Command-Line Arguments
auto constexpr Options = std::array<tr_option, 105>{
{ { 'a', "add", "Add torrent files by filename or URL", "a", false, nullptr },
{ 970, "alt-speed", "Use the alternate Limits", "as", false, nullptr },
{ 971, "no-alt-speed", "Don't use the alternate Limits", "AS", false, nullptr },
{ 972, "alt-speed-downlimit", "max alternate download speed (in " SPEED_K_STR ")", "asd", true, "<speed>" },
{ 973, "alt-speed-uplimit", "max alternate upload speed (in " SPEED_K_STR ")", "asu", true, "<speed>" },
{ 974, "alt-speed-scheduler", "Use the scheduled on/off times", "asc", false, nullptr },
{ 975, "no-alt-speed-scheduler", "Don't use the scheduled on/off times", "ASC", false, nullptr },
{ 976, "alt-speed-time-begin", "Time to start using the alt speed limits (in hhmm)", nullptr, true, "<time>" },
{ 977, "alt-speed-time-end", "Time to stop using the alt speed limits (in hhmm)", nullptr, true, "<time>" },
{ 978, "alt-speed-days", "Numbers for any/all days of the week - eg. \"1-7\"", nullptr, true, "<days>" },
{ 963, "blocklist-update", "Blocklist update", nullptr, false, nullptr },
{ 'c', "incomplete-dir", "Where to store new torrents until they're complete", "c", true, "<dir>" },
{ 'C', "no-incomplete-dir", "Don't store incomplete torrents in a different location", "C", false, nullptr },
{ 'b', "debug", "Print debugging information", "b", false, nullptr },
{ 730, "bandwidth-group", "Set the current torrents' bandwidth group", "bwg", true, "<group>" },
{ 731, "no-bandwidth-group", "Reset the current torrents' bandwidth group", "nwg", false, nullptr },
{ 732, "list-groups", "Show bandwidth groups with their parameters", "lg", false, nullptr },
{ 'd',
"downlimit",
"Set the max download speed in " SPEED_K_STR " for the current torrent(s) or globally",
"d",
true,
"<speed>" },
{ 'D', "no-downlimit", "Disable max download speed for the current torrent(s) or globally", "D", false, nullptr },
{ 'e', "cache", "Set the maximum size of the session's memory cache (in " MEM_M_STR ")", "e", true, "<size>" },
{ 910, "encryption-required", "Encrypt all peer connections", "er", false, nullptr },
{ 911, "encryption-preferred", "Prefer encrypted peer connections", "ep", false, nullptr },
{ 912, "encryption-tolerated", "Prefer unencrypted peer connections", "et", false, nullptr },
{ 850, "exit", "Tell the transmission session to shut down", nullptr, false, nullptr },
{ 940, "files", "List the current torrent(s)' files", "f", false, nullptr },
{ 'F', "filter", "Filter the current torrent(s)", "F", true, "criterion" },
{ 'g', "get", "Mark files for download", "g", true, "<files>" },
{ 'G', "no-get", "Mark files for not downloading", "G", true, "<files>" },
{ 'i', "info", "Show the current torrent(s)' details", "i", false, nullptr },
{ 944, "print-ids", "Print the current torrent(s)' ids", "ids", false, nullptr },
{ 940, "info-files", "List the current torrent(s)' files", "if", false, nullptr },
{ 941, "info-peers", "List the current torrent(s)' peers", "ip", false, nullptr },
{ 942, "info-pieces", "List the current torrent(s)' pieces", "ic", false, nullptr },
{ 943, "info-trackers", "List the current torrent(s)' trackers", "it", false, nullptr },
{ 'j', "json", "Return RPC response as a JSON string", "j", false, nullptr },
{ 920, "session-info", "Show the session's details", "si", false, nullptr },
{ 921, "session-stats", "Show the session's statistics", "st", false, nullptr },
{ 'l', "list", "List all torrents", "l", false, nullptr },
{ 'L', "labels", "Set the current torrents' labels", "L", true, "<label[,label...]>" },
{ 960, "move", "Move current torrent's data to a new folder", nullptr, true, "<path>" },
{ 968, "unix-socket", "Use a Unix domain socket", nullptr, true, "<path>" },
{ 961, "find", "Tell Transmission where to find a torrent's data", nullptr, true, "<path>" },
{ 964, "rename", "Rename torrents root folder or a file", nullptr, true, "<name>" },
{ 965, "path", "Provide path for rename functions", nullptr, true, "<path>" },
{ 'm', "portmap", "Enable portmapping via NAT-PMP or UPnP", "m", false, nullptr },
{ 'M', "no-portmap", "Disable portmapping", "M", false, nullptr },
{ 'n', "auth", "Set username and password", "n", true, "<user:pw>" },
{ 810, "authenv", "Set authentication info from the TR_AUTH environment variable (user:pw)", "ne", false, nullptr },
{ 'N', "netrc", "Set authentication info from a .netrc file", "N", true, "<file>" },
{ 820, "ssl", "Use SSL when talking to daemon", nullptr, false, nullptr },
{ 'o', "dht", "Enable distributed hash tables (DHT)", "o", false, nullptr },
{ 'O', "no-dht", "Disable distributed hash tables (DHT)", "O", false, nullptr },
{ 'p', "port", "Port for incoming peers (Default: " TR_DEFAULT_PEER_PORT_STR ")", "p", true, "<port>" },
{ 962, "port-test", "Port testing", "pt", false, nullptr },
{ 'P', "random-port", "Random port for incoming peers", "P", false, nullptr },
{ 900, "priority-high", "Try to download these file(s) first", "ph", true, "<files>" },
{ 901, "priority-normal", "Try to download these file(s) normally", "pn", true, "<files>" },
{ 902, "priority-low", "Try to download these file(s) last", "pl", true, "<files>" },
{ 700, "bandwidth-high", "Give this torrent first chance at available bandwidth", "Bh", false, nullptr },
{ 701, "bandwidth-normal", "Give this torrent bandwidth left over by high priority torrents", "Bn", false, nullptr },
{ 702,
"bandwidth-low",
"Give this torrent bandwidth left over by high and normal priority torrents",
"Bl",
false,
nullptr },
{ 600, "reannounce", "Reannounce the current torrent(s)", nullptr, false, nullptr },
{ 'r', "remove", "Remove the current torrent(s)", "r", false, nullptr },
{ 930, "peers", "Set the maximum number of peers for the current torrent(s) or globally", "pr", true, "<max>" },
{ 840, "remove-and-delete", "Remove the current torrent(s) and delete local data", "rad", false, nullptr },
{ 800, "torrent-done-script", "A script to run when a torrent finishes downloading", nullptr, true, "<file>" },
{ 801, "no-torrent-done-script", "Don't run the done-downloading script", nullptr, false, nullptr },
{ 802, "torrent-done-seeding-script", "A script to run when a torrent finishes seeding", nullptr, true, "<file>" },
{ 803, "no-torrent-done-seeding-script", "Don't run the done-seeding script", nullptr, false, nullptr },
{ 950, "seedratio", "Let the current torrent(s) seed until a specific ratio", "sr", true, "ratio" },
{ 951, "seedratio-default", "Let the current torrent(s) use the global seedratio settings", "srd", false, nullptr },
{ 952, "no-seedratio", "Let the current torrent(s) seed regardless of ratio", "SR", false, nullptr },
{ 953,
"global-seedratio",
"All torrents, unless overridden by a per-torrent setting, should seed until a specific ratio",
"gsr",
true,
"ratio" },
{ 954,
"no-global-seedratio",
"All torrents, unless overridden by a per-torrent setting, should seed regardless of ratio",
"GSR",
false,
nullptr },
{ 955,
"idle-seeding-limit",
"Let the current torrent(s) seed until a specific amount idle time",
"isl",
true,
"<minutes>" },
{ 956,
"default-idle-seeding-limit",
"Let the current torrent(s) use the default idle seeding settings",
"isld",
false,
nullptr },
{ 957, "no-idle-seeding-limit", "Let the current torrent(s) seed regardless of idle time", "ISL", false, nullptr },
{ 958,
"global-idle-seeding-limit",
"All torrents, unless overridden by a per-torrent setting, should seed until a specific amount of idle time",
"gisl",
true,
"<minutes>" },
{ 959,
"no-global-idle-seeding-limit",
"All torrents, unless overridden by a per-torrent setting, should seed regardless of idle time",
"GISL",
false,
nullptr },
{ 710, "tracker-add", "Add a tracker to a torrent", "td", true, "<tracker>" },
{ 712, "tracker-remove", "Remove a tracker from a torrent", "tr", true, "<trackerId>" },
{ 's', "start", "Start the current torrent(s)", "s", false, nullptr },
{ 'S', "stop", "Stop the current torrent(s)", "S", false, nullptr },
{ 't', "torrent", "Set the current torrent(s)", "t", true, "<torrent>" },
{ 990, "start-paused", "Start added torrents paused", nullptr, false, nullptr },
{ 991, "no-start-paused", "Start added torrents unpaused", nullptr, false, nullptr },
{ 992, "trash-torrent", "Delete torrents after adding", nullptr, false, nullptr },
{ 993, "no-trash-torrent", "Do not delete torrents after adding", nullptr, false, nullptr },
{ 994, "sequential-download", "Download the torrent sequentially", "seq", false, nullptr },
{ 995, "no-sequential-download", "Download the torrent normally", "SEQ", false, nullptr },
{ 984, "honor-session", "Make the current torrent(s) honor the session limits", "hl", false, nullptr },
{ 985, "no-honor-session", "Make the current torrent(s) not honor the session limits", "HL", false, nullptr },
{ 'u',
"uplimit",
"Set the max upload speed in " SPEED_K_STR " for the current torrent(s) or globally",
"u",
true,
"<speed>" },
{ 'U', "no-uplimit", "Disable max upload speed for the current torrent(s) or globally", "U", false, nullptr },
{ 830, "utp", "Enable µTP for peer connections", nullptr, false, nullptr },
{ 831, "no-utp", "Disable µTP for peer connections", nullptr, false, nullptr },
{ 'v', "verify", "Verify the current torrent(s)", "v", false, nullptr },
{ 'V', "version", "Show version number and exit", "V", false, nullptr },
{ 'w',
"download-dir",
"When used in conjunction with --add, set the new torrent's download folder. "
"Otherwise, set the default download folder",
"w",
true,
"<path>" },
{ 'x', "pex", "Enable peer exchange (PEX)", "x", false, nullptr },
{ 'X', "no-pex", "Disable peer exchange (PEX)", "X", false, nullptr },
{ 'y', "lpd", "Enable local peer discovery (LPD)", "y", false, nullptr },
{ 'Y', "no-lpd", "Disable local peer discovery (LPD)", "Y", false, nullptr },
{ 941, "peer-info", "List the current torrent(s)' peers", "pi", false, nullptr },
{ 0, nullptr, nullptr, nullptr, false, nullptr } }
};
using Arg = tr_option::Arg;
auto constexpr Options = std::array<tr_option, 105>{ {
{ 'a', "add", "Add torrent files by filename or URL", "a", Arg::None, nullptr },
{ 970, "alt-speed", "Use the alternate Limits", "as", Arg::None, nullptr },
{ 971, "no-alt-speed", "Don't use the alternate Limits", "AS", Arg::None, nullptr },
{ 972, "alt-speed-downlimit", "max alternate download speed (in " SPEED_K_STR ")", "asd", Arg::Required, "<speed>" },
{ 973, "alt-speed-uplimit", "max alternate upload speed (in " SPEED_K_STR ")", "asu", Arg::Required, "<speed>" },
{ 974, "alt-speed-scheduler", "Use the scheduled on/off times", "asc", Arg::None, nullptr },
{ 975, "no-alt-speed-scheduler", "Don't use the scheduled on/off times", "ASC", Arg::None, nullptr },
{ 976, "alt-speed-time-begin", "Time to start using the alt speed limits (in hhmm)", nullptr, Arg::Required, "<time>" },
{ 977, "alt-speed-time-end", "Time to stop using the alt speed limits (in hhmm)", nullptr, Arg::Required, "<time>" },
{ 978, "alt-speed-days", "Numbers for any/all days of the week - eg. \"1-7\"", nullptr, Arg::Required, "<days>" },
{ 963, "blocklist-update", "Blocklist update", nullptr, Arg::None, nullptr },
{ 'c', "incomplete-dir", "Where to store new torrents until they're complete", "c", Arg::Required, "<dir>" },
{ 'C', "no-incomplete-dir", "Don't store incomplete torrents in a different location", "C", Arg::None, nullptr },
{ 'b', "debug", "Print debugging information", "b", Arg::None, nullptr },
{ 730, "bandwidth-group", "Set the current torrents' bandwidth group", "bwg", Arg::Required, "<group>" },
{ 731, "no-bandwidth-group", "Reset the current torrents' bandwidth group", "nwg", Arg::None, nullptr },
{ 732, "list-groups", "Show bandwidth groups with their parameters", "lg", Arg::None, nullptr },
{ 'd',
"downlimit",
"Set the max download speed in " SPEED_K_STR " for the current torrent(s) or globally",
"d",
Arg::Required,
"<speed>" },
{ 'D', "no-downlimit", "Disable max download speed for the current torrent(s) or globally", "D", Arg::None, nullptr },
{ 'e', "cache", "Set the maximum size of the session's memory cache (in " MEM_M_STR ")", "e", Arg::Required, "<size>" },
{ 910, "encryption-required", "Encrypt all peer connections", "er", Arg::None, nullptr },
{ 911, "encryption-preferred", "Prefer encrypted peer connections", "ep", Arg::None, nullptr },
{ 912, "encryption-tolerated", "Prefer unencrypted peer connections", "et", Arg::None, nullptr },
{ 850, "exit", "Tell the transmission session to shut down", nullptr, Arg::None, nullptr },
{ 940, "files", "List the current torrent(s)' files", "f", Arg::None, nullptr },
{ 'F', "filter", "Filter the current torrent(s)", "F", Arg::Required, "criterion" },
{ 'g', "get", "Mark files for download", "g", Arg::Required, "<files>" },
{ 'G', "no-get", "Mark files for not downloading", "G", Arg::Required, "<files>" },
{ 'i', "info", "Show the current torrent(s)' details", "i", Arg::None, nullptr },
{ 944, "print-ids", "Print the current torrent(s)' ids", "ids", Arg::None, nullptr },
{ 940, "info-files", "List the current torrent(s)' files", "if", Arg::None, nullptr },
{ 941, "info-peers", "List the current torrent(s)' peers", "ip", Arg::None, nullptr },
{ 942, "info-pieces", "List the current torrent(s)' pieces", "ic", Arg::None, nullptr },
{ 943, "info-trackers", "List the current torrent(s)' trackers", "it", Arg::None, nullptr },
{ 'j', "json", "Return RPC response as a JSON string", "j", Arg::None, nullptr },
{ 920, "session-info", "Show the session's details", "si", Arg::None, nullptr },
{ 921, "session-stats", "Show the session's statistics", "st", Arg::None, nullptr },
{ 'l', "list", "List all torrents", "l", Arg::None, nullptr },
{ 'L', "labels", "Set the current torrents' labels", "L", Arg::Required, "<label[,label...]>" },
{ 960, "move", "Move current torrent's data to a new folder", nullptr, Arg::Required, "<path>" },
{ 968, "unix-socket", "Use a Unix domain socket", nullptr, Arg::Required, "<path>" },
{ 961, "find", "Tell Transmission where to find a torrent's data", nullptr, Arg::Required, "<path>" },
{ 964, "rename", "Rename torrents root folder or a file", nullptr, Arg::Required, "<name>" },
{ 965, "path", "Provide path for rename functions", nullptr, Arg::Required, "<path>" },
{ 'm', "portmap", "Enable portmapping via NAT-PMP or UPnP", "m", Arg::None, nullptr },
{ 'M', "no-portmap", "Disable portmapping", "M", Arg::None, nullptr },
{ 'n', "auth", "Set username and password", "n", Arg::Required, "<user:pw>" },
{ 810, "authenv", "Set authentication info from the TR_AUTH environment variable (user:pw)", "ne", Arg::None, nullptr },
{ 'N', "netrc", "Set authentication info from a .netrc file", "N", Arg::Required, "<file>" },
{ 820, "ssl", "Use SSL when talking to daemon", nullptr, Arg::None, nullptr },
{ 'o', "dht", "Enable distributed hash tables (DHT)", "o", Arg::None, nullptr },
{ 'O', "no-dht", "Disable distributed hash tables (DHT)", "O", Arg::None, nullptr },
{ 'p', "port", "Port for incoming peers (Default: " TR_DEFAULT_PEER_PORT_STR ")", "p", Arg::Required, "<port>" },
{ 962, "port-test", "Port testing", "pt", Arg::None, nullptr },
{ 'P', "random-port", "Random port for incoming peers", "P", Arg::None, nullptr },
{ 900, "priority-high", "Try to download these file(s) first", "ph", Arg::Required, "<files>" },
{ 901, "priority-normal", "Try to download these file(s) normally", "pn", Arg::Required, "<files>" },
{ 902, "priority-low", "Try to download these file(s) last", "pl", Arg::Required, "<files>" },
{ 700, "bandwidth-high", "Give this torrent first chance at available bandwidth", "Bh", Arg::None, nullptr },
{ 701, "bandwidth-normal", "Give this torrent bandwidth left over by high priority torrents", "Bn", Arg::None, nullptr },
{ 702,
"bandwidth-low",
"Give this torrent bandwidth left over by high and normal priority torrents",
"Bl",
Arg::None,
nullptr },
{ 600, "reannounce", "Reannounce the current torrent(s)", nullptr, Arg::None, nullptr },
{ 'r', "remove", "Remove the current torrent(s)", "r", Arg::None, nullptr },
{ 930, "peers", "Set the maximum number of peers for the current torrent(s) or globally", "pr", Arg::Required, "<max>" },
{ 840, "remove-and-delete", "Remove the current torrent(s) and delete local data", "rad", Arg::None, nullptr },
{ 800, "torrent-done-script", "A script to run when a torrent finishes downloading", nullptr, Arg::Required, "<file>" },
{ 801, "no-torrent-done-script", "Don't run the done-downloading script", nullptr, Arg::None, nullptr },
{ 802, "torrent-done-seeding-script", "A script to run when a torrent finishes seeding", nullptr, Arg::Required, "<file>" },
{ 803, "no-torrent-done-seeding-script", "Don't run the done-seeding script", nullptr, Arg::None, nullptr },
{ 950, "seedratio", "Let the current torrent(s) seed until a specific ratio", "sr", Arg::Required, "ratio" },
{ 951, "seedratio-default", "Let the current torrent(s) use the global seedratio settings", "srd", Arg::None, nullptr },
{ 952, "no-seedratio", "Let the current torrent(s) seed regardless of ratio", "SR", Arg::None, nullptr },
{ 953,
"global-seedratio",
"All torrents, unless overridden by a per-torrent setting, should seed until a specific ratio",
"gsr",
Arg::Required,
"ratio" },
{ 954,
"no-global-seedratio",
"All torrents, unless overridden by a per-torrent setting, should seed regardless of ratio",
"GSR",
Arg::None,
nullptr },
{ 955,
"idle-seeding-limit",
"Let the current torrent(s) seed until a specific amount idle time",
"isl",
Arg::Required,
"<minutes>" },
{ 956,
"default-idle-seeding-limit",
"Let the current torrent(s) use the default idle seeding settings",
"isld",
Arg::None,
nullptr },
{ 957, "no-idle-seeding-limit", "Let the current torrent(s) seed regardless of idle time", "ISL", Arg::None, nullptr },
{ 958,
"global-idle-seeding-limit",
"All torrents, unless overridden by a per-torrent setting, should seed until a specific amount of idle time",
"gisl",
Arg::Required,
"<minutes>" },
{ 959,
"no-global-idle-seeding-limit",
"All torrents, unless overridden by a per-torrent setting, should seed regardless of idle time",
"GISL",
Arg::None,
nullptr },
{ 710, "tracker-add", "Add a tracker to a torrent", "td", Arg::Required, "<tracker>" },
{ 712, "tracker-remove", "Remove a tracker from a torrent", "tr", Arg::Required, "<trackerId>" },
{ 's', "start", "Start the current torrent(s)", "s", Arg::None, nullptr },
{ 'S', "stop", "Stop the current torrent(s)", "S", Arg::None, nullptr },
{ 't', "torrent", "Set the current torrent(s)", "t", Arg::Required, "<torrent>" },
{ 990, "start-paused", "Start added torrents paused", nullptr, Arg::None, nullptr },
{ 991, "no-start-paused", "Start added torrents unpaused", nullptr, Arg::None, nullptr },
{ 992, "trash-torrent", "Delete torrents after adding", nullptr, Arg::None, nullptr },
{ 993, "no-trash-torrent", "Do not delete torrents after adding", nullptr, Arg::None, nullptr },
{ 994, "sequential-download", "Download the torrent sequentially", "seq", Arg::None, nullptr },
{ 995, "no-sequential-download", "Download the torrent normally", "SEQ", Arg::None, nullptr },
{ 984, "honor-session", "Make the current torrent(s) honor the session limits", "hl", Arg::None, nullptr },
{ 985, "no-honor-session", "Make the current torrent(s) not honor the session limits", "HL", Arg::None, nullptr },
{ 'u',
"uplimit",
"Set the max upload speed in " SPEED_K_STR " for the current torrent(s) or globally",
"u",
Arg::Required,
"<speed>" },
{ 'U', "no-uplimit", "Disable max upload speed for the current torrent(s) or globally", "U", Arg::None, nullptr },
{ 830, "utp", "Enable µTP for peer connections", nullptr, Arg::None, nullptr },
{ 831, "no-utp", "Disable µTP for peer connections", nullptr, Arg::None, nullptr },
{ 'v', "verify", "Verify the current torrent(s)", "v", Arg::None, nullptr },
{ 'V', "version", "Show version number and exit", "V", Arg::None, nullptr },
{ 'w',
"download-dir",
"When used in conjunction with --add, set the new torrent's download folder. "
"Otherwise, set the default download folder",
"w",
Arg::Required,
"<path>" },
{ 'x', "pex", "Enable peer exchange (PEX)", "x", Arg::None, nullptr },
{ 'X', "no-pex", "Disable peer exchange (PEX)", "X", Arg::None, nullptr },
{ 'y', "lpd", "Enable local peer discovery (LPD)", "y", Arg::None, nullptr },
{ 'Y', "no-lpd", "Disable local peer discovery (LPD)", "Y", Arg::None, nullptr },
{ 941, "peer-info", "List the current torrent(s)' peers", "pi", Arg::None, nullptr },
{ 0, nullptr, nullptr, nullptr, Arg::None, nullptr },
} };
static_assert(Options[std::size(Options) - 2].val != 0);
} // namespace
namespace
{
void show_usage()
{
tr_getopt_usage(MyName, Usage, std::data(Options));

View File

@@ -41,29 +41,33 @@ using namespace libtransmission::Values;
namespace
{
auto constexpr TimeoutSecs = std::chrono::seconds{ 30 };
char constexpr MyName[] = "transmission-show";
char constexpr Usage[] = "Usage: transmission-show [options] <torrent-file>";
auto options = std::array<tr_option, 14>{
{ { 'd', "header", "Show only header section", "d", false, nullptr },
{ 'i', "info", "Show only info section", "i", false, nullptr },
{ 't', "trackers", "Show only trackers section", "t", false, nullptr },
{ 'f', "files", "Show only file list", "f", false, nullptr },
{ 'D', "no-header", "Do not show header section", "D", false, nullptr },
{ 'I', "no-info", "Do not show info section", "I", false, nullptr },
{ 'T', "no-trackers", "Do not show trackers section", "T", false, nullptr },
{ 'F', "no-files", "Do not show files section", "F", false, nullptr },
{ 'b', "bytes", "Show file sizes in bytes", "b", false, nullptr },
{ 'm', "magnet", "Give a magnet link for the specified torrent", "m", false, nullptr },
{ 's', "scrape", "Ask the torrent's trackers how many peers are in the torrent's swarm", "s", false, nullptr },
{ 'u', "unsorted", "Do not sort files by name", "u", false, nullptr },
{ 'V', "version", "Show version number and exit", "V", false, nullptr },
{ 0, nullptr, nullptr, nullptr, false, nullptr } }
};
using Arg = tr_option::Arg;
auto constexpr Options = std::array<tr_option, 14>{ {
{ 'd', "header", "Show only header section", "d", Arg::None, nullptr },
{ 'i', "info", "Show only info section", "i", Arg::None, nullptr },
{ 't', "trackers", "Show only trackers section", "t", Arg::None, nullptr },
{ 'f', "files", "Show only file list", "f", Arg::None, nullptr },
{ 'D', "no-header", "Do not show header section", "D", Arg::None, nullptr },
{ 'I', "no-info", "Do not show info section", "I", Arg::None, nullptr },
{ 'T', "no-trackers", "Do not show trackers section", "T", Arg::None, nullptr },
{ 'F', "no-files", "Do not show files section", "F", Arg::None, nullptr },
{ 'b', "bytes", "Show file sizes in bytes", "b", Arg::None, nullptr },
{ 'm', "magnet", "Give a magnet link for the specified torrent", "m", Arg::None, nullptr },
{ 's', "scrape", "Ask the torrent's trackers how many peers are in the torrent's swarm", "s", Arg::None, nullptr },
{ 'u', "unsorted", "Do not sort files by name", "u", Arg::None, nullptr },
{ 'V', "version", "Show version number and exit", "V", Arg::None, nullptr },
{ 0, nullptr, nullptr, nullptr, Arg::None, nullptr },
} };
static_assert(Options[std::size(Options) - 2].val != 0);
} // namespace
namespace
{
struct app_opts
{
std::string_view filename;
@@ -83,7 +87,7 @@ int parseCommandLine(app_opts& opts, int argc, char const* const* argv)
int c;
char const* optarg;
while ((c = tr_getopt(Usage, argc, argv, std::data(options), &optarg)) != TR_OPT_DONE)
while ((c = tr_getopt(Usage, argc, argv, std::data(Options), &optarg)) != TR_OPT_DONE)
{
switch (c)
{
@@ -426,7 +430,7 @@ int tr_main(int argc, char* argv[])
if (std::empty(opts.filename))
{
fmt::print(stderr, "ERROR: No torrent file specified.\n");
tr_getopt_usage(MyName, Usage, std::data(options));
tr_getopt_usage(MyName, Usage, std::data(Options));
fmt::print(stderr, "\n");
return EXIT_FAILURE;
}