GithubHelp home page GithubHelp logo

muellan / clipp Goto Github PK

View Code? Open in Web Editor NEW
1.2K 29.0 144.0 256 KB

easy to use, powerful & expressive command line argument parsing for modern C++ / single header / usage & doc generation

License: MIT License

C++ 97.03% Python 2.37% CMake 0.60%
cpp cpp11 cli command-line argument-parser argument-parsing commandline cmdline-parser option options

clipp's People

Contributors

heavywatal avatar kessido avatar muellan avatar

Stargazers

 avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar

Watchers

 avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar

clipp's Issues

Option docstrings in generated documentation

Using VS 2017 Community 15.9.13 to build the following code:

int main(int argc, char *argv[])
{
    using namespace clipp; using std::cout; using std::string;

    enum class mode { help };
    mode selected = mode::help;
    std::vector<string> input;

    auto cli = (
        (command("help").set(selected, mode::help).doc("help with the thing")),
        option("-i", "--input") & value("name", input) % "input file",
        option("-v", "--version").call([] {cout << "version 1.0\n\n"; }).doc("show version"));

    parse(argc, argv, cli);
    cout << make_man_page(cli, "clipp-test");
    return 0;
}

The output is missing --input option name in the OPTIONS:

SYNOPSIS
        clipp-test help [-i <name>] [-v]

OPTIONS
        help        help with the thing
        <name>      input file
        -v, --version
                    show version

If I however prioritize the & operator in the option definition by adding the parenthesis like this:

        (option("-i", "--input") & value("name", input)) % "input file",

The output shows correctly:

SYNOPSIS
        clipp-test help [-i <name>] [-v]

OPTIONS
        help        help with the thing
        -i, --input <name>
                    input file

        -v, --version
                    show version

not set string value

sample code:

#include <iostream>
#include <filesystem>
#include <vector>
#include <clipp.h>

#define VERSION "0.0.0.0"
class app {
public:
    app(int argc, const char** argv) {
        args.assign(argv, argv + argc);
    }

    int exec() {
        try {
            cli =
                ("General:" %
                     ((clipp::option("-c", "--config") & clipp::value("file name", config_file)) %
                          "config file",
                      (clipp::option("-h", "--help").set(f_help)) % "help message",
                      (clipp::option("-v", "--version").set(f_version)) % "show version") ,
                 "Commands:" %
                     ("service" %
                      (clipp::command("service") % "service controll",
                       (((clipp::option("-I", "--install").set(cmd_install)) % "install service") |
                        ((clipp::option("-R", "--remove").set(cmd_remove)) % "remove service")))));

            auto parse_res = clipp::parse(args, cli);

            if(!parse_res.any_error()) {
                show_help();
                return 1;
            }

            if(f_help) {
                show_help();
                return 0;
            }

            if(f_version) {
                std::cout << VERSION << std::endl;
                return 0;
            }

            std::filesystem::path fname(config_file);
            std::cout << "cfg: " << config_file << "\n";
            std::cout << fname.is_absolute() << "\n";
            std::cout << fname << "\n";

            if(cmd_install) {
                //install
                return 0;
            }

            if(cmd_remove) {
                //remove
                return 0;
            }
        } catch(std::exception& rcError) {
            std::cerr << rcError.what() << std::endl;
            return 1;
        }
        return 0;
    }
    void show_help() {
        auto fmt = clipp::doc_formatting {}
                       .first_column(4)
                       .doc_column(36)
                       .merge_alternative_flags_with_common_prefix(true);

        std::cout << clipp::make_man_page(cli, "server", fmt)
                         .prepend_section("AUTHORS", "\tauthor")

                         .prepend_section("DESCRIPTION", "\tThe App")
                  << '\n';
    }

private:
    std::vector<std::string> args;
    clipp::group cli;
    bool f_help = false;
    bool f_version = false;
    bool cmd_install = false;
    bool cmd_remove = false;
    std::string config_file;
};

int main(int argc, const char* argv[]) {	
    app app_instance(argc, argv);
    return app_instance.exec();
}

flag output:

 .\cpp_clipp.exe -c config.toml

DESCRIPTION
        The App

AUTHORS
        author

SYNOPSIS
    server [-c <file name>] [-h] [-v] service [-(I|R)]

OPTIONS
    General:
        -c, --config <file name>    config file
        -h, --help                  help message
        -v, --version               show version

    Commands:
        service                     service controll
        -I, --install               install service
        -R, --remove                remove service

Output

.\cpp_clipp.exe -c config.toml
cfg:
0
""

variable config_file is empty.

Support std::optional<..>

I would like to be able to parse a struct containing std::optional<..> fields. For example:

struct arg_values {
     std::optional<int> pid;
} values;

auto cli = (
    clipp::option("--pid") & clipp::opt_integer("PID", values.parent_pid)
);

String option eats a value without its flag after repeatable values

It looks like a bug to me, but it is highly plausible that I misunderstand repeatable values or something. I would appreciate if you could explain the logic behind the following parsing result.

Code:

void main(int argc, char* argv[]) {
    std::vector<int> vec;
    std::string str = "hungry";
    auto cli = (
      clipp::option("-v", "--vec") & clipp::integers("label-v", vec),
      clipp::option("-s", "--str") & clipp::value("label-s", str)
    );
    auto parsed = clipp::parse(argc, argv, cli);
    if (!parsed) std::cout << "ERROR!\n";
    clipp::debug::print(std::cout, parsed);
    std::cout << "vec : [";
    for (const auto i: vec) std::cout << i << ",";
    std::cout << "]\n";
    std::cout << "str : " << str << "\n";
}

Product:

% ./a.out -v 1 2 hello
#1 -v -> -v
#2 1 -> label-v
#3 2 -> label-v         [repeat 1]
#4 hello -> label-s
vec : [1,2,]
str : hello

I expect "ERROR!" is printed in this case.
Thanks

Keep empty arguments as they are

Empty arguments seem to be dropped in parsing process (in parser::operator(), I guess). Is it possible to change this behavior on the user side and pass an empty argument "" as is? If not, I would suggest it should be changed for the consistency and flexibility.

Code:

