irc: I'm dumb. Fix syntax.

irc: I'm dumb. Fix syntax.

--- IRC client class
-- @classmod IRCClient

re = require "re"
socket = require 'cqueues.socket'
Logger = require 'logger'

escapers =  {['s']: ' ', ['r']: '\r', ['n']: '\n', [';']: ';'}

local IRCClient, ContextTable

class ContextTable
	-- ::TODO:: allow for multiple *and* index
	get: (name, opts = {})=>
		:multiple, :index = opts
		multiple = true if multiple == nil

		local output
		output = {} if multiple
		for context, items in pairs(self)
			-- context
			unless multiple
				-- return the singular if it exists
				return items[name] if items[name] ~= nil
			elseif items[name]
				-- add all that exist
				for element in *items[name]
					table.insert output, element

		if multiple
			output
		elseif index
			index\get name, :multiple

class IRCClient
	commands: ContextTable!
	hooks: ContextTable!
	handlers: ContextTable!
	senders: ContextTable!

	default_config = {
		prefix: "!"
	}

	line_pattern = re.compile [[
		-- tags, command, args
		line <- {| (tags sp)? (prefix sp)? {:command: (command / numeric) :}
			{:args: {| (sp arg)* |} :} |}
		tags <- {:tags: '@' tag (';' tag)* :}
		tag <- {| {:vendor: {[^/]+} '/' :}?
			{:key: {[^=; ]+} -> esc_tag :}
			{:value: ('=' [^; ]+) -> esc_tag :}?
		|}
		prefix <- ':' (
			{:nick: {[^ !]+} :} '!'
			{:user: {[^ @]+} :} '@'
			{:host: {[^ ]+} :} /
			{:nick: {[^ ]+} :})
		command <- [A-Za-z]+
		numeric <- %d^+3^-4 -- at most four digits, at least three
		arg <- ':' {.+} / {%S+}
		sp <- %s
	]], esc_tag: (tag)-> tag\gsub "\\(.)", setmetatable({
		[";"]: ":"
		s: " "
		r: "\r"
		n: "\n"
	}, __index: (t, k) -> k)

	--- Generate a new IRCClient
	-- @tparam string server IRC server name
	-- @tparam number port IRC port number
	-- @tparam table config Default configuration
	new: (server, port=6697, config)=>
		assert(server)
		@config = :server, :port, :config, ssl: port == 6697
		for k, v in pairs(default_config)
			@config[k] = v
		if config
			for k, v in pairs(config)
				@config[k] = v

		@commands = ContextTable!
		@hooks    = ContextTable!
		@handlers = ContextTable!
		@senders  = ContextTable!
		@server   = {}

	unpack = unpack or table.unpack

	handle_options = (options, fn)->
		unless fn
			fn = options
			options = {}

		if options.async
			return (...)->
				args = {...}
				require("queue")\wrap ->
					fn(unpack(args))

		fn

	assert_context = ()=> assert @context, "Missing context"

	--- Change the module context for the current command
	-- @tparam string context module context (typically, the name)
	-- @tparam function fn code to run under context
	with_context: (context, fn)=>
		assert @context == nil, "Already in context: #{@context}"
		@context = context

		for key in *{"hooks", "handlers", "commands", "senders"}
			self[key][context] = {}

		fn self, context
		@context = nil

	--- Add an IRC bot command
	-- @tparam string name Bot command name
	-- @tparam table options [Optional] async: bool, wraps in cqueues
	-- @tparam function command Function for handling command
	add_command: (name, options, command)=>
		context = assert_context self
		@commands[context][name] = handle_options(options, command)

	--- Add a client processing hook
	-- @tparam string name Name of event to hook into
	-- @tparam table options [Optional] async: bool, wraps in cqueues
	-- @tparam function hook Processor for hook event
	add_hook: (name, options, hook)=>
		context = assert_context self

		hooks = @hooks[context]

		if not hooks[name]
			hooks[name] = {handle_options(options, hook)}
		else
			table.insert hooks[name], handle_options(options, hook)

	--- Add an IRC command handler
	-- @tparam string id IRC command ID (numerics MUST be strings)
	-- @tparam table options [Optional] async: bool, wraps in cqueues
	-- @tparam function handler IRC command processor
	add_handler: (id, options, handler)=>
		context = assert_context self

		handlers = @handlers[context]

		unless handlers[id]
			handlers[id] = {handle_options(options, handler)}
		else
			table.insert handlers[id], handle_options(options, handler)

	--- Add an IRC command sending handler
	-- @tparam string id IRC command ID ("PRIVMSG", "JOIN", etc.)
	-- @tparam table options [Optional] async: bool, wraps in cqueues
	-- @tparam function sender Function to handle message to be sent
	add_sender: (id, options, sender)=>
		context = assert_context self
		@senders[context][id] = handle_options(options, sender)

	--- Reset all modules in the IRCClient
	clear_modules: ()=>
		@senders = {}
		@handlers = {}
		@hooks = {}
		@commands = {}

	--- Connect to the IRC server specified in the configuration
	connect: ()=>
		if @socket
			Logger.debug "Shutting down socket: #{tostring(@socket)}"
			@socket\shutdown!
		host = @config.server
		port = @config.port
		ssl  = @config.ssl
		debug_msg = ('Connecting... {host: "%s", port: "%s"}')\format host, port

		@config.nick = 'Moon-Moon' if not @config.nick
		@config.username = 'Mooooon' if not @config.username
		@config.realname = 'Moon Moon: MoonScript IRC Bot' if not @config.realname

		Logger.debug debug_msg, Logger.level.warn .. '--- Connecting...'
		@socket = assert socket.connect{:host, :port}
		if ssl
			Logger.debug 'Starting TLS exchange...'
			@socket\starttls!
			Logger.debug 'Started TLS exchange'
		Logger.print Logger.level.okay .. '--- Connected'
		@fire_hook 'CONNECT'
		nick = @config.nick
		user = @config.username
		real = @config.realname
		pass = @config.password
		Logger.print Logger.level.warn .. '--- Sending authentication data'
		@send_raw ('NICK %s')\format nick
		if pass and ssl
			Logger.debug '*** Sending password'
			@send_raw ('PASS :%s')\format pass
		elseif pass
			Logger.print Logger.level.error .. '*** Not sending password: TLS not enabled ***'
		@send_raw ('USER %s * * :%s')\format user, real
		debug_msg = ('Sent authentication data: {nickname: %s, username: %s, realname: %s}')\format nick, user, real
		Logger.debug debug_msg, Logger.level.okay .. '--- Sent authentication data'

	--- Disconnect from the current IRC server
	disconnect: ()=>
		@socket\shutdown! if @socket
		@fire_hook 'DISCONNECT'

	--- Send a raw line to the currently connected IRC server
	-- @param ... List of strings, concatenated using spaces
	send_raw: (...)=>
		@socket\write table.concat({...}, ' ') .. '\n'
		Logger.debug '==> ' .. table.concat {...}, ' '

	--- Send a command using a builtin sender configured with @add_sender
	-- @tparam string name Name of sender to use
	-- @param ... List of arguments to be passed to sender
	-- @see IRCClient\add_sender
	send: (name, ...)=>
		sender = @senders\get name, multiple: false, index: IRCClient.senders
		@send_raw sender(self, ...)

	date_pattern: "(%d+)-(%d+)-(%d+)T(%d+):(%d+):(%d+).(%d+)Z"

	--- Parse the time of an ISO 8601 compliant string
	-- @tparam string date ISO 8601 compliant YYYY-MM-DDThh:mm:ssZ date
	-- @treturn number Seconds from Epoch from configured date
	parse_time: (date)=>
		year, month, day, hour, min, sec, mil = date\match @date_pattern
		return os.time(:year, :month, :day, :hour, :min, :sec) + tonumber(mil) / 1000

	--- parse an IRC command using line_pattern
	parse: (line)=> line_pattern\match line

	--- Activate hooks configured for the event name
	-- @tparam string hook_name Name of event to "fire"
	-- @treturn boolean True if no errors were encountered, false otherwise
	-- @treturn table Table containing list of errors from hooks
	fire_hook: (hook_name, ...)=>
		Logger.debug "#{Logger.level.warn}--- Running hooks for #{hook_name}"
		has_run = false
		has_errors = false
		errors = {}

		for _, hook in pairs IRCClient.hooks\get hook_name
			has_run = true unless has_run
			Logger.debug Logger.level.warn .. '--- Running global hook: ' .. hook_name
			ok, err = pcall hook, @, ...
			if not ok
				has_errors = true if not has_errors
				table.insert(errors, err)
				Logger.print Logger.level.error .. '*** ' .. err
		for _, hook in pairs @hooks\get hook_name
			has_run = true unless has_run
			Logger.debug Logger.level.warn .. '--- Running hook: ' .. hook_name
			ok, err = pcall hook, @, ...
			if not ok
				has_errors = true if not has_errors
				table.insert(errors, err)
				Logger.print Logger.level.error .. '*** ' .. err

		Logger.debug Logger.level.error .. "*** Handler not found for #{command}" unless has_run

		return has_errors, errors

	--- Run handlers for an unparsed command
	-- @tparam string line Incoming wire-string formatted line from IRC server
	process: (line)=>
		{:tags, :nick, :user, :host, :command, :args} = @parse line
		Logger.debug Logger.level.warn .. '--- | Line: ' .. line
		Logger.debug Logger.level.okay .. '--- |\\ Running trigger: ' .. Logger.level.warn .. command
		if nick and user and host
			Logger.debug "#{Logger.level.okay} --- |\\ User: #{nick}!#{user}@#{host}"
		elseif nick
			Logger.debug "#{Logger.level.okay} --- |\\ Nick: #{nick}"
		if #args > 0
			Logger.debug "#{Logger.level.okay} --- |\\ Arguments: #{table.concat args}"

		has_run = false

		for handler in *IRCClient.handlers\get command
			has_run = true unless has_run
			ok, err, tb = xpcall handler, self\log_traceback, self,
				{:nick, :user, :host}, args, tags
			if not ok
				@log tb
				@log Logger.level.error .. '*** ' .. err

		for handler in *@handlers\get command
			has_run = true unless has_run
			ok, err = xpcall handler, self\log_traceback, @,
				prefix, args, rest, tags
			if not ok
				Logger.print Logger.level.error .. '*** ' .. err

		Logger.debug Logger.level.error .. "*** Handler not found for #{command}" unless has_run

	--- Iterate over lines from a server and handle errors appropriately
	loop: ()=>
		local line
		print_error = (err)->
			Logger.debug "Error: " .. err .. " (" .. line .. ")"

		for received_line in @socket\lines! do
			line = received_line
			xpcall @process, print_error, @, received_line

	--- Print a traceback using the internal logging mechanism
	-- @see IRCClient\log
	log_traceback: (err)=>
		@log Logger.level.error .. '*** ' .. err
		@log debug.traceback()
		@log Logger.level.error .. '---'

	--- Log message from IRC server (used in plugins)
	-- @tparam string input Line to print, IRC color formatted
	-- @see logger
	log: (input)=>
		for line in input\gmatch "[^\r\n]+"
			Logger.print '\00311(\003' .. (@server.caps and
				@server.caps['NETWORK'] or @config.server) ..
				"\00311)\003 #{line}"

