{"action":"created","comment":{"url":"https://api.github.com/repos/JakobOvrum/Diggler/pulls/comments/52893430","id":52893430,"diff_hunk":"@@ -422,16 +424,129 @@ abstract class CommandSet(T) : ICommandSet\n \n \tfinal void registerCommands(T cmdSet)\n \t{\n-\t\tforeach(memberName; __traits(derivedMembers, T))\n+\t\timport diggler.std_backport.meta : staticSort;\n+\t\timport std.meta : Filter;\n+\t\timport std.algorithm : canFind, commonPrefix, joiner, splitter;\n+\t\timport std.range : take, walkLength;\n+\t\timport std.string : startsWith, endsWith;\n+\t\timport std.conv : to;\n+\n+\t\tstatic if(__traits(hasMember, T, \"subCommandSeparator\"))\n \t\t{\n-\t\t\tstatic if(\n-\t\t\t\tmemberName != \"__ctor\" &&\n-\t\t\t\tmemberName != \"__dtor\" &&\n-\t\t\t\t__traits(compiles, __traits(getMember, T, memberName)) && // ahem...\n-\t\t\t\t__traits(getProtection, __traits(getMember, T, memberName)) == \"public\" &&\n-\t\t\t\tisSomeFunction!(mixin(\"T.\" ~ memberName)) &&\n-\t\t\t\t!__traits(isStaticFunction, mixin(\"T.\" ~ memberName)) &&\n-\t\t\t\t!hasAttribute!(mixin(\"T.\" ~ memberName), ignore))\n+\t\t\t//The separator is strictly a string: any other type will halt compilation\n+\t\t\tstatic assert(is(typeof(T.subCommandSeparator) == string), \"subCommandSeparator must be of type string, not \" ~ typeof(T.subCommandSeparator).stringof);\n+\t\t\tenum cmdSep = T.subCommandSeparator;\n+\t\t}\n+\t\telse\n+\t\t{\n+\t\t\tenum cmdSep = \"_\";\n+\t\t}\n+\n+\t\t//Determines whether a given symbol name is a valid command\n+\t\ttemplate isCommand(string symbol){\n+\t\t\tstatic if(__traits(getProtection, __traits(getMember, T, symbol)) != \"public\")\n+\t\t\t\tenum isCommand = false;\n+\t\t\telse\n+\t\t\t\tenum isCommand =\n+\t\t\t\t\tsymbol != \"__ctor\" &&\n+\t\t\t\t\tsymbol != \"__dtor\" &&\n+\t\t\t\t\t__traits(compiles, __traits(getMember, T, symbol)) && // ahem...\n+\t\t\t\t\tisSomeFunction!(__traits(getMember, T, symbol)) &&\n+\t\t\t\t\t!__traits(isStaticFunction, mixin(\"T.\" ~ symbol)) &&\n+\t\t\t\t\t!hasAttribute!(__traits(getMember, T, symbol), ignore);\n+\t\t}\n+\n+\t\t//Filters a sorted sequence by prefix, e.g. prefixFilter!(\"a\", \"a_b\", \"a_b_c\", \"d_e_f\") yields AliasSeq!(\"a_b\", \"a_b_c\")\n+\t\ttemplate prefixFilter(string prefix, sequence...)\n+\t\t{\n+\t\t\ttemplate prefixFilterImpl(size_t pos, seq...)\n+\t\t\t{\n+\t\t\t\tstatic if(pos >= seq.length || !seq[pos].startsWith(prefix))\n+\t\t\t\t\talias prefixFilterImpl = seq[0..pos];\n+\t\t\t\telse\n+\t\t\t\t\talias prefixFilterImpl = prefixFilterImpl!(pos+1, seq);\n+\t\t\t}\n+\n+\t\t\talias prefixFilter = prefixFilterImpl!(0, sequence);\n+\t\t}\n+\n+\t\t//Basic template to get the first part of a subcommand, e.g. rootCmd(\"a_b_c\") is \"a\"\n+\t\tenum rootCmd(string name) = name.splitter(cmdSep).front;\n+\t\t//Count how \"deep\" a subcommand is, e.g. depth(\"a_b_c\") is 3\n+\t\tenum depth(string name) = name.splitter(cmdSep).walkLength;\n+\n+\t\t//Sort all of the valid commands in T\n+\t\tenum sortingFunc(string left, string right) = left < right;\n+\t\talias sortedMembers = staticSort!(sortingFunc, Filter!(isCommand, __traits(derivedMembers, T)));\n+\n+\t\tforeach(index, memberName; sortedMembers)\n+\t\t{\n+\t\t\t//Captures a subcommand\n+\t\t\tstatic if(!memberName.startsWith(cmdSep) && //Starting with cmdSep will cause problems\n+\t\t\t\t  (memberName.canFind(cmdSep) || //A subcommand either contains cmdSep, or is next to another command with the same rootCmd\n+\t\t\t\t\t((index + 1) < sortedMembers.length &&\n+\t\t\t\t\t memberName.splitter(cmdSep).front == sortedMembers[index+1].splitter(cmdSep).front)))\n+\t\t\t{\n+\t\t\t\t//Ensure we havent already built a handler for this subcommand yet\n+\t\t\t\tstatic if(!(index && rootCmd!(sortedMembers[index-1]) == rootCmd!(memberName)))\n+\t\t\t\t{\n+\t\t\t\t\tCommand createHandler(string prefix, Members...)()","path":"src/diggler/command.d","position":88,"original_position":88,"commit_id":"8f388de820a1ca92f1db409350029e0a857ef26a","original_commit_id":"8f388de820a1ca92f1db409350029e0a857ef26a","user":{"login":"JakobOvrum","id":155856,"avatar_url":"https://avatars.githubusercontent.com/u/155856?v=3","gravatar_id":"","url":"https://api.github.com/users/JakobOvrum","html_url":"https://github.com/JakobOvrum","followers_url":"https://api.github.com/users/JakobOvrum/followers","following_url":"https://api.github.com/users/JakobOvrum/following{/other_user}","gists_url":"https://api.github.com/users/JakobOvrum/gists{/gist_id}","starred_url":"https://api.github.com/users/JakobOvrum/starred{/owner}{/repo}","subscriptions_url":"https://api.github.com/users/JakobOvrum/subscriptions","organizations_url":"https://api.github.com/users/JakobOvrum/orgs","repos_url":"https://api.github.com/users/JakobOvrum/repos","events_url":"https://api.github.com/users/JakobOvrum/events{/privacy}","received_events_url":"https://api.github.com/users/JakobOvrum/received_events","type":"User","site_admin":false},"body":"`members...`","created_at":"2016-02-15T12:32:43Z","updated_at":"2016-02-15T12:32:43Z","html_url":"https://github.com/JakobOvrum/Diggler/pull/16#discussion_r52893430","pull_request_url":"https://api.github.com/repos/JakobOvrum/Diggler/pulls/16","_links":{"self":{"href":"https://api.github.com/repos/JakobOvrum/Diggler/pulls/comments/52893430"},"html":{"href":"https://github.com/JakobOvrum/Diggler/pull/16#discussion_r52893430"},"pull_request":{"href":"https://api.github.com/repos/JakobOvrum/Diggler/pulls/16"}}},"pull_request":{"url":"https://api.github.com/repos/JakobOvrum/Diggler/pulls/16","id":55590107,"html_url":"https://github.com/JakobOvrum/Diggler/pull/16","diff_url":"https://github.com/JakobOvrum/Diggler/pull/16.diff","patch_url":"https://github.com/JakobOvrum/Diggler/pull/16.patch","issue_url":"https://api.github.com/repos/JakobOvrum/Diggler/issues/16","number":16,"state":"open","locked":false,"title":"Added support for sub-commands","user":{"login":"mitchgrout","id":11782978,"avatar_url":"https://avatars.githubusercontent.com/u/11782978?v=3","gravatar_id":"","url":"https://api.github.com/users/mitchgrout","html_url":"https://github.com/mitchgrout","followers_url":"https://api.github.com/users/mitchgrout/followers","following_url":"https://api.github.com/users/mitchgrout/following{/other_user}","gists_url":"https://api.github.com/users/mitchgrout/gists{/gist_id}","starred_url":"https://api.github.com/users/mitchgrout/starred{/owner}{/repo}","subscriptions_url":"https://api.github.com/users/mitchgrout/subscriptions","organizations_url":"https://api.github.com/users/mitchgrout/orgs","repos_url":"https://api.github.com/users/mitchgrout/repos","events_url":"https://api.github.com/users/mitchgrout/events{/privacy}","received_events_url":"https://api.github.com/users/mitchgrout/received_events","type":"User","site_admin":false},"body":"See issue #14 \r\nThis change allows the user to define sub-commands, helping to keep commands modular.\r\nUsage:\r\n```d\r\nclass TimeCommands : CommandSet!TimeCommands\r\n{\r\n    mixin CommandContext!();\r\n    string[string] tz;\r\n    void time(string s = null)\r\n    {\r\n        import std.datetime;\r\n        if(!s) time(user.nickName.idup);\r\n        else if(s !in tz) return;\r\n        else reply(\"Time for %s is %s\", s, Clock.currTime(TimeZone.getTimeZone(tz[s])));\r\n    }\r\n    void time_set(string zone)\r\n    {\r\n        tz[user.nickName.idup] = zone;\r\n    }\r\n    void time_unset()\r\n    {\r\n        tz.remove(user.nickName.idup);\r\n    }\r\n}\r\n```\r\nThese commands are callable via:\r\n - `!time`\r\n - `!time set [zone]`\r\n - `!time unset`\r\n\r\nand are listed under the `!help` command as only `time`","created_at":"2016-01-11T06:22:08Z","updated_at":"2016-02-15T12:32:43Z","closed_at":null,"merged_at":null,"merge_commit_sha":"7c6d5f854f6e46b8db9f930b5dae07142d2d172f","assignee":null,"milestone":null,"commits_url":"https://api.github.com/repos/JakobOvrum/Diggler/pulls/16/commits","review_comments_url":"https://api.github.com/repos/JakobOvrum/Diggler/pulls/16/comments","review_comment_url":"https://api.github.com/repos/JakobOvrum/Diggler/pulls/comments{/number}","comments_url":"https://api.github.com/repos/JakobOvrum/Diggler/issues/16/comments","statuses_url":"https://api.github.com/repos/JakobOvrum/Diggler/statuses/8f388de820a1ca92f1db409350029e0a857ef26a","head":{"label":"mitchgrout:sub_commands","ref":"sub_commands","sha":"8f388de820a1ca92f1db409350029e0a857ef26a","user":{"login":"mitchgrout","id":11782978,"avatar_url":"https://avatars.githubusercontent.com/u/11782978?v=3","gravatar_id":"","url":"https://api.github.com/users/mitchgrout","html_url":"https://github.com/mitchgrout","followers_url":"https://api.github.com/users/mitchgrout/followers","following_url":"https://api.github.com/users/mitchgrout/following{/other_user}","gists_url":"https://api.github.com/users/mitchgrout/gists{/gist_id}","starred_url":"https://api.github.com/users/mitchgrout/starred{/owner}{/repo}","subscriptions_url":"https://api.github.com/users/mitchgrout/subscriptions","organizations_url":"https://api.github.com/users/mitchgrout/orgs","repos_url":"https://api.github.com/users/mitchgrout/repos","events_url":"https://api.github.com/users/mitchgrout/events{/privacy}","received_events_url":"https://api.github.com/users/mitchgrout/received_events","type":"User","site_admin":false},"repo":{"id":48482011,"name":"Diggler","full_name":"mitchgrout/Diggler","owner":{"login":"mitchgrout","id":11782978,"avatar_url":"https://avatars.githubusercontent.com/u/11782978?v=3","gravatar_id":"","url":"https://api.github.com/users/mitchgrout","html_url":"https://github.com/mitchgrout","followers_url":"https://api.github.com/users/mitchgrout/followers","following_url":"https://api.github.com/users/mitchgrout/following{/other_user}","gists_url":"https://api.github.com/users/mitchgrout/gists{/gist_id}","starred_url":"https://api.github.com/users/mitchgrout/starred{/owner}{/repo}","subscriptions_url":"https://api.github.com/users/mitchgrout/subscriptions","organizations_url":"https://api.github.com/users/mitchgrout/orgs","repos_url":"https://api.github.com/users/mitchgrout/repos","events_url":"https://api.github.com/users/mitchgrout/events{/privacy}","received_events_url":"https://api.github.com/users/mitchgrout/received_events","type":"User","site_admin":false},"private":false,"html_url":"https://github.com/mitchgrout/Diggler","description":"IRC bot framework for the D programming language","fork":true,"url":"https://api.github.com/repos/mitchgrout/Diggler","forks_url":"https://api.github.com/repos/mitchgrout/Diggler/forks","keys_url":"https://api.github.com/repos/mitchgrout/Diggler/keys{/key_id}","collaborators_url":"https://api.github.com/repos/mitchgrout/Diggler/collaborators{/collaborator}","teams_url":"https://api.github.com/repos/mitchgrout/Diggler/teams","hooks_url":"https://api.github.com/repos/mitchgrout/Diggler/hooks","issue_events_url":"https://api.github.com/repos/mitchgrout/Diggler/issues/events{/number}","events_url":"https://api.github.com/repos/mitchgrout/Diggler/events","assignees_url":"https://api.github.com/repos/mitchgrout/Diggler/assignees{/user}","branches_url":"https://api.github.com/repos/mitchgrout/Diggler/branches{/branch}","tags_url":"https://api.github.com/repos/mitchgrout/Diggler/tags","blobs_url":"https://api.github.com/repos/mitchgrout/Diggler/git/blobs{/sha}","git_tags_url":"https://api.github.com/repos/mitchgrout/Diggler/git/tags{/sha}","git_refs_url":"https://api.github.com/repos/mitchgrout/Diggler/git/refs{/sha}","trees_url":"https://api.github.com/repos/mitchgrout/Diggler/git/trees{/sha}","statuses_url":"https://api.github.com/repos/mitchgrout/Diggler/statuses/{sha}","languages_url":"https://api.github.com/repos/mitchgrout/Diggler/languages","stargazers_url":"https://api.github.com/repos/mitchgrout/Diggler/stargazers","contributors_url":"https://api.github.com/repos/mitchgrout/Diggler/contributors","subscribers_url":"https://api.github.com/repos/mitchgrout/Diggler/subscribers","subscription_url":"https://api.github.com/repos/mitchgrout/Diggler/subscription","commits_url":"https://api.github.com/repos/mitchgrout/Diggler/commits{/sha}","git_commits_url":"https://api.github.com/repos/mitchgrout/Diggler/git/commits{/sha}","comments_url":"https://api.github.com/repos/mitchgrout/Diggler/comments{/number}","issue_comment_url":"https://api.github.com/repos/mitchgrout/Diggler/issues/comments{/number}","contents_url":"https://api.github.com/repos/mitchgrout/Diggler/contents/{+path}","compare_url":"https://api.github.com/repos/mitchgrout/Diggler/compare/{base}...{head}","merges_url":"https://api.github.com/repos/mitchgrout/Diggler/merges","archive_url":"https://api.github.com/repos/mitchgrout/Diggler/{archive_format}{/ref}","downloads_url":"https://api.github.com/repos/mitchgrout/Diggler/downloads","issues_url":"https://api.github.com/repos/mitchgrout/Diggler/issues{/number}","pulls_url":"https://api.github.com/repos/mitchgrout/Diggler/pulls{/number}","milestones_url":"https://api.github.com/repos/mitchgrout/Diggler/milestones{/number}","notifications_url":"https://api.github.com/repos/mitchgrout/Diggler/notifications{?since,all,participating}","labels_url":"https://api.github.com/repos/mitchgrout/Diggler/labels{/name}","releases_url":"https://api.github.com/repos/mitchgrout/Diggler/releases{/id}","deployments_url":"https://api.github.com/repos/mitchgrout/Diggler/deployments","created_at":"2015-12-23T09:32:23Z","updated_at":"2016-01-11T05:54:19Z","pushed_at":"2016-02-15T12:23:41Z","git_url":"git://github.com/mitchgrout/Diggler.git","ssh_url":"git@github.com:mitchgrout/Diggler.git","clone_url":"https://github.com/mitchgrout/Diggler.git","svn_url":"https://github.com/mitchgrout/Diggler","homepage":"http://jakobovrum.github.io/Diggler/","size":70,"stargazers_count":0,"watchers_count":0,"language":"D","has_issues":false,"has_downloads":true,"has_wiki":true,"has_pages":false,"forks_count":0,"mirror_url":null,"open_issues_count":0,"forks":0,"open_issues":0,"watchers":0,"default_branch":"master"}},"base":{"label":"JakobOvrum:master","ref":"master","sha":"4dd8d8854bc608718f9d02addc03a7fcdb717290","user":{"login":"JakobOvrum","id":155856,"avatar_url":"https://avatars.githubusercontent.com/u/155856?v=3","gravatar_id":"","url":"https://api.github.com/users/JakobOvrum","html_url":"https://github.com/JakobOvrum","followers_url":"https://api.github.com/users/JakobOvrum/followers","following_url":"https://api.github.com/users/JakobOvrum/following{/other_user}","gists_url":"https://api.github.com/users/JakobOvrum/gists{/gist_id}","starred_url":"https://api.github.com/users/JakobOvrum/starred{/owner}{/repo}","subscriptions_url":"https://api.github.com/users/JakobOvrum/subscriptions","organizations_url":"https://api.github.com/users/JakobOvrum/orgs","repos_url":"https://api.github.com/users/JakobOvrum/repos","events_url":"https://api.github.com/users/JakobOvrum/events{/privacy}","received_events_url":"https://api.github.com/users/JakobOvrum/received_events","type":"User","site_admin":false},"repo":{"id":13389222,"name":"Diggler","full_name":"JakobOvrum/Diggler","owner":{"login":"JakobOvrum","id":155856,"avatar_url":"https://avatars.githubusercontent.com/u/155856?v=3","gravatar_id":"","url":"https://api.github.com/users/JakobOvrum","html_url":"https://github.com/JakobOvrum","followers_url":"https://api.github.com/users/JakobOvrum/followers","following_url":"https://api.github.com/users/JakobOvrum/following{/other_user}","gists_url":"https://api.github.com/users/JakobOvrum/gists{/gist_id}","starred_url":"https://api.github.com/users/JakobOvrum/starred{/owner}{/repo}","subscriptions_url":"https://api.github.com/users/JakobOvrum/subscriptions","organizations_url":"https://api.github.com/users/JakobOvrum/orgs","repos_url":"https://api.github.com/users/JakobOvrum/repos","events_url":"https://api.github.com/users/JakobOvrum/events{/privacy}","received_events_url":"https://api.github.com/users/JakobOvrum/received_events","type":"User","site_admin":false},"private":false,"html_url":"https://github.com/JakobOvrum/Diggler","description":"IRC bot framework for the D programming language","fork":false,"url":"https://api.github.com/repos/JakobOvrum/Diggler","forks_url":"https://api.github.com/repos/JakobOvrum/Diggler/forks","keys_url":"https://api.github.com/repos/JakobOvrum/Diggler/keys{/key_id}","collaborators_url":"https://api.github.com/repos/JakobOvrum/Diggler/collaborators{/collaborator}","teams_url":"https://api.github.com/repos/JakobOvrum/Diggler/teams","hooks_url":"https://api.github.com/repos/JakobOvrum/Diggler/hooks","issue_events_url":"https://api.github.com/repos/JakobOvrum/Diggler/issues/events{/number}","events_url":"https://api.github.com/repos/JakobOvrum/Diggler/events","assignees_url":"https://api.github.com/repos/JakobOvrum/Diggler/assignees{/user}","branches_url":"https://api.github.com/repos/JakobOvrum/Diggler/branches{/branch}","tags_url":"https://api.github.com/repos/JakobOvrum/Diggler/tags","blobs_url":"https://api.github.com/repos/JakobOvrum/Diggler/git/blobs{/sha}","git_tags_url":"https://api.github.com/repos/JakobOvrum/Diggler/git/tags{/sha}","git_refs_url":"https://api.github.com/repos/JakobOvrum/Diggler/git/refs{/sha}","trees_url":"https://api.github.com/repos/JakobOvrum/Diggler/git/trees{/sha}","statuses_url":"https://api.github.com/repos/JakobOvrum/Diggler/statuses/{sha}","languages_url":"https://api.github.com/repos/JakobOvrum/Diggler/languages","stargazers_url":"https://api.github.com/repos/JakobOvrum/Diggler/stargazers","contributors_url":"https://api.github.com/repos/JakobOvrum/Diggler/contributors","subscribers_url":"https://api.github.com/repos/JakobOvrum/Diggler/subscribers","subscription_url":"https://api.github.com/repos/JakobOvrum/Diggler/subscription","commits_url":"https://api.github.com/repos/JakobOvrum/Diggler/commits{/sha}","git_commits_url":"https://api.github.com/repos/JakobOvrum/Diggler/git/commits{/sha}","comments_url":"https://api.github.com/repos/JakobOvrum/Diggler/comments{/number}","issue_comment_url":"https://api.github.com/repos/JakobOvrum/Diggler/issues/comments{/number}","contents_url":"https://api.github.com/repos/JakobOvrum/Diggler/contents/{+path}","compare_url":"https://api.github.com/repos/JakobOvrum/Diggler/compare/{base}...{head}","merges_url":"https://api.github.com/repos/JakobOvrum/Diggler/merges","archive_url":"https://api.github.com/repos/JakobOvrum/Diggler/{archive_format}{/ref}","downloads_url":"https://api.github.com/repos/JakobOvrum/Diggler/downloads","issues_url":"https://api.github.com/repos/JakobOvrum/Diggler/issues{/number}","pulls_url":"https://api.github.com/repos/JakobOvrum/Diggler/pulls{/number}","milestones_url":"https://api.github.com/repos/JakobOvrum/Diggler/milestones{/number}","notifications_url":"https://api.github.com/repos/JakobOvrum/Diggler/notifications{?since,all,participating}","labels_url":"https://api.github.com/repos/JakobOvrum/Diggler/labels{/name}","releases_url":"https://api.github.com/repos/JakobOvrum/Diggler/releases{/id}","deployments_url":"https://api.github.com/repos/JakobOvrum/Diggler/deployments","created_at":"2013-10-07T16:34:48Z","updated_at":"2015-04-21T13:57:20Z","pushed_at":"2016-02-15T12:23:41Z","git_url":"git://github.com/JakobOvrum/Diggler.git","ssh_url":"git@github.com:JakobOvrum/Diggler.git","clone_url":"https://github.com/JakobOvrum/Diggler.git","svn_url":"https://github.com/JakobOvrum/Diggler","homepage":"http://jakobovrum.github.io/Diggler/","size":72,"stargazers_count":5,"watchers_count":5,"language":"D","has_issues":true,"has_downloads":true,"has_wiki":true,"has_pages":true,"forks_count":5,"mirror_url":null,"open_issues_count":7,"forks":5,"open_issues":7,"watchers":5,"default_branch":"master"}},"_links":{"self":{"href":"https://api.github.com/repos/JakobOvrum/Diggler/pulls/16"},"html":{"href":"https://github.com/JakobOvrum/Diggler/pull/16"},"issue":{"href":"https://api.github.com/repos/JakobOvrum/Diggler/issues/16"},"comments":{"href":"https://api.github.com/repos/JakobOvrum/Diggler/issues/16/comments"},"review_comments":{"href":"https://api.github.com/repos/JakobOvrum/Diggler/pulls/16/comments"},"review_comment":{"href":"https://api.github.com/repos/JakobOvrum/Diggler/pulls/comments{/number}"},"commits":{"href":"https://api.github.com/repos/JakobOvrum/Diggler/pulls/16/commits"},"statuses":{"href":"https://api.github.com/repos/JakobOvrum/Diggler/statuses/8f388de820a1ca92f1db409350029e0a857ef26a"}}}}