int main(int argc, char* argv[]) {
    std::string optional = "default";
    std::string positional;
    auto cli = (
      clipp::option("-o", "--optional")
      & clipp::value(clipp::match::any, "label-o", optional),
      clipp::value("label-p", positional)
    );
    auto parsed = clipp::parse(argc, argv, cli);
    if (!parsed) std::cout << "ERROR!\n";
    clipp::debug::print(std::cout, parsed);
    std::cout << "optional: " << optional << "\n";
    std::cout << "positional: " << positional << "\n";
    return 0;
}

Product

% ./a.out -o "" hello
ERROR!
#1 -o -> -o
#3 hello -> label-o
label-p          [missing after 0]
optional: hello
positional:

I expect optional: (empty) and positional: hello.
Thanks.

Move most documentation out of README.md

This repository's README.md is way too long. The documentation specifics should be in a separate .md file, on the wiki, or both; and the main README.md should focus on the sections other than the Detailed Examples. In fact, Overview is also too long; I'd stick to a couple of examples, no more than that for the repo landing page.

(feature request) Support for conversion of std::filesystem::path

Since CLIs often have to deal with files and directories being passed to them on the command line, the C++17 filesystem classes make life a lot easier.
It would be nice to be able to just do this:

#include <filesystem>
std::filesystem::path input_file;
auto cli = (
		value("input file", input_file),
);

As far as I can tell, this would require another specialization of the make<> template.
While the issue is not really all that pressing for the official library (vendors are still working on officially implementing the C++17 library extensions, although std::experimental::filesystem does exist for VS2017), is there a documented way of providing user-supplied specializations of make<>?

Strange behavior with one_of

I have this code:

#include <iostream>

#include "clipp.h"

int main(int argc, char *argv[])
{
	std::string foo, bar, baz;

	auto a = (
		clipp::value("bar", bar)
	);

	auto b = (
		clipp::value("foo", foo),
		clipp::value("bar", bar)
	);

	auto cli = (a | b);

	if (!clipp::parse(argc, argv, cli)) {
		std::cerr << clipp::usage_lines(cli, argv[0]) << std::endl;
		return EXIT_FAILURE;
	}
}

Running c++ -std=c++17 main.cpp && ./a.out foo bar shows me the usage lines even though it's compatible.

Possible memory leak

data(): param{} {}

