1 /++ 2 + Module for IRCv3 core features. 3 +/ 4 module virc.ircv3.base; 5 6 /++ 7 + 8 +/ 9 enum IRCV3Commands { 10 cap = "CAP", 11 metadata = "METADATA", 12 authenticate = "AUTHENTICATE", 13 account = "ACCOUNT", 14 starttls = "STARTTLS", 15 monitor = "MONITOR", 16 batch = "BATCH", 17 chghost = "CHGHOST", 18 fail = "FAIL", 19 warn = "WARN", 20 note = "NOTE", 21 } 22 23 /++ 24 + 25 +/ 26 enum CapabilityServerSubcommands { 27 ls = "LS", 28 acknowledge = "ACK", 29 notAcknowledge = "NAK", 30 list = "LIST", 31 new_ = "NEW", 32 delete_ = "DEL" 33 } 34 35 /++ 36 + 37 +/ 38 enum CapabilityClientSubcommands { 39 ls = "LS", 40 list = "LIST", 41 request = "REQ", 42 end = "END" 43 } 44 45 /++ 46 + 47 +/ 48 struct Capability { 49 /// 50 string name; 51 ///DEPRECATED - indicates that the capability cannot be disabled 52 bool isSticky; 53 ///Indicates that the capability is being disabled 54 bool isDisabled; 55 ///DEPRECATED - indicates that the client must acknowledge this cap via CAP ACK 56 bool mustAck; 57 /// 58 string value; 59 60 alias name this; 61 62 @disable this(); 63 64 /// 65 this(string str) @safe pure @nogc in { 66 import std.range : empty; 67 assert(!str.empty); 68 } do { 69 import std.algorithm.comparison : among; 70 import std.algorithm.searching : findSplit; 71 import std.range : front, popFront; 72 import std.utf : byCodeUnit; 73 auto prefix = str.byCodeUnit.front; 74 setFlagsByPrefix(prefix); 75 if (prefix.among('~', '=', '-')) { 76 str.popFront(); 77 } 78 auto split = str.findSplit("="); 79 this(split[0], split[2]); 80 } 81 /// 82 this(string key, string val) @safe pure @nogc nothrow { 83 name = key; 84 value = val; 85 } 86 /++ 87 + Indicates whether or not this is a vendor-specific capability. 88 + 89 + Often these are draft implementations of not-yet-accepted capabilities. 90 +/ 91 bool isVendorSpecific() @safe pure @nogc nothrow { 92 import std.algorithm.searching : canFind; 93 import std.utf : byCodeUnit; 94 return name.byCodeUnit.canFind('/'); 95 } 96 /// 97 string toString() const pure @safe { 98 import std.range : empty; 99 return name~(value.empty ? "" : "=")~value; 100 } 101 private void setFlagsByPrefix(char chr) @safe pure @nogc nothrow { 102 switch (chr) { 103 case '~': 104 mustAck = true; 105 break; 106 case '=': 107 isSticky = true; 108 break; 109 case '-': 110 isDisabled = true; 111 break; 112 default: 113 break; 114 } 115 } 116 } 117 /// 118 @safe pure @nogc unittest { 119 { 120 auto cap = Capability("~account-notify"); 121 assert(cap.name == "account-notify"); 122 assert(cap.mustAck); 123 } 124 { 125 auto cap = Capability("=account-notify"); 126 assert(cap.name == "account-notify"); 127 assert(cap.isSticky); 128 } 129 { 130 auto cap = Capability("-account-notify"); 131 assert(cap.name == "account-notify"); 132 assert(cap.isDisabled); 133 } 134 { 135 auto cap = Capability("example.com/cap"); 136 assert(cap.name == "example.com/cap"); 137 assert(cap.isVendorSpecific); 138 } 139 { 140 auto cap = Capability("cap=value"); 141 assert(cap.name == "cap"); 142 assert(cap.value == "value"); 143 } 144 { 145 auto cap = Capability("cap=value/notvendor"); 146 assert(cap.name == "cap"); 147 assert(cap.value == "value/notvendor"); 148 assert(!cap.isVendorSpecific); 149 } 150 } 151 @system pure unittest { 152 import core.exception : AssertError; 153 import std.exception : assertThrown; 154 { 155 assertThrown!AssertError(Capability("")); 156 } 157 }