Use vector props without mandating vector, in static methods.

Caste = require('vendor/caste/lib/caste')

class Vector extends Caste

	@getLength: (vector) => math.sqrt(vector.x^2 + vector.y^2)

	-- Return normalized vector
	@normalize: (vector) =>
		length = @getLength(vector)
		return @(vector.x / length, vector.y / length)

	-- Return vector scaled to a specified length
	@scale: (vector, length = 1) =>
		if @getLength(vector) == length then return vector
		vector = @normalize(vector)
		if length != 1 then vector *= length
		return vector

	-- Return vector of max length
	@truncate: (vector, length = 1) =>
		if @getLength(vector) <= length then return vector
		vector = @normalize(vector)
		if length != 1 then vector *= length
		return vector

	new: (x, y) =>
		-- @param {Vector}
		-- @param {Number}
		if _.isTable(x) and _.isNumber(y)
			x = @@scale(x, y)
			y = nil

		if _.isTable(x)
			if x.x or x.y
				-- @param {Table{/(x|y)/ => Number}}
				@x = x.x
				@y = x.y
			elseif x[1] or x[2]
				-- @param {Array{Number}}
				@x = x[1]
				@y = x[2]
		else
			-- @param {Number}
			-- @param {Number}
			@x = x
			@y = y

	getLength: () => math.sqrt(@x^2 + @y^2)

	apply: (method, ...) =>
		vector = @@[method](@, ...)
		@x = vector.x
		@y = vector.y

	clone: () => @@(@x, @y)

	__tostring: () => "#{@@name}(#{@x}, #{@y})##{@getLength()}"
	toTuple: () => @x, @y
	toArray: () => { @x, @y }
	toTable: () => { x: @x, y: @y, length: @getLength() }

	__eq: (other) => @x == other.x and @y == other.y
	__le: (other) => @x <= other.x and @y <= other.y
	__lt: (other) => (@x < other.x and @y <= other.y) or (@x <= other.x and @y < other.y)

	__unm: () => @@(-@x, -@y)
	__add: (other) => Vector(@x + other.x, @y + other.y)
	__sub: (other) => Vector(@x - other.x, @y - other.y)
	__mul: (other) =>
		if _.isNumber(other) then return Vector(@x * other, @y * other)
		-- dot product
		return @x * other.x + @y * other.y
	__div: (other) =>
		if _.isNumber(other) then return Vector(@x / other, @y / other)
		-- cross product
		-- TODO

Caste = require('vendor/caste/lib/caste')

class Vector extends Caste

	@getLength: (vector) => math.sqrt(vector.x^2 + vector.y^2)

	-- Return normalized vector
	@normalize: (vector) =>
		if type(vector.getLength) != 'function' then vector = @(vector)
		return vector / @getLength(vector)

	-- Return vector scaled to a specified length
	@scale: (vector, length = 1) =>
		if @getLength(vector) == length then return vector
		vector = @normalize(vector)
		if length != 1 then vector *= length
		return vector

	-- Return vector of max length
	@truncate: (vector, length = 1) =>
		if @getLength(vector) <= length then return vector
		vector = @normalize(vector)
		if length != 1 then vector *= length
		return vector

	new: (x, y) =>
		-- @param {Vector}
		-- @param {Number}
		if _.isTable(x) and _.isNumber(y)
			x = @@scale(x, y)
			y = nil

		if _.isTable(x)
			if x.x or x.y
				-- @param {Table{/(x|y)/ => Number}}
				@x = x.x
				@y = x.y
			elseif x[1] or x[2]
				-- @param {Array{Number}}
				@x = x[1]
				@y = x[2]
		else
			-- @param {Number}
			-- @param {Number}
			@x = x
			@y = y

	getLength: () => math.sqrt(@x^2 + @y^2)

	apply: (method, ...) =>
		vector = @@[method](@, ...)
		@x = vector.x
		@y = vector.y

	clone: () => @@(@x, @y)

	__tostring: () => "#{@@name}(#{@x}, #{@y})##{@getLength()}"
	toTuple: () => @x, @y
	toArray: () => { @x, @y }
	toTable: () => { x: @x, y: @y, length: @getLength() }

	__eq: (other) => @x == other.x and @y == other.y
	__le: (other) => @x <= other.x and @y <= other.y
	__lt: (other) => (@x < other.x and @y <= other.y) or (@x <= other.x and @y < other.y)

	__unm: () => @@(-@x, -@y)
	__add: (other) => Vector(@x + other.x, @y + other.y)
	__sub: (other) => Vector(@x - other.x, @y - other.y)
	__mul: (other) =>
		if _.isNumber(other) then return Vector(@x * other, @y * other)
		-- dot product
		return @x * other.x + @y * other.y
	__div: (other) =>
		if _.isNumber(other) then return Vector(@x / other, @y / other)
		-- cross product
		-- TODO