return {:IRCClient}

--- IRC client class
-- @classmod IRCClient

re = require "re"
socket = require 'cqueues.socket'
Logger = require 'logger'

escapers =  {['s']: ' ', ['r']: '\r', ['n']: '\n', [';']: ';'}

local IRCClient, ContextTable

class ContextTable
	-- ::TODO:: allow for multiple *and* index
	get: (name, opts = {})=>
		:multiple, :index = opts
		multiple = true if multiple == nil

		local output
		output = {} if multiple
		for context, items in pairs(self)
			-- context
			unless multiple
				-- return the singular if it exists
				return items[name] if items[name] ~= nil
			elseif items[name]
				-- add all that exist
				for element in *items[name]
					table.insert output, element

		if multiple
			output
		elseif index
			index\get name, :multiple

class IRCClient
	commands: ContextTable!
	hooks: ContextTable!
	handlers: ContextTable!
	senders: ContextTable!

	default_config = {
		prefix: "!"
	}

	line_pattern = re.compile [[
		-- tags, command, args
		line <- {| (tags sp)? (prefix sp)? {:command: (command / numeric) :}
			{:args: {| (sp arg)* |} :} |}
		tags <- {:tags: '@' tag (';' tag)* :}
		tag <- {| {:vendor: {[^/]+} '/' :}?
			{:key: {[^=; ]+} -> esc_tag :}
			{:value: ('=' [^; ]+) -> esc_tag :}?
		|}
		prefix <- ':' (
			{:nick: {[^ !]+} :} '!'
			{:user: {[^ @]+} :} '@'
			{:host: {[^ ]+} :} /
			{:nick: {[^ ]+} :})
		command <- [A-Za-z]+
		numeric <- %d^+3^-4 -- at most four digits, at least three
		arg <- ':' {.+} / {%S+}
		sp <- %s
	]], esc_tag: (tag)-> tag\gsub "\\(.)", setmetatable({
		[";"]: ":"
		s: " "
		r: "\r"
		n: "\n"
	}, __index: (t, k) -> k)

	--- Generate a new IRCClient
	-- @tparam string server IRC server name
	-- @tparam number port IRC port number
	-- @tparam table config Default configuration
	new: (server, port=6697, config)=>
		assert(server)
		@config = :server, :port, :config, ssl: port == 6697
		for k, v in pairs(default_config)
			@config[k] = v
		if config
			for k, v in pairs(config)
				@config[k] = v

		@commands = ContextTable!
		@hooks    = ContextTable!
		@handlers = ContextTable!
		@senders  = ContextTable!
		@server   = {}

	unpack = unpack or table.unpack

	handle_options = (options, fn)->
		unless fn
			fn = options
			options = {}

		if options.async
			return (...)->
				args = {...}
				require("queue")\wrap ->
					fn(unpack(args))

		fn

	assert_context = ()=> assert @context, "Missing context"

	--- Change the module context for the current command
	-- @tparam string context module context (typically, the name)
	-- @tparam function fn code to run under context
	with_context: (context, fn)=>
		assert @context == nil, "Already in context: #{@context}"
		@context = context

		for key in *{"hooks", "handlers", "commands", "senders"}
			self[key][context] = {}

		fn self, context
		@context = nil

	--- Add an IRC bot command
	-- @tparam string name Bot command name
	-- @tparam table options [Optional] async: bool, wraps in cqueues
	-- @tparam function command Function for handling command
	add_command: (name, options, command)=>
		context = assert_context self
		@commands[context][name] = handle_options(options, command)

	--- Add a client processing hook
	-- @tparam string name Name of event to hook into
	-- @tparam table options [Optional] async: bool, wraps in cqueues
	-- @tparam function hook Processor for hook event
	add_hook: (name, options, hook)=>
		context = assert_context self

		hooks = @hooks[context]

		if not hooks[name]
			hooks[name] = {handle_options(options, hook)}
		else
			table.insert hooks[name], handle_options(options, hook)

	--- Add an IRC command handler
	-- @tparam string id IRC command ID (numerics MUST be strings)
	-- @tparam table options [Optional] async: bool, wraps in cqueues
	-- @tparam function handler IRC command processor
	add_handler: (id, options, handler)=>
		context = assert_context self

		handlers = @handlers[context]

		unless handlers[id]
			handlers[id] = {handle_options(options, handler)}
		else
			table.insert handlers[id], handle_options(options, handler)

	--- Add an IRC command sending handler
	-- @tparam string id IRC command ID ("PRIVMSG", "JOIN", etc.)
	-- @tparam table options [Optional] async: bool, wraps in cqueues
	-- @tparam function sender Function to handle message to be sent
	add_sender: (id, options, sender)=>
		context = assert_context self
		@senders[context][id] = handle_options(options, sender)

	--- Reset all modules in the IRCClient
	clear_modules: ()=>
		@senders = {}
		@handlers = {}
		@hooks = {}
		@commands = {}

	--- Connect to the IRC server specified in the configuration
	connect: ()=>
		if @socket
			Logger.debug "Shutting down socket: #{tostring(@socket)}"
			@socket\shutdown!
		host = @config.server
		port = @config.port
		ssl  = @config.ssl
		debug_msg = ('Connecting... {host: "%s", port: "%s"}')\format host, port

		@config.nick = 'Moon-Moon' if not @config.nick
		@config.username = 'Mooooon' if not @config.username
		@config.realname = 'Moon Moon: MoonScript IRC Bot' if not @config.realname

		Logger.debug debug_msg, Logger.level.warn .. '--- Connecting...'
		@socket = assert socket.connect{:host, :port}
		if ssl
			Logger.debug 'Starting TLS exchange...'
			@socket\starttls!
			Logger.debug 'Started TLS exchange'
		Logger.print Logger.level.okay .. '--- Connected'
		@fire_hook 'CONNECT'
		nick = @config.nick
		user = @config.username
		real = @config.realname
		pass = @config.password
		Logger.print Logger.level.warn .. '--- Sending authentication data'
		@send_raw ('NICK %s')\format nick
		if pass and ssl
			Logger.debug '*** Sending password'
			@send_raw ('PASS :%s')\format pass
		elseif pass
			Logger.print Logger.level.error .. '*** Not sending password: TLS not enabled ***'
		@send_raw ('USER %s * * :%s')\format user, real
		debug_msg = ('Sent authentication data: {nickname: %s, username: %s, realname: %s}')\format nick, user, real
		Logger.debug debug_msg, Logger.level.okay .. '--- Sent authentication data'

	--- Disconnect from the current IRC server
	disconnect: ()=>
		@socket\shutdown! if @socket
		@fire_hook 'DISCONNECT'

	--- Send a raw line to the currently connected IRC server
	-- @param ... List of strings, concatenated using spaces
	send_raw: (...)=>
		@socket\write table.concat({...}, ' ') .. '\n'
		Logger.debug '==> ' .. table.concat {...}, ' '

	--- Send a command using a builtin sender configured with @add_sender
	-- @tparam string name Name of sender to use
	-- @param ... List of arguments to be passed to sender
	-- @see IRCClient\add_sender
	send: (name, ...)=>
		sender = @senders\get name, multiple: false, index: IRCClient.senders
		@send_raw sender(self, ...)

	date_pattern: "(%d+)-(%d+)-(%d+)T(%d+):(%d+):(%d+).(%d+)Z"

	--- Parse the time of an ISO 8601 compliant string
	-- @tparam string date ISO 8601 compliant YYYY-MM-DDThh:mm:ssZ date
	-- @treturn number Seconds from Epoch from configured date
	parse_time: (date)=>
		year, month, day, hour, min, sec, mil = date\match @date_pattern
		return os.time(:year, :month, :day, :hour, :min, :sec) + tonumber(mil) / 1000

	--- parse an IRC command using line_pattern
	parse: (line)=> line_pattern\match line

	--- Activate hooks configured for the event name
	-- @tparam string hook_name Name of event to "fire"
	-- @treturn boolean True if no errors were encountered, false otherwise
	-- @treturn table Table containing list of errors from hooks
	fire_hook: (hook_name, ...)=>
		Logger.debug "#{Logger.level.warn}--- Running hooks for #{hook_name}"
		has_run = false
		has_errors = false
		errors = {}

		for _, hook in pairs IRCClient.hooks\get hook_name
			has_run = true unless has_run
			Logger.debug Logger.level.warn .. '--- Running global hook: ' .. hook_name
			ok, err = pcall hook, @, ...
			if not ok
				has_errors = true if not has_errors
				table.insert(errors, err)
				Logger.print Logger.level.error .. '*** ' .. err
		for _, hook in pairs @hooks\get hook_name
			has_run = true unless has_run
			Logger.debug Logger.level.warn .. '--- Running hook: ' .. hook_name
			ok, err = pcall hook, @, ...
			if not ok
				has_errors = true if not has_errors
				table.insert(errors, err)
				Logger.print Logger.level.error .. '*** ' .. err

		Logger.debug Logger.level.error .. "*** Handler not found for #{command}" unless has_run

		return has_errors, errors

	--- Run handlers for an unparsed command
	-- @tparam string line Incoming wire-string formatted line from IRC server
	process: (line)=>
		{
			:tags,
			:nick,
			:user,
			:host,
			:command,
			:args = @parse line
		)
		Logger.debug Logger.level.warn .. '--- | Line: ' .. line
		Logger.debug Logger.level.okay .. '--- |\\ Running trigger: ' .. Logger.level.warn .. command
		if nick and user and host
			Logger.debug "#{Logger.level.okay} --- |\\ User: #{nick}!#{user}@#{host}"
		elseif nick
			Logger.debug "#{Logger.level.okay} --- |\\ Nick: #{nick}"
		if #args > 0
			Logger.debug "#{Logger.level.okay} --- |\\ Arguments: #{table.concat args}"

		has_run = false

		for handler in *IRCClient.handlers\get command
			has_run = true unless has_run
			ok, err, tb = xpcall handler, self\log_traceback, self,
				{:nick, :user, :host}, args, tags
			if not ok
				@log tb
				@log Logger.level.error .. '*** ' .. err

		for handler in *@handlers\get command
			has_run = true unless has_run
			ok, err = xpcall handler, self\log_traceback, @,
				prefix, args, rest, tags
			if not ok
				Logger.print Logger.level.error .. '*** ' .. err

		Logger.debug Logger.level.error .. "*** Handler not found for #{command}" unless has_run

	--- Iterate over lines from a server and handle errors appropriately
	loop: ()=>
		local line
		print_error = (err)->
			Logger.debug "Error: " .. err .. " (" .. line .. ")"

		for received_line in @socket\lines! do
			line = received_line
			xpcall @process, print_error, @, received_line

	--- Print a traceback using the internal logging mechanism
	-- @see IRCClient\log
	log_traceback: (err)=>
		@log Logger.level.error .. '*** ' .. err
		@log debug.traceback()
		@log Logger.level.error .. '---'

	--- Log message from IRC server (used in plugins)
	-- @tparam string input Line to print, IRC color formatted
	-- @see logger
	log: (input)=>
		for line in input\gmatch "[^\r\n]+"
			Logger.print '\00311(\003' .. (@server.caps and
				@server.caps['NETWORK'] or @config.server) ..
				"\00311)\003 #{line}"

