<commit_before>//
//  ChatListener.swift
//  FireAlarm
//
//  Created by NobodyNada on 8/28/16.
//  Copyright © 2016 NobodyNada. All rights reserved.
//

import Foundation
import Dispatch

///A ChatListener parses commands from the chat room.
open class ChatListener {
	///The commands recognized by this ChatListener.
	open let commands: [Command.Type]
	
	open var info: Any? = nil
	
	///The name of the bot.  A message must start with this name to be recognized as a command.
	open var name = "@FireAlarm"
	
	///The number of characters of the name that must be included.
	///
	///
	///For example, if `name` is `@FireAlarm` and `minNameCharacters` is 4,
	///`@FireAlarm`, `@Fire`, and `@Fir` will all be recognized, but
	///`@FirTree`, `@Fi`, and `@Kyll` will not.
	open var minNameCharacters = 4
	
	///A closure to run when the bot shuts down.
	open var shutdownHandler: (StopReason) -> () = {reason in}
	
	///Runs a closure on shutdown.
	open func onShutdown(_ handler: @escaping (StopReason) -> ()) {
		shutdownHandler = handler
	}
	
	public enum StopReason {
		case halt
		case reboot
		case update
	}
	
	
	let commandQueue = DispatchQueue(label: "Command Queue", attributes: DispatchQueue.Attributes.concurrent)
	
	open var runningCommands = [Command]()
	
	
	
	fileprivate var pendingStopReason: StopReason? = nil
	
	fileprivate func runCommand(_ command: Command) {
		let required = type(of: command).privileges()
		let missing = command.message.user.missing(from: required)
		
		
		guard missing.isEmpty else {
			let message = "You need the \(formatArray(missing.names, conjunction: "and")) " +
			"\(pluralize(missing.names.count, "privilege")) to run that command."
			
			command.message.room.postReply(message, to: command.message)
			return
		}
		
		
		runningCommands.append(command)
		commandQueue.async {
			do {
				try command.run()
			}
			catch {
				handleError(error, "while running \"\(command.message.content)\"")
			}
			self.runningCommands.remove(at: self.runningCommands.index {$0 === command}!)
			if (self.pendingStopReason != nil) && self.runningCommands.isEmpty {
				self.shutdownHandler(self.pendingStopReason!)
			}
		}
	}
	
	fileprivate func handleCommand(_ message: ChatMessage) {
		var components = message.content.lowercased().components(separatedBy: CharacterSet.whitespaces)
		components.removeFirst()
		
		var args = [String]()
		
		var commandScores = [String:Int]()
		
		for command in commands {
			let usages = command.usage()
			
			for i in 0..<usages.count {
				var score = 0
				let usage = usages[i]
				args = []
				
				var match = true
				let usageComponents = usage.components(separatedBy: CharacterSet.whitespaces)
				let lastIndex = min(components.count, usageComponents.count)
				
				for i in 0..<lastIndex {
					let component = components[i]
					let usageComponent = usageComponents[i]
					
					if usageComponent == "*" {
						args.append(component)
					}
					else if usageComponent == "..." {
						//everything else is arguments; add them to the list
						args.append(contentsOf: components[i..<components.count])<commit_msg>fix for ignoring punctuation suffix<commit_after>//
//  ChatListener.swift
//  FireAlarm
//
//  Created by NobodyNada on 8/28/16.
//  Copyright © 2016 NobodyNada. All rights reserved.
//

import Foundation
import Dispatch

///A ChatListener parses commands from the chat room.
open class ChatListener {
	///The commands recognized by this ChatListener.
	open let commands: [Command.Type]
	
	open var info: Any? = nil
	
	///The name of the bot.  A message must start with this name to be recognized as a command.
	open var name = "@SockFireAlarm"
	
	///The number of characters of the name that must be included.
	///
	///
	///For example, if `name` is `@FireAlarm` and `minNameCharacters` is 4,
	///`@FireAlarm`, `@Fire`, and `@Fir` will all be recognized, but
	///`@FirTree`, `@Fi`, and `@Kyll` will not.
	open var minNameCharacters = 4
	
	///A closure to run when the bot shuts down.
	open var shutdownHandler: (StopReason) -> () = {reason in}
	
	///Runs a closure on shutdown.
	open func onShutdown(_ handler: @escaping (StopReason) -> ()) {
		shutdownHandler = handler
	}
	
	public enum StopReason {
		case halt
		case reboot
		case update
	}
	
	
	let commandQueue = DispatchQueue(label: "Command Queue", attributes: DispatchQueue.Attributes.concurrent)
	
	open var runningCommands = [Command]()
	
	
	
	fileprivate var pendingStopReason: StopReason? = nil
	
	fileprivate func runCommand(_ command: Command) {
		let required = type(of: command).privileges()
		let missing = command.message.user.missing(from: required)
		
		
		guard missing.isEmpty else {
			let message = "You need the \(formatArray(missing.names, conjunction: "and")) " +
			"\(pluralize(missing.names.count, "privilege")) to run that command."
			
			command.message.room.postReply(message, to: command.message)
			return
		}
		
		
		runningCommands.append(command)
		commandQueue.async {
			do {
				try command.run()
			}
			catch {
				handleError(error, "while running \"\(command.message.content)\"")
			}
			self.runningCommands.remove(at: self.runningCommands.index {$0 === command}!)
			if (self.pendingStopReason != nil) && self.runningCommands.isEmpty {
				self.shutdownHandler(self.pendingStopReason!)
			}
		}
	}
	
	fileprivate func handleCommand(_ message: ChatMessage) {
		var components = message.content.lowercased().components(separatedBy: CharacterSet.whitespaces)
		components.removeFirst()
        
        var i: Int!
        i = 0
        
        while (i < components.count) {
            var suffix = String (components [i].characters.suffix(1))
            
            while (suffix == "?" || suffix == "." || suffix == "!") {
                components [i] = String (components [i].characters.dropLast())
                suffix = String (components [i].characters.suffix (1))
            }
            
            i = i + 1
        }
		
		var args = [String]()
		
		var commandScores = [String:Int]()
		
		for command in commands {
			let usages = command.usage()
			
			for i in 0..<usages.count {
				var score = 0
				let usage = usages[i]
				args = []
				
				var match = true
				let usageComponents = usage.components(separatedBy: CharacterSet.whitespaces)
				let lastIndex = min(components.count, usageComponents.count)
				
				for i in 0..<lastIndex {
					let component = components[i]
					let usageComponent = usageComponents[i]
					
					if usageComponent == "*" {
						args.append(component)
					}
					else if usageComponent == "..." {
						//everything else is arguments; add them to the list
						args.append(contentsOf: components[i..<components.count])
{'fraction_non_alphanumeric': 0.11892296185489903, 'fraction_numerical': 0.0050860134629768135, 'mean_word_length': 2.9609004739336493, 'pattern_counts': {'":': 0, '<': 10, '<?xml version=': 0, '>': 7, 'https://': 0, 'lorem ipsum': 0, 'www.': 0, 'xml': 0}, 'pii_count': 0, 'substrings_counts': 0, 'word_list_counts': {'cursed_substrings.json': 0, 'profanity_word_list.json': 0, 'sexual_word_list.json': 0, 'zh_pornsignals.json': 0}}
{'dir': 'git-commits-cleaned', 'id': '656454', 'n_tokens_mistral': 2349, 'n_tokens_neox': 1962, 'n_words': 603}