acl logic fix

acl logic fix

import Model, enum from require "lapis.db.model"
import create_table, types, add_column from require "lapis.db.schema"

Users = require "lazuli.modules.user_management.models.users"
ACL_Entries = require "models.acl_entries"

class ACLs extends Model

  -- user is one of:
  --  * "lazuli.modules.user_management.models.users" instance
  --  * user id (number) to be found by that model
  --  * nil if no user is logged in/applicable
  matchUser: do
    _logic= (user)->
      entries=ACL_Entries\select "where acl_id = ? order by position asc nulls first, id", @id
      for entry in *entries
        ret, err=entry\matchUser user
        return nil, err if ret == nil and err
        return ret if type(ret)=="boolean"
    (user,return_default=true)=>
      if user
        if type(user)=="number"
          user=Users\find user
        return nil, "user not found" unless user
      cname=(user and tostring(user.id) or "[N]").."-"..@id
      cached=ngx.shared.acl_cache\get cname
      return @default_policy if cached == "[D]"
      return cached if cached ~= nil
      ret,err=_logic user
      if type(ret)=="boolean"
        ngx.shared.acl_cache\set cname, ret, 10
      if not ret and not err and return_default
        ngx.shared.acl_cache\set cname, "[D]", 10
        return @default_policy
      return ret, err

  @relations: {
    {"user", belongs_to: "Users"}
  }

  @get_relation_model: (name)=> switch name
    when "Users"
      require "lazuli.modules.user_management.models.users"
    when "ACLs"
      require "models.acls"

  @migrations: {
    ->
      create_table "acls", {
        {"id",             types.serial}
        {"user_id",        types.integer}
        {"name",           types.varchar}
        {"default_policy", types.boolean}
        "PRIMARY KEY (id)"
      }
  }

import Model, enum from require "lapis.db.model"
import create_table, types, add_column from require "lapis.db.schema"

Users = require "lazuli.modules.user_management.models.users"
ACL_Entries = require "models.acl_entries"

class ACLs extends Model

  -- user is one of:
  --  * "lazuli.modules.user_management.models.users" instance
  --  * user id (number) to be found by that model
  --  * nil if no user is logged in/applicable
  matchUser: do
    _logic= (user)->
      entries=ACL_Entries\select "where acl_id = ? order by position asc nulls first, id", @id
      for entry in *entries
        ret, err=entry\matchUser user
        return nil, err if ret == nil and err
        return ret if type(ret)=="boolean"
    (user,return_default=true)=>
      if user
        if type(user)=="number"
          user=Users\find user
        return nil, "user not found" unless user
      cname=(user and tostring(user.id) or "[nil]").."-"..@id
      cached=ngx.shared.acl_cache\get cname
      return @default_policy if cached == "[default]"
      return cached if cached ~= nil
      ret,err=_logic user
      if type(ret)=="boolean"
        ngx.shared.acl_cache\set cname, ret, 10
      if not ret and not err and return_default
        ngx.shared.acl_cache\set cname, "[default]", 10
        return @default_policy
      return ret, err

  @relations: {
    {"user", belongs_to: "Users"}
  }

  @get_relation_model: (name)=> switch name
    when "Users"
      require "lazuli.modules.user_management.models.users"
    when "ACLs"
      require "models.acls"

  @migrations: {
    ->
      create_table "acls", {
        {"id",             types.serial}
        {"user_id",        types.integer}
        {"name",           types.varchar}
        {"default_policy", types.boolean}
        "PRIMARY KEY (id)"
      }
  }


alot: Only match once per line.

alot: Only match once per line.

alot = (source, destination) =>
  say 'http://hyperboleandahalf.blogspot.no/2010/04/alot-is-better-than-you-at-everything.html'
PRIVMSG:
  '.*[aA][lL][oO][tT].*': alot

alot = (source, destination) =>
  say 'http://hyperboleandahalf.blogspot.no/2010/04/alot-is-better-than-you-at-everything.html'