It seems that here param is initialized but sometimes not destroyed. ASAN Log (see #5):

    #0 0x3b85d2 in operator new(unsigned long) /opt/llvm/src/projects/compiler-rt/lib/asan/asan_new_delete.cc:92:3
    #1 0x534142 in std::__1::__allocate(unsigned long) /opt/llvm/stage/bin/../include/c++/v1/new:226:10
    #2 0x534142 in std::__1::allocator<std::__1::__function::__func<clipp::parameter::predicate_adapter, std::__1::allocator<clipp::parameter::predicate_adapter>, clipp::subrange (std::__1::basic_string<char, std::__1::char_traits<char>, std::__1::allocator<char> > const&)> >::allocate(unsigned long, void const*) /opt/llvm/stage/bin/../include/c++/v1/memory:1747
    #3 0x534142 in std::__1::function<clipp::subrange (std::__1::basic_string<char, std::__1::char_traits<char>, std::__1::allocator<char> > const&)>::function<clipp::parameter::predicate_adapter, void>(clipp::parameter::predicate_adapter) /opt/llvm/stage/bin/../include/c++/v1/functional:1774
    #4 0x5301d2 in clipp::parameter::parameter() .../clipp.h:1820:9
    #5 0x52f81b in clipp::group::child_t<clipp::parameter, clipp::group>::data::data() .../clipp.h:2590:21
    #6 0x552079 in clipp::group::child_t<clipp::parameter, clipp::group>::child_t(clipp::group::child_t<clipp::parameter, clipp::group> const&) .../clipp.h:2437:38
    #7 0x551da8 in void std::__1::allocator<clipp::group::child_t<clipp::parameter, clipp::group> >::construct<clipp::group::child_t<clipp::parameter, clipp::group>, clipp::group::child_t<clipp::parameter, clipp::group>&>(clipp::group::child_t<clipp::parameter, clipp::group>*, clipp::group::child_t<clipp::parameter, clipp::group>&&&) /opt/llvm/stage/bin/../include/c++/v1/memory:1759:31
    #8 0x551da8 in void std::__1::allocator_traits<std::__1::allocator<clipp::group::child_t<clipp::parameter, clipp::group> > >::__construct<clipp::group::child_t<clipp::parameter, clipp::group>, clipp::group::child_t<clipp::parameter, clipp::group>&>(std::__1::integral_constant<bool, true>, std::__1::allocator<clipp::group::child_t<clipp::parameter, clipp::group> >&, clipp::group::child_t<clipp::parameter, clipp::group>*, clipp::group::child_t<clipp::parameter, clipp::group>&&&) /opt/llvm/stage/bin/../include/c++/v1/memory:1670
    #9 0x551da8 in void std::__1::allocator_traits<std::__1::allocator<clipp::group::child_t<clipp::parameter, clipp::group> > >::construct<clipp::group::child_t<clipp::parameter, clipp::group>, clipp::group::child_t<clipp::parameter, clipp::group>&>(std::__1::allocator<clipp::group::child_t<clipp::parameter, clipp::group> >&, clipp::group::child_t<clipp::parameter, clipp::group>*, clipp::group::child_t<clipp::parameter, clipp::group>&&&) /opt/llvm/stage/bin/../include/c++/v1/memory:1516
    #10 0x551da8 in void std::__1::allocator_traits<std::__1::allocator<clipp::group::child_t<clipp::parameter, clipp::group> > >::__construct_range_forward<clipp::group::child_t<clipp::parameter, clipp::group>*, clipp::group::child_t<clipp::parameter, clipp::group>*>(std::__1::allocator<clipp::group::child_t<clipp::parameter, clipp::group> >&, clipp::group::child_t<clipp::parameter, clipp::group>*, clipp::group::child_t<clipp::parameter, clipp::group>*, clipp::group::child_t<clipp::parameter, clipp::group>*&) /opt/llvm/stage/bin/../include/c++/v1/memory:1600
    #11 0x551da8 in std::__1::enable_if<__is_forward_iterator<clipp::group::child_t<clipp::parameter, clipp::group>*>::value, void>::type std::__1::vector<clipp::group::child_t<clipp::parameter, clipp::group>, std::__1::allocator<clipp::group::child_t<clipp::parameter, clipp::group> > >::__construct_at_end<clipp::group::child_t<clipp::parameter, clipp::group>*>(clipp::group::child_t<clipp::parameter, clipp::group>*, clipp::group::child_t<clipp::parameter, clipp::group>*, unsigned long) /opt/llvm/stage/bin/../include/c++/v1/vector:1030
    #12 0x54fea8 in std::__1::vector<clipp::group::child_t<clipp::parameter, clipp::group>, std::__1::allocator<clipp::group::child_t<clipp::parameter, clipp::group> > >::vector(std::__1::vector<clipp::group::child_t<clipp::parameter, clipp::group>, std::__1::allocator<clipp::group::child_t<clipp::parameter, clipp::group> > > const&) /opt/llvm/stage/bin/../include/c++/v1/vector:1213:9
    #13 0x520006 in clipp::group::group(clipp::group const&) .../clipp.h:2936:5
    #14 0x3c4b5c in clipp::operator,(clipp::parameter, clipp::parameter) .../clipp.h:3333:12
    #15 0x3c34a1 in main .../main.cpp:22:86
    #16 0x7fde62ff2f39 in __libc_start_main (/lib64/libc.so.6+0x20f39)

SUMMARY: AddressSanitizer: 128 byte(s) leaked in 2 allocation(s).

data probably shouldn't initialize param at all since the enclosing child_t doesn't have a default ctor anyway. This gets rid of the leak.

data() {} 

Repeatable required option fails to parse with one value

Right from the README quick reference, trying to use repeatable(required("-l") & value("lines", ids)) fails to parse correctly, where the parsing_result considers the required value as a missing argument:

#include "clipp.h"
#include <iostream>

int main(int argc, char *argv[])
{
    using namespace clipp;

    std::vector<size_t> ids;

    auto cli = repeatable(required("-l") & value("lines", ids));
    auto result = parse(argc, argv, cli);
    if (!result) {
        debug::print(std::cerr, result);
        return 1;
    }
    return 0;
}

Result of running:

$ ./a.out -l 0
#1 -l -> -l 
#2 0 -> lines 
lines    [missing after 0]

Note that more than one argument (e.g., ./a.out -l 0 -l 1) parses correctly.

Group of single parameter

Hi,

I am playing around this lib right now and found it is great.

I wanted to created a group of one prarameter:

cli = (
    "group 1" % (
        option("-a").set(a) % "option a"
    ),
    "group 2" % (
        option("-b").set(b) % "option b",
        option("-c").set(c) % "option c"
    )
)

but the documentation generated from this is incorrect:

-a    group1
group2
    -b option b
    -c option c

The problem is that when there is only one entry in the parenthesis, no group is created. I tried doing clipp::group(option("-a").set(a) % "option a") but it seems does not fix the problem.

Any idea?

Double move/forward

clipp/include/clipp.h

Lines 1827 to 1833 in 384d106

parameter(arg_string str, Strings&&... strs):
flags_{std::move(str), std::forward<Strings>(strs)...},
matcher_{predicate_adapter{match::none}},
label_{}, required_{false}
{
flags(std::move(str), std::forward<Strings>(strs)...);
}

arg_string str and Strings&&... strs are moved/forwarded twice. Possible bug/typo?

CMakeLists.txt is not included in the release package

Hello:

The CMakeLists.txt file is not included in the release package. As a result, it is not possible to use the tagged code with CMake FetchContent. Yes, using the latest master is possible, but it would be nice to use the release versions also.

Infinite loop in possibly wrong clipp descriptor

Hello, when build the following code I'm getting stuck into a infinite loop up until a SIGSEV crash.

#include "clipp.h"
#include <iostream>

enum class Validation
{
	SynthesisVoting,
	Help
};


int main(int argc, char* argv[])
{

	Validation validation_action = Validation::Help;

	std::string source_image_path;
	std::string target_image_path;
	std::string result_path;

	bool source_image_to_be_loaded = false;
	bool target_image_to_be_loaded = false;

	{
		auto cli = (
				(clipp::command("voting")
			 	 .set(validation_action, Validation::SynthesisVoting),
			 	 clipp::value("source image", &source_image_path).set(source_image_to_be_loaded, true),
			 	 clipp::value("target image", &target_image_path).set(target_image_to_be_loaded, true),
			 	 clipp::value("result path",  &result_path)) |
				(clipp::command("help", "-?", "--help", "-h").set(validation_action, Validation::Help))
				);
		auto parse_result = clipp::parse(argc, argv, cli);

		if (!parse_result)
		{
			clipp::debug::print(std::cerr, parse_result);
			return EXIT_FAILURE;
		}
		if (validation_action == Validation::Help)
		{
			auto format = clipp::doc_formatting{} .start_column(4);
			std::cerr << clipp::make_man_page(cli, "validate", format);
			return EXIT_SUCCESS;
		}
	}

	return 0;
}

Here is a sample of the bottom of the call stack in GDB. I'm not including the remaing parts as I think you must have got it from the first three.

(gdb) f 261909
#261909 0x00000000004024dd in main (argc=1, argv=0x7fffffffdf78) at ./clipp_rec_loop.cpp:29
29                                       clipp::value("result path",  &result_path)) |
(gdb) f 261908
#261908 0x0000000000413a75 in clipp::value<std::string*>(std::string const&, std::string*&&) (label="result path", 
    tgts#0=<unknown type in /home/nbrack/Projects/minimal_failing_product/clipp_rec_loop, CU 0x0, DIE 0x490ff>) at ./clipp.h:2091
2091            .required(true).blocking(true).repeatable(false);
(gdb) f 261907
#261907 0x000000000041b250 in clipp::detail::action_provider<clipp::parameter>::target<std::string*>(std::string*&&) (this=0x7fffffffd2b0, 
    t=<unknown type in /home/nbrack/Projects/minimal_failing_product/clipp_rec_loop, CU 0x0, DIE 0x42305>) at ./clipp.h:1308
1308            target(std::forward<T>(t));
(gdb) f 261906
#261906 0x000000000041b250 in clipp::detail::action_provider<clipp::parameter>::target<std::string*>(std::string*&&) (this=0x7fffffffd2b0, 
    t=<unknown type in /home/nbrack/Projects/minimal_failing_product/clipp_rec_loop, CU 0x0, DIE 0x42305>) at ./clipp.h:1308