return {:IRCClient}


Arg and ArgNum are almost complete! Just a few more missing functions.

[=[
File: Arguments
This file defines argument types to be used in TODO.
]=]

[=[
Class: Arg

The base argument class.

Revisions:
	1.0.0 - Initial.
]=]
export class Arg
	[=[
	Function: ShortcutFn
	Only available statically, meant for internal use only.
	]=]
	ShortcutFn: (name, typ, default using nil) =>
		@__base[name] = (val=default using nil) =>
			UtilX.CheckArg(1, "#{@@__name}.#{name}", typ, val)
			@["_" .. name] = val
			@

	[=[
	Variables: Arg Variables
	All these variables are optional, with sensible defaults.

		_Default  - A value of *any type*. If an argument is optional and unspecified, this value is used.
		_Optional - A *boolean* of whether or not this argument is optional.
		_Hint - A *string* (usually a word or two) used in help output to give users a general idea of what the argument is for.
		_Help - A *string* used in longer help output to describe the argument.
	]=]
	_Default:  nil
	_Optional: false
	_Hint:     ""
	_Help:     ""

	@ShortcutFn "Optional", "boolean", true
	@ShortcutFn "Default"
	@ShortcutFn "Hint", "string"
	@ShortcutFn "Help", "string"

	[=[
	Function: Combine
	TODO
	]=]
	@Combine: (args, copyTo=Arg() using nil) =>
		copyTo = Arg()
		for copyFrom in *args
			if copyFrom._Default ~= nil
				copyTo._Default = copyFrom._Default
			copyTo._Optional = copyFrom._Optional or copyTo._Optional

		copyTo

	[=[
	Function: UsageShort
	TODO
	]=]
	UsageShort: (str using nil) =>
		str ..= ", "                   if @_Optional and #str > 0
		str ..= "default #{@_Default}" if @_Optional
		str = "#{@_Hint}: " .. str     if @_Hint
		str = "<" .. str .. ">"
		str = "[" .. str .. "]"        if @_Optional

		str

	[=[
	Function: UsageLong
	TODO
	]=]
	UsageLong: (str using nil) =>
		str ..= "Type:     #{@__name}\n"
		str ..= "Default:  #{@_Default}\n" if @_Optional
		str ..= @_Optional and "This argument is optional\n" or "This argument is required\n"
		str ..= "Hint:     #{@_Hint}\n"    if @_Hint
		str ..= "Help:     #{@_Help}\n"    if @_Help

		str

	[=[
	Function: Completes
	TODO
	]=]
	Completes: (str using nil) =>
		nil

	[=[
	Function: Parse
	TODO
	]=]
	Parse: (str using nil) =>
		UtilX.Raise "Arg.Parse() called, but is intentionally unimplemented"