PRIVMSG:
  ' [aA][lL][oO][tT] ': alot
  '^[aA][lL][oO][tT] ': alot
  ' [aA][lL][oO][tT]$': alot
  '[aA][lL][oO][tT]%p': alot
  '%p[aA][lL][oO][tT]%p': alot
  '^[aA][lL][oO][tT]$': alot


Added utils.Ease

Added utils.Ease

export ^

-- Static class with useful easer functions that can be used by Tweens.
--
-- Operation of in/out easers:
--
-- in(t)
--		return t
-- out(t)
-- 		return 1 - in(1 - t)
-- inOut(t)
-- 		return (t <= .5) ? in(t * 2) / 2 : out(t * 2 - 1) / 2 + .5
class ease
	-- Easing constants.
	B1 = 1 / 2.75
	B2 = 2 / 2.75
	B3 = 1.5 / 2.75
	B4 = 2.5 / 2.75
	B5 = 2.25 / 2.75
	B6 = 2.625 / 2.75
	PI = math.pi
	PI2 = math.pi / 2
	EL = 2 * PI / 0.45

	-- Quadratic in.
	quadIn: (t) ->t^2

	-- Quadratic out.
	quadOut: (t) -> -t * (t - 2)

	-- Quadratic in and out.
	quadInOut: (t) -> if t <= .5 then t^2 * 2 else 1 - t * (t-1) * 2

	-- Cubic in.
	cubeIn: (t) -> t^3

	-- Cubic out.
	cubeOut: (t) -> 1 + t * (t-1)^2

	-- Cubic in and out.
	cubeInOut: (t) -> if t <= .5 then t^3 * 4 else 1 + t * (t-1)^2 * 4

	-- Quart in.
	quartIn: (t) -> t^4

	-- Quart out.
	quartOut: (t) ->
		t -= 1
		1 - t^4

	-- Quart in and out.
	quartInOut: (t) -> if t <= .5
			t^4 * 8
		else
			t = t * 2 - 2
			(1 - t^4) / 2 + .5

	-- Quint in.
	quintIn: (t) -> t^5

	-- Quint out.
	quintOut: (t) ->
		t -= 1
		t^5 + 1

	-- Quint in and out.
	quintInOut: (t) ->
		t *= 2
		if t < 1
			(t^5) / 2
		else
			t -= 2
			(t^5 + 2) / 2

	-- Sine in.
	sineIn: (t) -> -math.cos(PI2 * t) + 1

	-- Sine out.
	sineOut: (t) -> math.sin(PI2 * t)

	-- Sine in and out.
	sineInOut: (t) -> -math.cos(PI * t) / 2 + .5

	-- Bounce in.
	bounceIn: (t)->
		t = 1 - t
		return 1 - 7.5625 * t * t if t < B1
		return 1 - (7.5625 * (t - B3) * (t - B3) + .75) if t < B2
		return 1 - (7.5625 * (t - B5) * (t - B5) + .9375) if t < B4
		return 1 - (7.5625 * (t - B6) * (t - B6) + .984375)

	-- Bounce out.
	bounceOut: (t)->
		return 7.5625 * t * t if t < B1
		return 7.5625 * (t - B3) * (t - B3) + .75 if t < B2
		return 7.5625 * (t - B5) * (t - B5) + .9375 if t < B4
		return 7.5625 * (t - B6) * (t - B6) + .984375

	-- Bounce in and out.
	bounceInOut: (t)->
		if t < .5
			t = 1 - t * 2
			return (1 - 7.5625 * t * t) / 2 if t < B1
			return (1 - (7.5625 * (t - B3) * (t - B3) + .75)) / 2 if t < B2
			return (1 - (7.5625 * (t - B5) * (t - B5) + .9375)) / 2 if t < B4
			return (1 - (7.5625 * (t - B6) * (t - B6) + .984375)) / 2

		t = t * 2 - 1
		return (7.5625 * t * t) / 2 + .5 if t < B1
		return (7.5625 * (t - B3) * (t - B3) + .75) / 2 + .5 if t < B2
		return (7.5625 * (t - B5) * (t - B5) + .9375) / 2 + .5 if t < B4
		return (7.5625 * (t - B6) * (t - B6) + .984375) / 2 + .5

	-- Circle in.
	circIn: (t) -> -(math.sqrt(1 - t^2) - 1)

	-- Circle out.
	circOut: (t) -> math.sqrt(1 - (t - 1)^2)

	-- Circle in and out.
	circInOut: (t)-> if t <= .5
			(math.sqrt(1 - t^2 * 4) - 1) / -2
		else
			(math.sqrt(1 - (t * 2 - 2)^2) + 1) / 2

	-- Exponential in.
	expoIn: (t) -> math.pow(2, 10 * (t - 1))

	-- Exponential out.
	expoOut: (t) -> -math.pow(2, -10 * t) + 1

	-- Exponential in and out.
	expoInOut: (t)-> if t < .5
			math.pow(2, 10 * (t * 2 - 1)) / 2
		else
			(-math.pow(2, -10 * (t * 2 - 1)) + 2) / 2

	-- Back in.
	backIn: (t) -> t^2 * (2.70158 * t - 1.70158)

	-- Back out.
	backOut: (t)->
		t2 = t - 1
		1 - (t) * (t2) * (-2.70158 * t2 - 1.70158)

	-- Back in and out.
	backInOut: (t)->
		t *= 2
		return t^2 * (2.70158 * t - 1.70158) / 2 if t < 1
		t -= 2
		return (1 - (t + 1) * (t) * (-2.70158 * t - 1.70158)) / 2 + .5