1308            target(std::forward<T>(t));
(gdb) f 261905
#261905 0x000000000041b250 in clipp::detail::action_provider<clipp::parameter>::target<std::string*>(std::string*&&) (this=0x7fffffffd2b0, 
    t=<unknown type in /home/nbrack/Projects/minimal_failing_product/clipp_rec_loop, CU 0x0, DIE 0x42305>) at ./clipp.h:1308
1308            target(std::forward<T>(t));

I'm building with g++7 under CentOS Linux 7:

$ /opt/rh/devtoolset-7/root/bin/g++ --version
g++ (GCC) 7.2.1 20170829 (Red Hat 7.2.1-1)
Copyright (C) 2017 Free Software Foundation, Inc.
This is free software; see the source for copying conditions.  There is NO
warranty; not even for MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.

If you need any more information, please ask.

Weird problem with order of elements in the cli spec

This program gives:

test(false)=Failed
test(true)=OK

With HEAD on Linux Ubuntu with gcc 9.2.1 and clang 9.0.0.

I don't think the changein order should matter.

#include "clipp.h"

#include
#include

using namespace clipp;
using namespace std;

enum class Mode
{
Unknown,
Error,
Help,
DeviceList,
DeviceInfo
};

bool test_(int argc, char* argv[], bool order)
{
Mode m = Mode::Unknown;
string target;

auto helpMode = (
    command("help").set(m, Mode::Help)
);

auto deviceListMode = (
    command("devicelist")
        .set(m, Mode::DeviceList)
        .doc("List available hardware devices")
);

auto deviceInfoMode = (
    command("deviceinfo")
        .set(m, Mode::DeviceInfo)
        .doc("Display information about a hardware device")
        ,
    value("device", target)
        .doc("Device name to provide information for")
);

auto cli = order ? (
        helpMode | deviceInfoMode | deviceListMode
    ) : (
        helpMode | deviceListMode | deviceInfoMode
    );

return (bool)clipp::parse(argc, argv, cli);

}

const char* test(bool order)
{
static char* argv[] = {
(char*)"app", (char*)"deviceinfo", (char*)"foo", nullptr
};

auto rc = test_(3, argv, order);

return rc ? "OK" : "Failed";

}

int main(int, char*[])
{
std::cout << "test(false)=" << test(false) << std::endl;
std::cout << "test(true)=" << test(true) << std::endl;
return 0;
}
<<<<<<<
bug.cpp.gz

unused imports

For a lot of my projects I like to define log() so I can type as little as possible. This might be a bad idea, but nonetheless.. When I take it out #include <cmath> it compiles just fine (g++ 7.x), and the less imports the better IMO.

Edit: actually cloned and ran the tests.py file. And there a lot of errors popped up for missing imports. But for some, it seems the import is only needed by the test cpp files. In the case above, values_conversion_test.cpp failed, but putting the include into the test.cpp and it passed.

how to ignore arguments, like --undefok in glog

glog --undefok: https://gflags.github.io/gflags/

It is a fatal error to specify a flag on the commandline that has not been DEFINED somewhere in the executable. If you need that functionality for some reason -- say you want to use the same set of flags for several executables, but not all of them DEFINE every flag in your list -- you can specify --undefok to suppress the error.

Parse failing on otherwise valid arguments

	auto compress = (
		command("compress").set(sc, subcommand::COMPRESS)
		| command("decompress").set(sc, subcommand::DECOMPRESS)
	) % "(de)compress stdin to stdout";

	auto convert = (
		command("convert").set(sc, subcommand::CONVERT),
		value("input.vox", in_path),
		value("output.tox", out_path)
	) % "convert a .VOX scene to .TOX asset";

	auto info = (
		command("info").set(sc, subcommand::INFO),
		value("input.tox", in_path)
	) % "show information about a .TOX asset";

	auto verbose_flag = option("-v", "--verbose").set(verbose, true)
		% "enable verbose output";
	auto help_flag = option("--help").set(help, true)
		% "show this help message";

	auto cli = one_of(
		(
			verbose_flag,
			one_of(
				compress,
				convert,
				info
			)
		),
		help_flag
	);

With the above, I get the following manpage output (looks 100% correct):

SYNOPSIS
        tox [-v] (compress|decompress)
        tox [-v] convert <input.vox> <output.tox>
        tox [-v] info <input.tox>
        tox --help

OPTIONS
        -v, --verbose
                    enable verbose output

        compress|decompress
                    (de)compress stdin to stdout

        convert <input.vox> <output.tox>
                    convert a .VOX scene to .TOX asset

        info <input.tox>
                    show information about a .TOX asset

        --help      show this help message

However, only compress and decompress work; convert and info cause clipp to fail at the parsing step.

Seems like a bug - is that the case? Or is there some sort of workaround for this?

cmake version

cmake version 3.8 is too high for nothing
on debian, standard version is 3.7.?
I don't know for other distrib but I guess 3.8 is too high for most of them
perhaps someone could details the version of cmake on mainstream distrib, to help choosing a better version level :)

thx for that clever lib!

How to specify number of times a value can be repeated

What is the best way to achieve a command line like the following:

exe fetch <origin> <origin> <origin>

The number of origins should be fixed to a given number.

The only way I could come up with after reading the documentation would be something like this:

std::string origin1, origin2, origin3;
command("fetch"), value("origin", origin1), value("origin", origin2), value("origin", origin3)

Is there any better way as this feels a bit clumsy? I was expecting something along these lines:

std::vector<std::string> origins;
command("fetch"), values(3, "origin", origins)

Repeatable required option parses incorrectly.

From the README, I expected

exe (-l <lines>)...      repeatable( required("-l") & value("lines", ids) )

to parse exe -l 0 -l 1 -l 2 where ids would contain 0, 1, and 2. However, it tries to parse the additional -l's, and converts them to 0 values (i.e., ids contains 0, 0, 1, 0, 2).

Also, exe -l 0 1 2 is accepted (which I think should be rejected) and ids contains 0, 1, 2.

Use "=" as an optional separator of a long option and its value

Is it possible to use = as an option-value separator like in boost::program_options and Python argparse?

Current behavior (AFAIK):