[=[
Class: ArgNum
The argument class used for any numeric data.

Passes:
	A *number* value, defaulting to _0_.

Revisions:
	1.0.0 - Initial.
]=]
export class ArgNum extends Arg
	-- Values from parent that we want to override the defaults for
	_Default: 0
	_Hint:    "number"
	_Help:    "A number argument"

	@ShortcutFn "Default", "number"

	[=[
	Variables: ArgNum Variables
	All these variables are optional, with sensible defaults.

		_Min   - A *number or nil* specifying the minimum value for the argument.
		_Max   - A *number or nil* specifying the maximum value for the argument.
		_Round - A *number or nil* specifying the digit to round to, as passed to <UtilX.Round>.
		_Hint  - A *string* (usually a word or two) used in help output to give users a general idea of what the argument is for.
	]=]
	_Min:   nil
	_Max:   nil
	_Round: nil

	@ShortcutFn "Min", {"number", "nil"}
	@ShortcutFn "Max", {"number", "nil"}
	@ShortcutFn "Round", {"number", "nil"}, 0

	[=[
	Function: Combine
	TODO
	]=]
	@Combine: (args, copyTo=ArgNum() using nil) =>
		super\Combine args, copyTo
		for copyFrom in *args
			if copyFrom._Min and (not copyTo._Min or copyTo._Min and copyFrom._Min > copyTo._Min)
				copyTo._Min = copyFrom._Min
			if copyFrom._Max and (not copyTo._Max or copyTo._Max and copyFrom._Max < copyTo._Max)
				copyTo._Max = copyFrom._Max
			copyTo._Round = copyFrom._Round or copyTo._Round

		copyTo

	[=[
	Function: UsageShort
	TODO
	]=]
	UsageShort: (str = "" using nil) =>
		if @_Min and @_Min == @_Max
			str ..= tostring @_Min

		else
			str ..= @_Min .. "<=" if @_Min
			str ..= "x"
			str ..= "<=" .. @_Max if @_Max

		super\UsageShort str

	[=[
	Function: UsageLong
	TODO
	]=]
	UsageLong: (str = "" using nil) =>
		str = super\UsageLong str

		str ..= "Min:      #{@_Min}\n"   if @_Min
		str ..= "Max:      #{@_Max}\n"   if @_Max
		str ..= "Round:    #{@_Round}\n" if @_Round
		str