Ease = ease!


improve hammerspoon profile actions

improve hammerspoon profile actions

require 'moonscript'

import unpack from table

class Profile
  new: (title, screens, config) =>
    @title = title
    @screens = screens
    @config = config

  _actionsFor: (appName) =>
    actions = @config[appName]
    if actions then actions else @config["_"]

  _activateForApp: (app) =>
    actions = @_actionsFor(app\title!)
    if actions then for action in *actions do for win in *app\allWindows! do action\perform(win)

  isActive: =>
    for screen in *hs.screen.allScreens()
      if hs.fnutils.contains(@screens, screen\id!) then return true
    return false

  activateFor: (appName) =>
    app = hs.appfinder.appFromName(appName)
    if app then @_activateForApp(app)

  activate: =>
    hs.alert.show("Arranging #{@title}", 1)
    for app in *hs.application.runningApplications! do @_activateForApp(app)

{
  :Profile
}

require 'moonscript'

import unpack from table

class Profile
  new: (title, screens, config) =>
    @title = title
    @screens = screens
    @config = config

  isActive: =>
    for screen in *hs.screen.allScreens()
      if hs.fnutils.contains(@screens, screen\id!) then return true
    return false

  activateFor: (appname) =>
    app = hs.appfinder.appFromName(appname)
    if not app then return {}
    actions = @config[appname]
    windows = app\allWindows!
    for action in *actions
      for win in *windows do action\perform(win)
    return windows

  activate: =>
    hs.alert.show("Arranging #{@title}", 1)
    processed = [w for appname, _ in pairs(@config) for w in *@activateFor(appname)]

    -- Apply actions to all remaining windows
    actions = @config["_"]
    if not actions then return

    windows = [w for w in *hs.window.allWindows! when not hs.fnutils.contains(processed, w)]
    for action in *actions
      for win in *windows do action\perform(win)

{
  :Profile
}


utf8: new function throw. （╯°□°）╯︵

utf8: new function throw. （╯°□°）╯︵

Former-commit-id: b0568499919700a6bc249a9a4f2c16a0cb29a825 [formerly d56bc6bc901d6c591d2252be2f7a784ad8967eba]
Former-commit-id: 6bb2d0f05a25793a65e9e0ae0c486a3d0571d088

html2unicode = require'html'
math = require'math'