# (option("--longopt") & value("arg", target))
Command           target
--longopt foo  => "foo"
--longoptfoo   => "foo"
--longopt=foo  => "=foo"

# (option("--longopt=") & value("arg", target))
Command           target
--longopt foo  => Not match
--longoptfoo   => Not match
--longopt=foo  => "foo"

Proposed behavior:

Command           target
--longopt foo  => "foo"
--longopt=foo  => "foo"

Thanks.

parse(): no known conversion from 'clipp::parameter' to 'const clipp::group' for 'cli'

I cannot compile a simple example. Am I doing anything wrong?

clipp 2c32b2f
clang version 8.0.1 (tags/RELEASE_801/final 366581)
g++ (SUSE Linux) 9.1.1 20190723 [gcc-9-branch revision 273734]

% cat main.cpp
#include <iostream>
#include "clipp.h"
int main(int argc, char** argv)
{
        using namespace clipp;
        bool help = false;
        auto cli = (option("-h").set(help));
        parse(argc, argv, cli);
        return 0;
}
% clang++ main.cpp
main.cpp:8:2: error: no matching function for call to 'parse'
        parse(argc, argv, cli);
        ^~~~~
./clipp.h:5325:1: note: candidate function not viable: no known conversion from 'clipp::parameter' to
      'const clipp::group' for 3rd argument
parse(const int argc, char* argv[], const group& cli, arg_index offset = 1)
^
./clipp.h:5313:1: note: candidate template ignored: deduced conflicting types for parameter 'InputIterator'
      ('int' vs. 'char **')