[=[
Class: ArgTime
The argument class used for a timespan using natural language.

Passes:
	A *number* of seconds, defaulting to _0_.

Revisions:
	1.0.0 - Initial.
]=]
class ArgTime extends ArgNum
	[=[
	Variables: ArgNum Variables
	All these variables are optional, with sensible defaults.

		Min - A *number, string, or nil* specifying the minimum value for the argument.
		Max - A *number, string, or nil* specifying the maximum value for the argument.
	]=]
	Min: nil
	Max: nil

[=[
Class: ArgString
The argument class used for string arguments.

Passes:
	A *string* value, defaulting to _0_.

Revisions:
	1.0.0 - Initial.
]=]
class ArgString extends Arg
	[=[
	Variables: ArgString Variables
	All these variables are optional, with sensible defaults.

		Default - A *string*, defaults to _""_. If an argument is optional and unspecified, this value is used.
		RestrictToCompletes - A *boolean*, defaults to _false_.
			If true, the argument passed will /always/ be one of the arguments from the <Completes> table.
		Completes           - A *table or nil* of auto-completes (suggestions) for the argument.

	]=]
	Default:             ""
	RestrictToCompletes: false
	Completes:           nil

[=[
Class: ArgPlayerID
The argument class used for SteamID arguments.

Passes:
	A *table of strings or players* (between one and <MaximumTargets> items).
	Each item is either a valid SteamID or a connected player.

Revisions:
	1.0.0 - Initial.
]=]
class ArgPlayerID extends Arg
	[=[
	Variables: ArgPlayerID Variables
	All these variables are optional, with sensible defaults.

		Default            - A *string*, defaults to _"^"_ (keyword for the player calling the command).
			If an argument is optional and unspecified, this value is used.
		RestrictTarget     - A *string or nil* specifying the players this argument is allowed to target.
			This is passed to <TODO.GetUser()>. Nil indicates no restriction.
		MaximumTargets     - A *number*, defaulting to _1_, specifying the maximum number of players this argument can target.
		PassPlayerIfActive - A *boolean*. If true, will pass the player object if they are connected.

	]=]
	Default:            "^"
	RestrictTarget:     nil
	MaximumTargets:     1
	PassPlayerIfActive: false