an = [[ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz0123456789,.?!"'`()[]{}<>&_]]
charmaps = {
ci: 'ⒶⒷⒸⒹⒺⒻⒼⒽⒾⒿⓀⓁⓂⓃⓄⓅⓆⓇⓈⓉⓊⓋⓌⓍⓎⓏⓐⓑⓒⓓⓔⓕⓖⓗⓘⓙⓚⓛⓜⓝⓞⓟⓠⓡⓢⓣⓤⓥⓦⓧⓨⓩ⓪①②③④⑤⑥⑦⑧⑨'
bl: '𝔄𝔅ℭ𝔇𝔈𝔉𝔊ℌℑ𝔍𝔎𝔏𝔐𝔑𝔒𝔓𝔔ℜ𝔖𝔗𝔘𝔙𝔚𝔛𝔜ℨ𝔞𝔟𝔠𝔡𝔢𝔣𝔤𝔥𝔦𝔧𝔨𝔩𝔪𝔫𝔬𝔭𝔮𝔯𝔰𝔱𝔲𝔳𝔴𝔵𝔶𝔷'
ud: [[∀BƆDƎℲפHIſK˥WNOԀQRS┴∩ΛMX⅄Zɐqɔpǝɟƃɥᴉɾʞlɯuodbɹsʇnʌʍxʎz0ƖᄅƐㄣϛ9ㄥ86'˙¿¡,,,)(][}{><⅋‾]]
nc: [[🅐🅑🅒🅓🅔🅕🅖🅗🅘🅙🅚🅛🅜🅝🅞🅟🅠🅡🅢🅣🅤🅥🅦🅧🅨🅩🅐🅑🅒🅓🅔🅕🅖🅗🅘🅙🅚🅛🅜🅝🅞🅟🅠🅡🅢🅣🅤🅥🅦🅧🅨🅩⓿]]
sq: [[🄰🄱🄲🄳🄴🄵🄶🄷🄸🄹🄺🄻🄼🄽🄾🄿🅀🅁🅂🅃🅄🅅🅆🅇🅈🅉🄰🄱🄲🄳🄴🄵🄶🄷🄸🄹🄺🄻🄼🄽🄾🄿🅀🅁🅂🅃🅄🅅🅆🅇🅈🅉0123456789,⊡]]
ns: [[🅰🅱🅲🅳🅴🅵🅶🅷🅸🅹🅺🅻🅼🅽🅾🅿🆀🆁🆂🆃🆄🆅🆆🆇🆈🆉🅰🅱🅲🅳🅴🅵🅶🅷🅸🅹🅺🅻🅼🅽🅾🅿🆀🆁🆂🆃🆄🆅🆆🆇🆈🆉]]
ds: [[𝔸𝔹ℂ𝔻𝔼𝔽𝔾ℍ𝕀𝕁𝕂𝕃𝕄ℕ𝕆ℙℚℝ𝕊𝕋𝕌𝕍𝕎𝕏𝕐ℤ𝕒𝕓𝕔𝕕𝕖𝕗𝕘𝕙𝕚𝕛𝕜𝕝𝕞𝕟𝕠𝕡𝕢𝕣𝕤𝕥𝕦𝕧𝕨𝕩𝕪𝕫𝟘𝟙𝟚𝟛𝟜𝟝𝟞𝟟𝟠𝟡]]
bo: [[𝐀𝐁𝐂𝐃𝐄𝐅𝐆𝐇𝐈𝐉𝐊𝐋𝐌𝐍𝐎𝐏𝐐𝐑𝐒𝐓𝐔𝐕𝐖𝐗𝐘𝐙𝐚𝐛𝐜𝐝𝐞𝐟𝐠𝐡𝐢𝐣𝐤𝐥𝐦𝐧𝐨𝐩𝐪𝐫𝐬𝐭𝐮𝐯𝐰𝐱𝐲𝐳𝟎𝟏𝟐𝟑𝟒𝟓𝟔𝟕𝟖𝟗]]
bi: [[𝑨𝑩𝑪𝑫𝑬𝑭𝑮𝑯𝑰𝑱𝑲𝑳𝑴𝑵𝑶𝑷𝑸𝑹𝑺𝑻𝑼𝑽𝑾𝑿𝒀𝒁𝒂𝒃𝒄𝒅𝒆𝒇𝒈𝒉𝒊𝒋𝒌𝒍𝒎𝒏𝒐𝒑𝒒𝒓𝒔𝒕𝒖𝒗𝒘𝒙𝒚𝒛0123456789]]
bs: [[𝓐𝓑𝓒𝓓𝓔𝓕𝓖𝓗𝓘𝓙𝓚𝓛𝓜𝓝𝓞𝓟𝓠𝓡𝓢𝓣𝓤𝓥𝓦𝓧𝓨𝓩𝓪𝓫𝓬𝓭𝓮𝓯𝓰𝓱𝓲𝓳𝓴𝓵𝓶𝓷𝓸𝓹𝓺𝓻𝓼𝓽𝓾𝓿𝔀𝔁𝔂𝔃]]
pt: [[⒜⒝⒞⒟⒠⒡⒢⒣⒤⒥⒦⒧⒨⒩⒪⒫⒬⒭⒮⒯⒰⒱⒲⒳⒴⒵⒜⒝⒞⒟⒠⒡⒢⒣⒤⒥⒦⒧⒨⒩⒪⒫⒬⒭⒮⒯⒰⒱⒲⒳⒴⒵0⑴⑵⑶⑷⑸⑹⑺⑻⑼]]
}
codepoints = (str) ->
  str\gmatch("[%z\1-\127\194-\244][\128-\191]*")

-- Construct a table which can be used for lookup replacement later
-- Iterate over normal ascii and find counterparts in the weirdo strings as substrings
maps = {}
for charmap, chars in pairs charmaps
  i = 1
  maps[charmap] = {}
  for uchar in string.gmatch(chars, "([%z\1-\127\194-\244][\128-\191]*)")
    maps[charmap][an\sub(i,i)] = uchar
    i = i +1

unichr = (n) ->
  html2unicode('&#x%x;'\format(n))

wireplace = (offset, arg) ->
    s = arg or ''
    t = {}
    for i = 1, #s
      bc = string.byte(s, i, i)
      -- Replace space width ideographic space for fullwidth offset
      if bc == 32 and offset == 0xFEE0
        t[#t + 1] = '\227\128\128'
      elseif bc == 32
        t[#t + 1] = ' '
      elseif bc < 0x80 then
        t[#t + 1] = html2unicode("&#" .. (offset + bc) .. ";")
      else
        t[#t + 1] = s\sub(i, i)

    table.concat(t, "")

remap = (map, s) ->
  table.concat [map[s\sub(i,i)] or s\sub(i,i) for i=1, #s], ''

zalgo = (text, intensity=50) ->
  zalgo_chars = {}
  for i=0x0300, 0x036f
    zalgo_chars[i-0x2ff] = unichr(i)

  zalgo_chars[#zalgo_chars + 1] = unichr(0x0488)
  zalgo_chars[#zalgo_chars + 0] = unichr(0x0489)

  zalgoized = {}
  for letter in codepoints(text)
    zalgoized[#zalgoized + 1] = letter
    zalgo_num = math.random(1, intensity)
    for i=1, zalgo_num
      zalgoized[#zalgoized + 1] = zalgo_chars[math.random(1, #zalgo_chars)]
  table.concat(zalgoized)

PRIVMSG:
  '^%pwide (.+)$': (source, destination, arg) =>
    say wireplace(0xFEE0, arg)
  '^%pblackletter (.+)$': (source, destination, arg) =>
    say remap(maps.bl, arg)
  '^%pcircled (.+)$': (source, destination, arg) =>
    say remap(maps.ci, arg)
  '^%pzalgo (.+)$': (source, destination, arg) =>
    say zalgo(arg, 7)
  '^%pupsidedown (.+)$': (source, destination, arg) =>
    say remap(maps.ud, arg)
  '^%pflip (.+)$': (source, destination, arg) =>
    say remap(maps.ud, arg)
  '^%pthrow (.+)$': (source, destination, arg) =>
    say "（╯°□°）╯︵ #{remap maps.ud, arg}"
  '^%pparanthesized (.+)$': (source, destination, arg) =>
    say remap(maps.pt, arg)
  '^%pnegcircle (.+)$': (source, destination, arg) =>
    say remap(maps['nc'], arg)
  '^%psquare (.+)$': (source, destination, arg) =>
    say remap(maps.sq, arg)
  '^%pnegsquare (.+)$': (source, destination, arg) =>
    say remap(maps.ns, arg)
  '^%pdoublestruck (.+)$': (source, destination, arg) =>
    say remap(maps.ds, arg)
  '^%pubold (.+)$': (source, destination, arg) =>
    say remap(maps.bo, arg)
  '^%pbolditalic (.+)$': (source, destination, arg) =>
    say remap(maps.bi, arg)
  '^%pboldscript (.+)$': (source, destination, arg) =>
    say remap(maps.bs, arg)
  '^%putfuk (.+)$': (source, destination, arg) =>
    keys = [x for x,_ in pairs(maps)]
    say table.concat([remap(maps[keys[math.random(#keys)]], letter) for letter in codepoints(arg)])

html2unicode = require'html'
math = require'math'

an = [[ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz0123456789,.?!"'`()[]{}<>&_]]
charmaps = {
ci: 'ⒶⒷⒸⒹⒺⒻⒼⒽⒾⒿⓀⓁⓂⓃⓄⓅⓆⓇⓈⓉⓊⓋⓌⓍⓎⓏⓐⓑⓒⓓⓔⓕⓖⓗⓘⓙⓚⓛⓜⓝⓞⓟⓠⓡⓢⓣⓤⓥⓦⓧⓨⓩ⓪①②③④⑤⑥⑦⑧⑨'
bl: '𝔄𝔅ℭ𝔇𝔈𝔉𝔊ℌℑ𝔍𝔎𝔏𝔐𝔑𝔒𝔓𝔔ℜ𝔖𝔗𝔘𝔙𝔚𝔛𝔜ℨ𝔞𝔟𝔠𝔡𝔢𝔣𝔤𝔥𝔦𝔧𝔨𝔩𝔪𝔫𝔬𝔭𝔮𝔯𝔰𝔱𝔲𝔳𝔴𝔵𝔶𝔷'
ud: [[∀BƆDƎℲפHIſK˥WNOԀQRS┴∩ΛMX⅄Zɐqɔpǝɟƃɥᴉɾʞlɯuodbɹsʇnʌʍxʎz0ƖᄅƐㄣϛ9ㄥ86'˙¿¡,,,)(][}{><⅋‾]]
nc: [[🅐🅑🅒🅓🅔🅕🅖🅗🅘🅙🅚🅛🅜🅝🅞🅟🅠🅡🅢🅣🅤🅥🅦🅧🅨🅩🅐🅑🅒🅓🅔🅕🅖🅗🅘🅙🅚🅛🅜🅝🅞🅟🅠🅡🅢🅣🅤🅥🅦🅧🅨🅩⓿]]
sq: [[🄰🄱🄲🄳🄴🄵🄶🄷🄸🄹🄺🄻🄼🄽🄾🄿🅀🅁🅂🅃🅄🅅🅆🅇🅈🅉🄰🄱🄲🄳🄴🄵🄶🄷🄸🄹🄺🄻🄼🄽🄾🄿🅀🅁🅂🅃🅄🅅🅆🅇🅈🅉0123456789,⊡]]
ns: [[🅰🅱🅲🅳🅴🅵🅶🅷🅸🅹🅺🅻🅼🅽🅾🅿🆀🆁🆂🆃🆄🆅🆆🆇🆈🆉🅰🅱🅲🅳🅴🅵🅶🅷🅸🅹🅺🅻🅼🅽🅾🅿🆀🆁🆂🆃🆄🆅🆆🆇🆈🆉]]
ds: [[𝔸𝔹ℂ𝔻𝔼𝔽𝔾ℍ𝕀𝕁𝕂𝕃𝕄ℕ𝕆ℙℚℝ𝕊𝕋𝕌𝕍𝕎𝕏𝕐ℤ𝕒𝕓𝕔𝕕𝕖𝕗𝕘𝕙𝕚𝕛𝕜𝕝𝕞𝕟𝕠𝕡𝕢𝕣𝕤𝕥𝕦𝕧𝕨𝕩𝕪𝕫𝟘𝟙𝟚𝟛𝟜𝟝𝟞𝟟𝟠𝟡]]
bo: [[𝐀𝐁𝐂𝐃𝐄𝐅𝐆𝐇𝐈𝐉𝐊𝐋𝐌𝐍𝐎𝐏𝐐𝐑𝐒𝐓𝐔𝐕𝐖𝐗𝐘𝐙𝐚𝐛𝐜𝐝𝐞𝐟𝐠𝐡𝐢𝐣𝐤𝐥𝐦𝐧𝐨𝐩𝐪𝐫𝐬𝐭𝐮𝐯𝐰𝐱𝐲𝐳𝟎𝟏𝟐𝟑𝟒𝟓𝟔𝟕𝟖𝟗]]
bi: [[𝑨𝑩𝑪𝑫𝑬𝑭𝑮𝑯𝑰𝑱𝑲𝑳𝑴𝑵𝑶𝑷𝑸𝑹𝑺𝑻𝑼𝑽𝑾𝑿𝒀𝒁𝒂𝒃𝒄𝒅𝒆𝒇𝒈𝒉𝒊𝒋𝒌𝒍𝒎𝒏𝒐𝒑𝒒𝒓𝒔𝒕𝒖𝒗𝒘𝒙𝒚𝒛0123456789]]
bs: [[𝓐𝓑𝓒𝓓𝓔𝓕𝓖𝓗𝓘𝓙𝓚𝓛𝓜𝓝𝓞𝓟𝓠𝓡𝓢𝓣𝓤𝓥𝓦𝓧𝓨𝓩𝓪𝓫𝓬𝓭𝓮𝓯𝓰𝓱𝓲𝓳𝓴𝓵𝓶𝓷𝓸𝓹𝓺𝓻𝓼𝓽𝓾𝓿𝔀𝔁𝔂𝔃]]
pt: [[⒜⒝⒞⒟⒠⒡⒢⒣⒤⒥⒦⒧⒨⒩⒪⒫⒬⒭⒮⒯⒰⒱⒲⒳⒴⒵⒜⒝⒞⒟⒠⒡⒢⒣⒤⒥⒦⒧⒨⒩⒪⒫⒬⒭⒮⒯⒰⒱⒲⒳⒴⒵0⑴⑵⑶⑷⑸⑹⑺⑻⑼]]
}
codepoints = (str) ->
  str\gmatch("[%z\1-\127\194-\244][\128-\191]*")

-- Construct a table which can be used for lookup replacement later
-- Iterate over normal ascii and find counterparts in the weirdo strings as substrings
maps = {}
for charmap, chars in pairs charmaps
  i = 1
  maps[charmap] = {}
  for uchar in string.gmatch(chars, "([%z\1-\127\194-\244][\128-\191]*)")
    maps[charmap][an\sub(i,i)] = uchar
    i = i +1

unichr = (n) ->
  html2unicode('&#x%x;'\format(n))

wireplace = (offset, arg) ->
    s = arg or ''
    t = {}
    for i = 1, #s
      bc = string.byte(s, i, i)
      -- Replace space width ideographic space for fullwidth offset
      if bc == 32 and offset == 0xFEE0
        t[#t + 1] = '\227\128\128'
      elseif bc == 32
        t[#t + 1] = ' '
      elseif bc < 0x80 then
        t[#t + 1] = html2unicode("&#" .. (offset + bc) .. ";")
      else
        t[#t + 1] = s\sub(i, i)

    table.concat(t, "")

remap = (map, s) ->
  table.concat [map[s\sub(i,i)] or s\sub(i,i) for i=1, #s], ''

zalgo = (text, intensity=50) ->
  zalgo_chars = {}
  for i=0x0300, 0x036f
    zalgo_chars[i-0x2ff] = unichr(i)

  zalgo_chars[#zalgo_chars + 1] = unichr(0x0488)
  zalgo_chars[#zalgo_chars + 0] = unichr(0x0489)

  zalgoized = {}
  for letter in codepoints(text)
    zalgoized[#zalgoized + 1] = letter
    zalgo_num = math.random(1, intensity)
    for i=1, zalgo_num
      zalgoized[#zalgoized + 1] = zalgo_chars[math.random(1, #zalgo_chars)]
  table.concat(zalgoized)

PRIVMSG:
  '^%pwide (.+)$': (source, destination, arg) =>
    say wireplace(0xFEE0, arg)
  '^%pblackletter (.+)$': (source, destination, arg) =>
    say remap(maps.bl, arg)
  '^%pcircled (.+)$': (source, destination, arg) =>
    say remap(maps.ci, arg)
  '^%pzalgo (.+)$': (source, destination, arg) =>
    say zalgo(arg, 7)
  '^%pupsidedown (.+)$': (source, destination, arg) =>
    say remap(maps.ud, arg)
  '^%pflip (.+)$': (source, destination, arg) =>
    say remap(maps.ud, arg)
  '^%pparanthesized (.+)$': (source, destination, arg) =>
    say remap(maps.pt, arg)
  '^%pnegcircle (.+)$': (source, destination, arg) =>
    say remap(maps['nc'], arg)
  '^%psquare (.+)$': (source, destination, arg) =>
    say remap(maps.sq, arg)
  '^%pnegsquare (.+)$': (source, destination, arg) =>
    say remap(maps.ns, arg)
  '^%pdoublestruck (.+)$': (source, destination, arg) =>
    say remap(maps.ds, arg)
  '^%pubold (.+)$': (source, destination, arg) =>
    say remap(maps.bo, arg)
  '^%pbolditalic (.+)$': (source, destination, arg) =>
    say remap(maps.bi, arg)
  '^%pboldscript (.+)$': (source, destination, arg) =>
    say remap(maps.bs, arg)
  '^%putfuk (.+)$': (source, destination, arg) =>
    keys = [x for x,_ in pairs(maps)]
    say table.concat([remap(maps[keys[math.random(#keys)]], letter) for letter in codepoints(arg)])