parse(InputIterator first, InputIterator last, const group& cli)
^
./clipp.h:5281:1: note: candidate function not viable: requires 2 arguments, but 3 were provided
parse(arg_list args, const group& cli)
^
./clipp.h:5294:1: note: candidate function not viable: requires 2 arguments, but 3 were provided
parse(std::initializer_list<const char*> arglist, const group& cli)
^
1 error generated.
% g++ main.cpp                                                                        :(
main.cpp: In function โ€˜int main(int, char**)โ€™:
main.cpp:8:23: error: no matching function for call to โ€˜parse(int&, char**&, clipp::parameter&)โ€™
    8 |  parse(argc, argv, cli);
      |                       ^
In file included from main.cpp:2:
clipp.h:5281:1: note: candidate: โ€˜clipp::parsing_result clipp::parse(clipp::arg_list, const clipp::group&)โ€™
 5281 | parse(arg_list args, const group& cli)
      | ^~~~~
clipp.h:5281:1: note:   candidate expects 2 arguments, 3 provided
clipp.h:5294:1: note: candidate: โ€˜clipp::parsing_result clipp::parse(std::initializer_list<const char*>, const clipp::group&)โ€™
 5294 | parse(std::initializer_list<const char*> arglist, const group& cli)
      | ^~~~~
clipp.h:5294:1: note:   candidate expects 2 arguments, 3 provided
clipp.h:5313:1: note: candidate: โ€˜template<class InputIterator> clipp::parsing_result clipp::parse(InputIterator, InputIterator, const clipp::group&)โ€™
 5313 | parse(InputIterator first, InputIterator last, const group& cli)
      | ^~~~~
clipp.h:5313:1: note:   template argument deduction/substitution failed:
main.cpp:8:23: note:   deduced conflicting types for parameter โ€˜InputIteratorโ€™ (โ€˜intโ€™ and โ€˜char**โ€™)
    8 |  parse(argc, argv, cli);
      |                       ^
In file included from main.cpp:2:
clipp.h:5325:1: note: candidate: โ€˜clipp::parsing_result clipp::parse(int, char**, const clipp::group&, clipp::arg_index)โ€™
 5325 | parse(const int argc, char* argv[], const group& cli, arg_index offset = 1)
      | ^~~~~
clipp.h:5325:50: note:   no known conversion for argument 3 from โ€˜clipp::parameterโ€™ to โ€˜const clipp::group&โ€™
 5325 | parse(const int argc, char* argv[], const group& cli, arg_index offset = 1)
      |                                     ~~~~~~~~~~~~~^~~

Compiler error when compiling with MSVC & C++20

The std::result_of type trait was removed with C++20 in favor of std::invoke_result. With on of MSVCs latest updates they did this removal (if set to compile as C++20), leading to compile errors of clipp because of it's use in e.g.

clipp/include/clipp.h

Lines 160 to 163 in 2c32b2f

check_is_callable(int) -> decltype(
std::declval<Fn>()(std::declval<Args>()...),
std::integral_constant<bool,
std::is_same<Ret,typename std::result_of<Fn(Args...)>::type>::value>{} );

To reproduce you can run the testsuite with /std:c++latest when using at least MSVC16.7.3 (did not check if the change occured with this or an earlier version)

Parameter flagged as missing even though it appears

Hi. I get the following behavior using the latest version of clipp. The issue seems to be that the required options are only properly parsed if they appear in a certain order. Consider the following small test program:

#include "clipp.h"
#include <iostream>

int main(int argc, char* argv[]) {
  using namespace clipp;
  enum class mode {help, index};
  mode selected = mode::help;
  std::string odir;
  std::string gfaFile;
  std::string rfile;
  int k{31};
  bool isSparse{false};
  int extensionSize{4};

  auto indexMode = (
                    command("index").set(selected, mode::index),
                    (required("-o", "--output") & value("output dir", odir)) % "directory where index is written",
                    (required("-g", "--gfa") & value("gfa file",gfaFile)) % "path to the GFA file",
                    (required("-r", "--ref") & value("ref file",rfile)) % "path to the reference fasta file",
                    (option("-k", "--klen") & value("kmer length",k))  % "length of the k-mer with which the dBG was built (default = 31)",
                    (option("-s", "--sparse").set(isSparse, true)) % "use the sparse pufferfish index (less space, but slower lookup)",
                    (option("-e", "--extension") & value("extension size",extensionSize)) % "length of the extension to store in the sparse index (default = 4)"
                    );

auto cli = (
              (indexMode | command("help").set(selected,mode::help) ),
              option("-v", "--version").call([]{std::cout << "version 0.1.0\n\n";}).doc("show version"));

  decltype(parse(argc, argv, cli)) res;
  try {
    res = parse(argc, argv, cli);
  } catch (std::exception& e) {
    std::cout << "\n\nParsing command line failed with exception: " << e.what() << "\n";
    std::cout << "\n\n";
    std::cout << make_man_page(cli, "x");
    return 1;
  }


  if(res) {
    switch(selected) {
    case mode::index: std::cout << "successful; call index\n";  break;
    case mode::help: std::cout << make_man_page(cli, "x"); break;
    }
  } else {
    auto b = res.begin();
    auto e = res.end();
    std::cerr << "any blocked " << res.any_blocked() << "\n";
    std::cerr << "any conflict " << res.any_conflict() << "\n";
    std::cerr << "any bad repeat " << res.any_bad_repeat() << "\n";
    std::cerr << "any error " << res.any_error() << "\n";
    for( auto& m : res.missing() ) {
      std::cerr << "missing " << m.param()->label() << "\n";
    }
    if (std::distance(b,e) > 0) {
      if (b->arg() == "index") {
        std::cout << make_man_page(indexMode, "x");
      } else {
        std::cout << "There is no command \"" << b->arg() << "\"\n";
        std::cout << usage_lines(cli, "x") << '\n';
        return 1;
      }
    } else {
      std::cout << usage_lines(cli, "x") << '\n';
      return 1;
    }
  }
return 0;
}

When I pass all of the required options as follows, the parser fails. All of the options do look to have the appropriate associated value, but the "gfa_file" option is marked as missing even though it is clearly present.

rob@server:~/main/build$ ./main index -g valid_file.gfa -o out_dir -r rfile 
any blocked 0
any conflict 0
any bad repeat 0
any error 1
missing
missing gfa_file
SYNOPSIS
        main index -o <output_dir> -g <gfa_file> -r <ref_file> [-k <kmer_length>] [-s] [-e <extension_size>]

OPTIONS
        -o, --output <output_dir>
                    directory where index is written

        -g, --gfa <gfa_file>
                    path to the GFA file

        -r, --ref <ref_file>
                    path to the reference fasta file

        -k, --klen <kmer_length>
                    length of the k-mer with which the dBG was built (default = 31)

        -s, --sparse
                    use the sparse pufferfish index (less space, but slower lookup)

        -e, --extension <extension_size>
                    length of the extension to store in the sparse index (default = 4)
rob@server:~/main/build$

Strangely, if I simply change the order of the named options, everything works as expected:

rob@server:~/main/build$ ./main index -o out_dir -g valid_file.gfa -r rfile 
successful; call index
rob@server:~/main/build$

Presumably, both of these invocations should be equivalent. Any idea why the former is failing?

Thanks!
Rob

[Edit: In case it's helpful, I found the function you have to print the parsing results in the debug namespace. Here is what it gives with the example that fails]

#1 index -> index
#2 -g -> -g
#3 /mnt/scratch7/pufferfish_data/k31_n_gencode.v25.pc_transcripts_fixed.pufferized.gfa -> gfa_file
#4 -o -> -o
#5 test_new_parser -> output_dir
#6 -r -> -r
#7 /mnt/scratch7/gencode.v25.pc_transcripts_fixed.fa -> ref_file
-g       [missing after 4]
gfa_file         [missing after 4]

Matching flags with similar prefix

Let's assume we have the following interface:

  cli = (option("--flag"),
         option("--flag-extra", "--flag-extra=") & value)

Passing arguments --flag --flag-extra=foo will fail, because --flag-extra= will first partially match with --flag in try_match_joined_sequence โ€“ prefix_match. One option to fix this would be to reorder groups, or perhaps when the matched string is not exactly the same length we could continue iterating through arguments to find the longest match. What would be a good way to fix it?

Parse error for float/double with leading dot

I could have misunderstood how to use clipp properly but I have found using multiple optional numeric values with leading dot leads to incorrect parsing, for example:

double a, b;
auto FOO = (command("foo").set(selected,mode::FOO),
            opt_value("a", a) & opt_value("b", b));

gives the correct values when called as "> foo 0.1 0.2", but when called as "> foo .1 .2" this results in

a = 0
b = 1.2

Wide string support?

Any plans for this? I took a look at the code, it seems the majority of the library is written with a type alias so that is nice. I think a #define CLIPP_STR_TYPE std::wstring then using arg_str = CLIPP_STR_TYPE would be an easy solution, does this seem difficult?

Timing example not working with flags

The timing.cpp example is broken when passing flags:

This works fine:

./timing -n 10 exename -- some_string

This gives a parse error:

./timing -n 10 exename -- --another-flag -a

I would like to have functionality similar to this so I can pass the arguments after -- to another executable like in this example.

(feature request) Automatically print default values for options

From docs it looks like the task of displaying a default value for an option is fully on the shoulders of the deveolper. i.e. one should manually write option("-s") & integer("filesize", size).doc("max size of the file (default: " + to_string(size) + ")").

It would be nice if the library would provide method to easily display defaults in some sort of "standard way" if it's needed, maybe something like option() & integer.doc().print_default()

Error while compiling with vs2015 default compiler

in clipp.h:

Severity Code Description Project File Line Suppression State
Error C3256 'npos': variable use does not produce a constant expression
line: 101

Severity Code Description Project File Line Suppression State
Error C3256 'npos': variable use does not produce a constant expression
line: 122

Severity Code Description Project File Line Suppression State
Error C2066 cast to function type is illegal ArgumentParser
line: 174

How to repeat repeatable values.

How do i correctly write:

std::string cmdFnTypeDef;
std::vector<std::string> cmdFnArgs;
auto cli = (
	clipp::repeatable(clipp::option("-f", "--func") & clipp::value("typedef", cmdFnTypeDef) & clipp::values("args", cmdFnArgs))
);

i want it to look like:
-f blah(a1,a2,a3,a4) 0 1 2 3
-f blah2(a1,a2) 0 1
-f blah2(a0) 4

but as is the first -f is only matched and all the rest of the args end up in the vector. I could duplicate the group but then i'd have hard cap on the count of -f's i support.

Don't duplicate documentation for re-used options

Is it possible to dedup args that are listed multiple times in different commands? Here's an example:

bool val1, val2, val3;
auto arg1 = required("--arg1").set(val1) % "doc for arg1";
auto arg2 = required("--arg2").set(val2) % "doc for arg2";
auto arg3 = required("--arg3").set(val3) % "doc for arg3";

auto cli = (command("cmd1"), arg1, arg2) | (command("cmd2"), arg2, arg3) | (command("cmd3"), arg1, arg3);
SYNOPSIS
        prog cmd1 --arg1 --arg2
        prog cmd2 --arg2 --arg3
        prog cmd3 --arg1 --arg3

OPTIONS
        --arg1      doc for arg1
        --arg2      doc for arg2
        --arg2      doc for arg2
        --arg3      doc for arg3
        --arg1      doc for arg1
        --arg3      doc for arg3

Wrong Documentation line for Option

Option Definition:

option("-b") & value("size=1", bs) % "Maximum b size",

Documentation formatting:
<size=1> Maximum b size
Expected:
-b <size=1> Maximum b size

Issue with optional flag

See this example:

int main(int argc, char **argv) {
	bool optionSpecified = false;
	std::string optionvalue;

	auto helpCmd = command("help");
	auto cmd1 = command("cmd1");
	auto cmd2 = command("cmd2");
	auto cmd3 = command("cmd3");

	auto optionFlag = (option("-o").set(optionSpecified, true) & value("optionvalue").set(optionvalue));

	auto cli = (helpCmd | cmd1 | ((cmd2 | cmd3), optionFlag));

	if (parse(argc, argv, cli)) {
		tfm::printf("Option: %s Value: %s\n", optionSpecified, optionvalue);
	} else {
		tfm::printf("Parse failed!\n");
		std::cerr << usage_lines(cli, argv[0]) << std::endl;
	}
	return 0;
}

This should accept the "help", "cmd1", "cmd2" or "cmd3" commands, but allow the "-o value" flag to be specified optionally on the "cmd2" or "cmd3" commands. However, while the usage_lines output seems to be correct, the parse fails if "cmd2" or "cmd3" is specified at all, with or without the "-o value" flag.

(tfm::printf is from https://github.com/c42f/tinyformat)

Am I doing something stupid or is this a bug?

help is never sets to true and required makes all invalid

In the example I expect that in case of args1 the variable _help will be set to true. But its always false.

If I set the address as clipp::required then all except args1 is considered valid. But even in case of args1, _help is set to false.

#include "clipp.h"
#include <string>
#include <cstdint>
#include <iostream>

template <typename It>
void parse(It first, It last){
    std::string _target;
    std::uint32_t _batch = 0, _wait = 0;
    bool _help = false;
    
    auto command_options = (
        clipp::value("address", _target) % "target address",
        clipp::option("-b", "--batch") & clipp::value("size", _batch)   % "number of requests in one batch",
        clipp::option("-w", "--wait")  & clipp::value("seconds", _wait) % "number of seconds to wait after each batch of requests completes"
    );
    auto options = (
        command_options | clipp::command("-h", "--help").set(_help, false) % "show this help message"
    );
    auto res = clipp::parse(first, last, options);
    if(res.any_error() || _help){
        std::cout << "help: " << _help << std::endl;
        clipp::man_page man = clipp::make_man_page(options, "cmd");
        std::cout << man; 
    }else{
        std::cout << "cmd" << " " << _target << " -b" << _batch << " -w" << _wait << std::endl;
    }
    std::cout << "-------------------" << std::endl;
    std::cout << "_target: " << _target << " _batch: " << _batch << " _wait: " << _wait << " _help: " << _help << std::endl;
    std::cout << "-------------------" << std::endl;
}

int main(){
    std::vector<std::string> args1 = {"-h"};
    parse(args1.begin(), args1.end());
    
    std::vector<std::string> args2 = {"target", "-b", "20", "-w", "4"};
    parse(args2.begin(), args2.end());
    
    std::vector<std::string> args3 = {"target"};
    parse(args3.begin(), args3.end());
    
    std::vector<std::string> args4 = {};
    parse(args4.begin(), args4.end());
    
    return 0;
}

Multiple commands problem - use one_of instead of |

Hi,

some time ago I stumbled upon a problem with CLI, which consisted of three or more commands. I have found a work-around and I am posting it here hoping it will help others struggling with the same.

The smallest non-working example is:

    auto cli = (
        command("a").set(m.a) |
        command("b").set(m.b) |
        (
            command("c").set(m.c) & value("i", m.i)
        )
    );

where clipp would parse a, b, but not c 123.

It turned out that I can make clipp do what I want if instead of specifying the alternative of commands with | I'd use clipp::one_of.
The example above becomes:

    auto cli = one_of(
        command("a").set(m.a),
        command("b").set(m.b),
        command("c").set(m.c) & value("i", m.i)
    );

The docs, unless I am missing something, specify that one_of is the same as |, yet they produce different usage lines. The first one gives

((a|b) | (c <i>))

while the second one

(a | b | (c <i>))

Parse bug with option and value within command

Using the following:

auto dither =
(
    command("dither").set(run_mode, mode::dither),
    option("--palette") & value("palette", palette),
    option("--hicolor").set(hicolor, true),
    option("--error-diffusion").set(error_diffusion, true),
    value("image").set(in)
);

Help string looks OK:

tool dither [--palette <palette>] [--hicolor] [--error-diffusion] <image>

However nothing gets accepted; I tried all the following:

tool dither
tool dither image.jpeg
tool dither --palette x image.jpeg
tool dither image.jpeg --palette x

What does work:

  • remove the --palette option
  • move the value("image") part before the option parts; but I want to be able to specify options before the final argument in the commandline

Parameter Verification

Hi,

I had q quick look through the docs but apart from generating a "filter", i.e "integer", I didn't find any way to define the required type of options.

For example, if I specify option "n" as an integer, I'd ideally like an automated error message during parsing if the user specifies something that can't be parsed as an integer. I'd also like the integer requirement to be stated in the auto-generated documentation.

Is this possible at the moment?

Case insensitive matching

Is it possible for the parameter matching to be case insensitive ?
For example, option("-ext") would parse correctly with "-Ext", "-ext", "-EXT", "-eXt", "-ExT"

Wrong usage line for (option & value | option)

int main(int argc, char* argv[]) {
    using namespace clipp;
    int i = 0;
    auto cli = (option("-b") & integer("size", i)) | option("-a");
    std::cout << usage_lines(cli, argv[0]) << '\n';
}

observed: ./usage ([-b <size>] | -a)
expected: ./usage [-b <size>] | -a]

Prefix free check seems broken

The prefix free check doesn't seem to work (i.e., enabling it on various programs from the examples directory leads to failed assertions). I'm not entirely sure why this happens, but at least one issue seems to be that value options, when encountered in the prefix_free check are parsed as the empty string, and therefore appear as a prefix of everything else.

any_other() records the wrong argument

auto cli = (
	option("--help").set(show_help)
		% "Show this help message",
	(option("-J", "--threads") & value("num_threads", num_threads))
		% "The number of threads to use (default 0, meaning 'auto')",
	value("db_dir").set(dbdir).required(true).blocking(true)
		% "The database directory location",
	any_other(invalid_params)
);

I'm trying to use any_other to error out on unknown flags. In the above, if I run with ./my_prog --askdfjaksjd some_directory, invalid_params receives some_directory instead of the garbage flag. Is there a way to get the garbage flag in a list somewhere to give better error messages?

Is there a way to define a "default" command (if none is given)?

I am using the commands semantics to implement several mandatory choices, but I would like to have a default command choice, when the user does not include the command on the command line.

So far I have found a workaround which parses the arguments twice. First with the (mandatory) commands and then without them. If the second parsing succeeds I assume the command was not given and supply the default one. I.e. like this:

clipp::group cli_fallback = (
    (option("-a"),
    (option("-b"),
    (option("-c")
    );

clipp::group cli = (
    (command("cmd1") \
        | command("cmd2") \
        | command("cmd3") \
        | command("cmd4")),
    cli_fallback);

if (parse(argc, argv, cli)) {
        res = run(...);
} else {
    if (parse(argc, argv, cli_fallback)) {
        res = run(default_cmd, ...);
    } else {
        std::cout << "Usage:\n" << usage_lines(...) << '\n';
    }
}

This works, but seems a bit heavy.

option string value with special char

Could you help me to parse an option with a string like this:

command --arg "abc: - (cde, 0)"

is it possible to use delimiter, like ", to specify the start and the end of a string value?

documentation formatting wrong for groups with only value options

Code:

auto cli = (option("--a") & value("A") % "help a", option("-b") & value("B") % "help b") % "group 1";
std::cout << make_man_page(cli, "test");

Expected:

SYNOPSIS
        test [--a <A>] [-b <B>]

OPTIONS
        group 1
            <A>     help a
            <B>     help b

Actual:

SYNOPSIS
        test [--a <A>] [-b <B>]

OPTIONS
        [--a <A>] [-b <B>]
                    group 1

This works fine:

auto cli = (option("--a") & value("A") % "help a", option("-b") % "help b") % "group 1";
std::cout << make_man_page(cli, "test");
SYNOPSIS
        test [--a <A>] [-b]

OPTIONS
        group 1
            <A>     help a
            -b      help b

live example: https://wandbox.org/permlink/m9OeLGfP7ZDiOjD1

Included Windows.h causes std::min bug

Problem

When using the library on Windows, with the header Windows.h included, I get an error in the places that std::min is used. Wherever min is used, the macro expands to:

(a < b) ? a : b

This is because Microsoft defines min and max in its headers.

Solution

To fix this you must define NOMINMAX before including Windows.h.

#define NOMINMAX 
#include <Windows.h>
#include <clipp.h>

Proposed Fix

I would recommend performing a check and if on Windows, defining NOMINMAX.

Problem with option_value having the same prefix as a command

I have this code

auto run_spec = "run options" % (
	command("run").set(args.selected_mode, mode::run),
	value("input file or folder", args.target) % "Path to a file",
	(option("--test_spec") & value("wildcard pattern", args.test_spec)) % "Run specific tests"
);
auto cli = ( run_spec | command("help").set(args.show_help) );
if (!parse(argc, argv, cli)) {
	std::cout << make_man_page(cli, "testo") << std::endl;
	return -1;
}

Running like this

program run some_file --test_spec something is totally fine, but

Running like this

program run some_file --test_spec run_something gives me usage lines

how to define single repeatable joinable option

I am trying to figure out, how to implement an option for a "verbosity" level -v. The idea is that it should be repeatable and joinable, i.e. -v should set it to 1, -vvv should set it to 3.

I have found an example for repeatable and joinable options:

auto cli = joinable(
        repeatable(
            option("a")([&]{++as;}) |
            option("b")([&]{++bs;})
        )
    );

which works fine. But when I try to define my option:

auto verb = 0;
bool d = false;
auto cli = (<...>,
        joinable(repeatable(option("-v").call(increment(verb)) % verb_help))
//        joinable(repeatable(option("-v").call(increment(verb)) % verb_help, option("-d").set(d)))
        );

the parsing fails on anything different from -v. What I found that it starts working as expected when I add "dummy" option -d to the joinable and repeatable list.

Recommend Projects

  • React photo React

    A declarative, efficient, and flexible JavaScript library for building user interfaces.

  • Vue.js photo Vue.js

    ๐Ÿ–– Vue.js is a progressive, incrementally-adoptable JavaScript framework for building UI on the web.

  • Typescript photo Typescript

    TypeScript is a superset of JavaScript that compiles to clean JavaScript output.

  • TensorFlow photo TensorFlow

    An Open Source Machine Learning Framework for Everyone

  • Django photo Django

    The Web framework for perfectionists with deadlines.

  • D3 photo D3

    Bring data to life with SVG, Canvas and HTML. ๐Ÿ“Š๐Ÿ“ˆ๐ŸŽ‰

Recommend Topics

  • javascript

    JavaScript (JS) is a lightweight interpreted programming language with first-class functions.

  • web

    Some thing interesting about web. New door for the world.

  • server

    A server is a program made to process requests and deliver data to clients.

  • Machine learning

    Machine learning is a way of modeling and interpreting data that allows a piece of software to respond intelligently.

  • Game

    Some thing interesting about game, make everyone happy.

Recommend Org

  • Facebook photo Facebook

    We are working to build community through open source technology. NB: members must have two-factor auth.

  • Microsoft photo Microsoft

    Open source projects and samples from Microsoft.

  • Google photo Google

    Google โค๏ธ Open Source for everyone.

  • D3 photo D3

    Data-Driven Documents codes.