[=[
Class: ArgPlayerActive
The argument class used for player arguments.

Passes:
	A *table of players* (between one and <MaximumTargets> items).

Revisions:
	1.0.0 - Initial.
]=]
class ArgPlayerActive extends ArgPlayerID

[=[
File: Arguments
This file defines argument types to be used in TODO.
]=]

[=[
Class: Arg

The base argument class.

Revisions:
	1.0.0 - Initial.
]=]
class Arg
	[=[
	Function: ShortcutFn
	Only available statically, meant for internal use only.
	]=]
	@ShortcutFn: (name, typ, default using nil) =>
		@[name] = (val=default using nil) =>
			UtilX.CheckArg(1, "#{@__name}.#{name}", typ, val)
			@["_" .. name] = val

	[=[
	Variables: Arg Variables
	All these variables are optional, with sensible defaults.

		_Default  - A value of *any type*. If an argument is optional and unspecified, this value is used.
		_Optional - A *boolean* of whether or not this argument is optional.
		_Hint - A *string* (usually a word or two) used in help output to give users a general idea of what the argument is for.
		_Help - A *string* used in longer help output to describe the argument.
	]=]
	_Default:  nil
	_Optional: false
	_Hint:     ""
	_Help:     ""

	@ShortcutFn "Optional", "boolean", true
	@ShortcutFn "Default"
	@ShortcutFn "Hint", "string"
	@ShortcutFn "Help", "string"

	[=[
	Function: Combine
	TODO
	]=]
	@Combine: (args, copyTo=Arg() using nil) =>
		copyTo = Arg()
		for copyFrom in *args
			if copyFrom._Default ~= nil
				copyTo._Default = copyFrom._Default
			copyTo._Optional = copyFrom._Optional or copyTo._Optional

		copyTo

	[=[
	Function: UsageShort
	TODO
	]=]
	UsageShort: (str using nil) =>
		str ..= ", "                   if @_Optional and #str > 0
		str ..= "default #{@_Default}" if @_Optional
		str = "#{@_Hint}: " .. str     if @_Hint
		str = "<" .. str .. ">"
		str = "[" .. str .. "]"        if @_Optional

		str

	[=[
	Function: UsageLong
	TODO
	]=]
	UsageLong: (str using nil) =>
		str ..= "Type:     #{@__name}\n"
		str ..= "Default:  #{@_Default}\n" if @_Optional
		str ..= @_Optional and "This argument is optional\n" or "This argument is required\n"
		str ..= "Hint:     #{@_Hint}\n"    if @_Hint
		str ..= "Help:     #{@_Help}\n"    if @_Help

		str

	[=[
	Function: Completes
	TODO
	]=]
	Completes: (str using nil) =>
		nil

	[=[
	Function: Parse
	TODO
	]=]
	Parse: (str using nil) =>
		UtilX.Raise "Arg.Parse() called, but is intentionally unimplemented"

