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 }