[=[
Class: ArgNum
The argument class used for any numeric data.

Passes:
	A *number* value, defaulting to _0_.

Revisions:
	1.0.0 - Initial.
]=]
class ArgNum extends Arg
	-- Values from parent that we want to override the defaults for
	_Default: 0
	_Hint:    "number"
	_Help:    "A number argument"

	[=[
	Variables: ArgNum Variables
	All these variables are optional, with sensible defaults.

		_Min   - A *number or nil* specifying the minimum value for the argument.
		_Max   - A *number or nil* specifying the maximum value for the argument.
		_Round - A *number or nil* specifying the digit to round to, as passed to <UtilX.Round>.
		_Hint  - A *string* (usually a word or two) used in help output to give users a general idea of what the argument is for.
	]=]
	_Min:   nil
	_Max:   nil
	_Round: nil

	@ShortcutFn "Min", {"number", "nil"}
	@ShortcutFn "Max", {"number", "nil"}
	@ShortcutFn "Round", {"number", "nil"}, 0

	[=[
	Function: Combine
	TODO
	]=]
	@Combine: (args, copyTo=ArgNum() using nil) =>
		super\Combine args, copyTo
		for copyFrom in *args
			if copyFrom._Min and (not copyTo._Min or copyTo._Min and copyFrom._Min > copyTo._Min)
				copyTo._Min = copyFrom._Min
			if copyFrom._Max and (not copyTo._Max or copyTo._Max and copyFrom._Max < copyTo._Max)
				copyTo._Max = copyFrom._Max
			copyTo._Round = copyFrom._Round or copyTo._Round

		copyTo

	[=[
	Function: UsageShort
	TODO
	]=]
	UsageShort: (str = "" using nil) =>
		if @_Min and @_Min == @_Max
			str ..= tostring @_Min

		else
			str ..= @_Min .. "<=" if @_Min
			str ..= "x"
			str ..= "<=" .. @_Max if @_Max

		super\UsageShort str

	[=[
	Function: UsageLong
	TODO
	]=]
	UsageLong: (str = "" using nil) =>
		super\UsageLong str

		str .. "Min:   #{@_Min}"
		str .. "Max:   #{@_Max}"
		str .. "Round: #{@_Round}"

[=[
Class: ArgTime
The argument class used for a timespan using natural language.

Passes:
	A *number* of seconds, defaulting to _0_.

Revisions:
	1.0.0 - Initial.
]=]
class ArgTime extends ArgNum
	[=[
	Variables: ArgNum Variables
	All these variables are optional, with sensible defaults.

		Min - A *number, string, or nil* specifying the minimum value for the argument.
		Max - A *number, string, or nil* specifying the maximum value for the argument.
	]=]
	Min: nil
	Max: nil

[=[
Class: ArgString
The argument class used for string arguments.

Passes:
	A *string* value, defaulting to _0_.

Revisions:
	1.0.0 - Initial.
]=]
class ArgString extends Arg
	[=[
	Variables: ArgString Variables
	All these variables are optional, with sensible defaults.

		Default - A *string*, defaults to _""_. If an argument is optional and unspecified, this value is used.
		RestrictToCompletes - A *boolean*, defaults to _false_.
			If true, the argument passed will /always/ be one of the arguments from the <Completes> table.
		Completes           - A *table or nil* of auto-completes (suggestions) for the argument.

	]=]
	Default:             ""
	RestrictToCompletes: false
	Completes:           nil

[=[
Class: ArgPlayerID
The argument class used for SteamID arguments.

Passes:
	A *table of strings or players* (between one and <MaximumTargets> items).
	Each item is either a valid SteamID or a connected player.

Revisions:
	1.0.0 - Initial.
]=]
class ArgPlayerID extends Arg
	[=[
	Variables: ArgPlayerID Variables
	All these variables are optional, with sensible defaults.

		Default            - A *string*, defaults to _"^"_ (keyword for the player calling the command).
			If an argument is optional and unspecified, this value is used.
		RestrictTarget     - A *string or nil* specifying the players this argument is allowed to target.
			This is passed to <TODO.GetUser()>. Nil indicates no restriction.
		MaximumTargets     - A *number*, defaulting to _1_, specifying the maximum number of players this argument can target.
		PassPlayerIfActive - A *boolean*. If true, will pass the player object if they are connected.

	]=]
	Default:            "^"
	RestrictTarget:     nil
	MaximumTargets:     1
	PassPlayerIfActive: false

[=[
Class: ArgPlayerActive
The argument class used for player arguments.

Passes:
	A *table of players* (between one and <MaximumTargets> items).

Revisions:
	1.0.0 - Initial.
]=]
class ArgPlayerActive extends ArgPlayerID


Using MoonScript awesome import keyword

Using MoonScript awesome import keyword

-- Easy type checking for MoonScript classes
oldType = type
export type = (value) ->
  baseType = oldType value
  if base_type == "table"
    cls = value.__class
    return cls.__name if cls
  baseType

-- Initialize java library
export java = require "java"

-- Initialize main non table
export non = {}

-- Initialize the modules
print "Initializing the accelerometer module"
non.accelerometer = require "non.accelerometer"

print "Initializing the audio module"
non.audio = require "non.audio"

print "Initializing the compass module"
non.compass = require "non.compass"

print "Initializing the filesystem module"
non.filesystem = require "non.filesystem"

print "Initializing the graphics module"
non.graphics = require "non.graphics"

print "Initializing the keyboard module"
non.keyboard = require "non.keyboard"

print "Initializing the mouse module"
non.mouse = require "non.mouse"

print "Initializing the system module"
non.system = require "non.system"

print "Initializing the timer module"
non.timer = require "non.timer"

print "Initializing the touch module"
non.touch = require "non.touch"

print "Initializing the window module"
non.window = require "non.window"

-- Wrap the callbacks to be usefull for the engine
Constants = require "non.constants"
import keycodes, buttoncodes from Constants

non._keypressed = (keycode) ->
  if non.keypressed then non.keypressed keycodes[keycode]

non._keyreleased = (keycode) ->
  if non.keyreleased then non.keyreleased keycodes[keycode]

non._mousepressed = (x, y, buttoncode) ->
  if non.mousepressed then non.mousepressed buttoncodes[buttoncode]

non._mousereleased = (x, y, buttoncode) ->
  if non.mousereleased then non.mousereleased buttoncodes[buttoncode]

non._quit = ->
  if non.quit then non.quit!
  non.audio.stopAll!

-- Main loop function
non.run = ->
  dt = 0
  if non.timer then dt = non.timer.getDelta!
  if non.update then non.update dt

  if non.graphics
    non.graphics.clear!
    non.graphics.origin!
    if non.draw then non.draw!
    non.graphics.present!

require "main"

-- Easy type checking for MoonScript classes
oldType = type
export type = (value) ->
  baseType = oldType value
  if base_type == "table"
    cls = value.__class
    return cls.__name if cls
  baseType

-- Initialize java library
export java = require "java"

-- Initialize main non table
export non = {}

-- Initialize the modules
print "Initializing the accelerometer module"
non.accelerometer = require "non.accelerometer"

print "Initializing the audio module"
non.audio = require "non.audio"

print "Initializing the compass module"
non.compass = require "non.compass"

print "Initializing the filesystem module"
non.filesystem = require "non.filesystem"

print "Initializing the graphics module"
non.graphics = require "non.graphics"

print "Initializing the keyboard module"
non.keyboard = require "non.keyboard"

print "Initializing the mouse module"
non.mouse = require "non.mouse"

print "Initializing the system module"
non.system = require "non.system"

print "Initializing the timer module"
non.timer = require "non.timer"

print "Initializing the touch module"
non.touch = require "non.touch"

print "Initializing the window module"
non.window = require "non.window"

-- Wrap the callbacks to be usefull for the engine
c = require "non.constants"

non._keypressed = (keycode) ->
  if non.keypressed then non.keypressed c.keycodes[keycode]

non._keyreleased = (keycode) ->
  if non.keyreleased then non.keyreleased c.keycodes[keycode]

non._mousepressed = (x, y, buttoncode) ->
  if non.mousepressed then non.mousepressed c.buttoncodes[buttoncode]

non._mousereleased = (x, y, buttoncode) ->
  if non.mousereleased then non.mousereleased c.buttoncodes[buttoncode]

non._quit = ->
  if non.quit then non.quit!
  non.audio.stopAll!

-- Main loop function
non.run = ->
  dt = 0
  if non.timer then dt = non.timer.getDelta!
  if non.update then non.update dt

  if non.graphics
    non.graphics.clear!
    non.graphics.origin!
    if non.draw then non.draw!
    non.graphics.present!

require "main"


prep-kr: fix dummy quiz

prep-kr: fix dummy quiz

import Widget from require "lapis.html"

model = require('model')
msk_time = require "msk_time"
all = require('quiz.all')
kurs1 = require('kurs1')

Helpers = require('views.helpers')

class PrepKr extends Widget
  @include Helpers

  content: =>
    q = all[@quiz_name]
    if not q
      error(@_("No such quiz found"))
    quizs0 = model.kr(@quiz_name)
    name2quiz = {}
    for quiz in *quizs0
      if name2quiz[quiz.user] == nil
        name2quiz[quiz.user] = quiz
    quizs = {}
    for stud in *kurs1.excel_list
      quiz = name2quiz[stud]
      if not quiz
        quiz = {user: stud,
          all_tasks: ->
            {},
          color: ->
            'white'}
      table.insert(quizs, quiz)
    @detailed_results(q, quizs)

import Widget from require "lapis.html"

model = require('model')
msk_time = require "msk_time"
all = require('quiz.all')
kurs1 = require('kurs1')

Helpers = require('views.helpers')

class PrepKr extends Widget
  @include Helpers

  content: =>
    q = all[@quiz_name]
    if not q
      error(@_("No such quiz found"))
    quizs0 = model.kr(@quiz_name)
    name2quiz = {}
    for quiz in *quizs0
      if name2quiz[quiz.user] == nil
        name2quiz[quiz.user] = quiz
    quizs = {}
    for stud in *kurs1.excel_list
      quiz = name2quiz[stud]
      if not quiz
        quiz = {user: stud, all_tasks: -> {}}
      table.insert(quizs, quiz)
    @detailed_results(q, quizs)