add MoonScript sample

add MoonScript sample

types = require "moonscript.types"
util = require "moonscript.util"
data = require "moonscript.data"

import reversed, unpack from util
import ntype, mtype, build, smart_node, is_slice, value_is_singular from types
import insert from table
import NameProxy, LocalName from require "moonscript.transform.names"

destructure = require "moonscript.transform.destructure"

local implicitly_return

class Run
  new: (@fn) =>
    self[1] = "run"

  call: (state) =>
    self.fn state

-- transform the last stm is a list of stms
-- will puke on group
apply_to_last = (stms, fn) ->
  -- find last (real) exp
  last_exp_id = 0
  for i = #stms, 1, -1
    stm = stms[i]
    if stm and mtype(stm) != Run
      last_exp_id = i
      break

  return for i, stm in ipairs stms
    if i == last_exp_id
      fn stm
    else
      stm

-- is a body a sindle expression/statement
is_singular = (body) ->
  return false if #body != 1
  if "group" == ntype body
    is_singular body[2]
  else
    true

find_assigns = (body, out={}) ->
  for thing in *body
    switch thing[1]
      when "group"
        find_assigns thing[2], out
      when "assign"
        table.insert out, thing[2] -- extract names
  out

hoist_declarations = (body) ->
  assigns = {}

  -- hoist the plain old assigns
  for names in *find_assigns body
    for name in *names
      table.insert assigns, name if type(name) == "string"

  -- insert after runs
  idx = 1
  while mtype(body[idx]) == Run do idx += 1

  table.insert body, idx, {"declare", assigns}

expand_elseif_assign = (ifstm) ->
  for i = 4, #ifstm
    case = ifstm[i]
    if ntype(case) == "elseif" and ntype(case[2]) == "assign"
      split = { unpack ifstm, 1, i - 1 }
      insert split, {
        "else", {
          {"if", case[2], case[3], unpack ifstm, i + 1}
        }
      }
      return split

  ifstm

constructor_name = "new"

with_continue_listener = (body) ->
  continue_name = nil
  {
    Run =>
      @listen "continue", ->
        unless continue_name
          continue_name = NameProxy"continue"
          @put_name continue_name
        continue_name

    build.group body

    Run =>
      return unless continue_name
      @put_name continue_name, nil
      @splice (lines) -> {
        {"assign", {continue_name}, {"false"}}
        {"repeat", "true", {
          lines
          {"assign", {continue_name}, {"true"}}
        }}
        {"if", {"not", continue_name}, {
          {"break"}
        }}
      }
  }

class Transformer
  new: (@transformers) =>
    @seen_nodes = setmetatable {}, __mode: "k"

  transform: (scope, node, ...) =>
    return node if @seen_nodes[node]
    @seen_nodes[node] = true
    while true
      transformer = @transformers[ntype node]
      res = if transformer
        transformer(scope, node, ...) or node
      else
        node
      return node if res == node
      node = res
    node

  bind: (scope) =>
    (...) -> @transform scope, ...

  __call: (...) => @transform ...

  can_transform: (node) =>
    @transformers[ntype node] != nil

construct_comprehension = (inner, clauses) ->
  current_stms = inner
  for _, clause in reversed clauses
    t = clause[1]
    current_stms = if t == "for"
      _, names, iter = unpack clause
      {"foreach", names, {iter}, current_stms}
    elseif t == "when"
      _, cond = unpack clause
      {"if", cond, current_stms}
    else
      error "Unknown comprehension clause: "..t
    current_stms = {current_stms}

  current_stms[1]

Statement = Transformer {
  root_stms: (body) =>
    apply_to_last body, implicitly_return @

  assign: (node) =>
    names, values = unpack node, 2
    -- bubble cascading assigns
    transformed = if #values == 1
      value = values[1]
      t = ntype value

      if t == "decorated"
        value = @transform.statement value
        t = ntype value

      if types.cascading[t]
        ret = (stm) ->
          if types.is_value stm
            {"assign", names, {stm}}
          else
            stm

        build.group {
          {"declare", names}
          @transform.statement value, ret, node
        }

    node = transformed or node

    if destructure.has_destructure names
      return destructure.split_assign node

    node

  continue: (node) =>
    continue_name = @send "continue"
    error "continue must be inside of a loop" unless continue_name
    build.group {
      build.assign_one continue_name, "true"
      {"break"}
    }

  export: (node) =>
    -- assign values if they are included
    if #node > 2
      if node[2] == "class"
        cls = smart_node node[3]
        build.group {
          {"export", {cls.name}}
          cls
        }
      else
        build.group {
          node
          build.assign {
            names: node[2]
            values: node[3]
          }
        }
    else
      nil

  update: (node) =>
    _, name, op, exp = unpack node
    op_final = op\match "^(.+)=$"
    error "Unknown op: "..op if not op_final
    exp = {"parens", exp} unless value_is_singular exp
    build.assign_one name, {"exp", name, op_final, exp}

  import: (node) =>
    _, names, source = unpack node

    stubs = for name in *names
      if type(name) == "table"
        name
      else
        {"dot", name}

    real_names = for name in *names
      type(name) == "table" and name[2] or name

    if type(source) == "string"
      build.assign {
        names: real_names
        values: [build.chain { base: source, stub} for stub in *stubs]
      }
    else
      source_name = NameProxy "table"
      build.group {
        {"declare", real_names}
        build["do"] {
          build.assign_one source_name, source
          build.assign {
            names: real_names
            values: [build.chain { base: source_name, stub} for stub in *stubs]
          }
        }
      }

  comprehension: (node, action) =>
    _, exp, clauses = unpack node

    action = action or (exp) -> {exp}
    construct_comprehension action(exp), clauses

  do: (node, ret) =>
    node[2] = apply_to_last node[2], ret if ret
    node

  decorated: (node) =>
    stm, dec = unpack node, 2

    wrapped = switch dec[1]
      when "if"
        cond, fail = unpack dec, 2
        fail = { "else", { fail } } if fail
        { "if", cond, { stm }, fail }
      when "unless"
        { "unless", dec[2], { stm } }
      when "comprehension"
        { "comprehension", stm, dec[2] }
      else
        error "Unknown decorator " .. dec[1]

    if ntype(stm) == "assign"
      wrapped = build.group {
        build.declare names: [name for name in *stm[2] when type(name) == "string"]
        wrapped
      }

    wrapped

  unless: (node) =>
    { "if", {"not", {"parens", node[2]}}, unpack node, 3 }

  if: (node, ret) =>
    -- expand assign in cond
    if ntype(node[2]) == "assign"
      _, assign, body = unpack node
      if destructure.has_destructure assign[2]
        name = NameProxy "des"

        body = {
          destructure.build_assign assign[2][1], name
          build.group node[3]
        }

        return build.do {
          build.assign_one name, assign[3][1]
          {"if", name, body, unpack node, 4}
        }
      else
        name = assign[2][1]
        return build["do"] {
          assign
          {"if", name, unpack node, 3}
        }

    node = expand_elseif_assign node

    -- apply cascading return decorator
    if ret
      smart_node node
      -- mutate all the bodies
      node['then'] = apply_to_last node['then'], ret
      for i = 4, #node
        case = node[i]
        body_idx = #node[i]
        case[body_idx] = apply_to_last case[body_idx], ret

    node

  with: (node, ret) =>
    _, exp, block = unpack node

    scope_name = NameProxy "with"

    named_assign = if ntype(exp) == "assign"
      names, values = unpack exp, 2
      assign_name = names[1]
      exp = values[1]
      values[1] = scope_name
      {"assign", names, values}

    build.do {
      Run => @set "scope_var", scope_name
      build.assign_one scope_name, exp
      build.group { named_assign }
      build.group block

      if ret
        ret scope_name
    }

  foreach: (node) =>
    smart_node node
    source = unpack node.iter

    destructures = {}
    node.names = for i, name in ipairs node.names
      if ntype(name) == "table"
        with proxy = NameProxy "des"
          insert destructures, destructure.build_assign name, proxy
      else
        name

    if next destructures
      insert destructures, build.group node.body
      node.body = destructures

    if ntype(source) == "unpack"
      list = source[2]

      index_name = NameProxy "index"
      list_name = NameProxy "list"

      slice_var = nil
      bounds = if is_slice list
        slice = list[#list]
        table.remove list
        table.remove slice, 1

        slice[2] = if slice[2] and slice[2] != ""
          max_tmp_name = NameProxy "max"
          slice_var = build.assign_one max_tmp_name, slice[2]
          {"exp", max_tmp_name, "<", 0
            "and", {"length", list_name}, "+", max_tmp_name
            "or", max_tmp_name }
        else
          {"length", list_name}

        slice
      else
        {1, {"length", list_name}}

      return build.group {
        build.assign_one list_name, list
        slice_var
        build["for"] {
          name: index_name
          bounds: bounds
          body: {
            {"assign", node.names, {list_name\index index_name}}
            build.group node.body
          }
        }
      }

    node.body = with_continue_listener node.body

  while: (node) =>
    smart_node node
    node.body = with_continue_listener node.body

  for: (node) =>
    smart_node node
    node.body = with_continue_listener node.body

  switch: (node, ret) =>
    _, exp, conds = unpack node
    exp_name = NameProxy "exp"

    -- convert switch conds into if statment conds
    convert_cond = (cond) ->
      t, case_exps, body = unpack cond
      out = {}
      insert out, t == "case" and "elseif" or "else"
      if  t != "else"
        cond_exp = {}
        for i, case in ipairs case_exps
          if i == 1
            insert cond_exp, "exp"
          else
            insert cond_exp, "or"

          case = {"parens", case} unless value_is_singular case
          insert cond_exp, {"exp", case, "==", exp_name}

        insert out, cond_exp
      else
        body = case_exps

      if ret
        body = apply_to_last body, ret

      insert out, body

      out

    first = true
    if_stm = {"if"}
    for cond in *conds
      if_cond = convert_cond cond
      if first
        first = false
        insert if_stm, if_cond[2]
        insert if_stm, if_cond[3]
      else
        insert if_stm, if_cond

    build.group {
      build.assign_one exp_name, exp
      if_stm
    }

  class: (node, ret, parent_assign) =>
    _, name, parent_val, body = unpack node

    -- split apart properties and statements
    statements = {}
    properties = {}
    for item in *body
      switch item[1]
        when "stm"
          insert statements, item[2]
        when "props"
          for tuple in *item[2,]
            if ntype(tuple[1]) == "self"
              insert statements, build.assign_one unpack tuple
            else
              insert properties, tuple

    -- find constructor
    constructor = nil
    properties = for tuple in *properties
      key = tuple[1]
      if key[1] == "key_literal" and key[2] == constructor_name
        constructor = tuple[2]
        nil
      else
        tuple

    parent_cls_name = NameProxy "parent"
    base_name = NameProxy "base"
    self_name = NameProxy "self"
    cls_name = NameProxy "class"

    if not constructor
      constructor = build.fndef {
        args: {{"..."}}
        arrow: "fat"
        body: {
          build["if"] {
            cond: parent_cls_name
            then: {
              build.chain { base: "super", {"call", {"..."}} }
            }
          }
        }
      }
    else
      smart_node constructor
      constructor.arrow = "fat"

    real_name = name or parent_assign and parent_assign[2][1]
    real_name = switch ntype real_name
      when "chain"
        last = real_name[#real_name]
        switch ntype last
          when "dot"
            {"string", '"', last[2]}
          when "index"
            last[2]
          else
            "nil"
      when "nil"
        "nil"
      else
        {"string", '"', real_name}

    cls = build.table {
      {"__init", constructor}
      {"__base", base_name}
      {"__name", real_name} -- "quote the string"
      {"__parent", parent_cls_name}
    }

    -- look up a name in the class object
    class_lookup = build["if"] {
      cond: {"exp", "val", "==", "nil", "and", parent_cls_name}
      then: {
        parent_cls_name\index"name"
      }
    }
    insert class_lookup, {"else", {"val"}}

    cls_mt = build.table {
      {"__index", build.fndef {
        args: {{"cls"}, {"name"}}
        body: {
          build.assign_one LocalName"val", build.chain {
            base: "rawget", {"call", {base_name, "name"}}
          }
          class_lookup
        }
      }}
      {"__call", build.fndef {
        args: {{"cls"}, {"..."}}
        body: {
          build.assign_one self_name, build.chain {
            base: "setmetatable"
            {"call", {"{}", base_name}}
          }
          build.chain {
            base: "cls.__init"
            {"call", {self_name, "..."}}
          }
          self_name
        }
      }}
    }

    cls = build.chain {
      base: "setmetatable"
      {"call", {cls, cls_mt}}
    }

    value = nil
    with build
      out_body = {
        Run =>
          -- make sure we don't assign the class to a local inside the do
          @put_name name if name

          @set "super", (block, chain) ->
            if chain
              slice = [item for item in *chain[3,]]
              new_chain = {"chain", parent_cls_name}

              head = slice[1]

              if head == nil
                return parent_cls_name

              switch head[1]
                -- calling super, inject calling name and self into chain
                when "call"
                  calling_name = block\get"current_block"
                  slice[1] = {"call", {"self", unpack head[2]}}

                  if ntype(calling_name) == "key_literal"
                    insert new_chain, {"dot", calling_name[2]}
                  else
                    insert new_chain, {"index", calling_name}

                -- colon call on super, replace class with self as first arg
                when "colon"
                  call = head[3]
                  insert new_chain, {"dot", head[2]}
                  slice[1] = { "call", { "self", unpack call[2] } }

              insert new_chain, item for item in *slice

              new_chain
            else
              parent_cls_name

        .assign_one parent_cls_name, parent_val == "" and "nil" or parent_val
        .assign_one base_name, {"table", properties}
        .assign_one base_name\chain"__index", base_name

        .if {
          cond: parent_cls_name
          then: {
            .chain {
              base: "setmetatable"
              {"call", {
                base_name,
                .chain { base: parent_cls_name,  {"dot", "__base"}}
              }}
            }
          }
        }

        .assign_one cls_name, cls
        .assign_one base_name\chain"__class", cls_name

        .group if #statements > 0 then {
          .assign_one LocalName"self", cls_name
          .group statements
        }

        -- run the inherited callback
        .if {
          cond: {"exp",
            parent_cls_name, "and", parent_cls_name\chain "__inherited"
          }
          then: {
            parent_cls_name\chain "__inherited", {"call", {
              parent_cls_name, cls_name
            }}
          }
        }

        .group if name then {
          .assign_one name, cls_name
        }

        if ret
          ret cls_name
      }

      hoist_declarations out_body

      value = .group {
        .group if ntype(name) == "value" then {
          .declare names: {name}
        }

        .do out_body
      }

    value
}

class Accumulator
  body_idx: { for: 4, while: 3, foreach: 4 }

  new: =>
    @accum_name = NameProxy "accum"
    @value_name = NameProxy "value"
    @len_name = NameProxy "len"

  -- wraps node and mutates body
  convert: (node) =>
    index = @body_idx[ntype node]
    node[index] = @mutate_body node[index]
    @wrap node

  -- wrap the node into a block_exp
  wrap: (node) =>
    build.block_exp {
      build.assign_one @accum_name, build.table!
      build.assign_one @len_name, 0
      node
      @accum_name
    }

  -- mutates the body of a loop construct to save last value into accumulator
  -- can optionally skip nil results
  mutate_body: (body, skip_nil=true) =>
    val = if not skip_nil and is_singular body
      with body[1]
        body = {}
    else
      body = apply_to_last body, (n) ->
        if types.is_value n
          build.assign_one @value_name, n
        else
          -- just ignore it
          build.group {
            {"declare", {@value_name}}
            n
          }
      @value_name

    update = {
      {"update", @len_name, "+=", 1}
      build.assign_one @accum_name\index(@len_name), val
    }

    if skip_nil
      table.insert body, build["if"] {
        cond: {"exp", @value_name, "!=", "nil"}
        then: update
      }
    else
      table.insert body, build.group update

    body

default_accumulator = (node) =>
  Accumulator!\convert node

implicitly_return = (scope) ->
  is_top = true
  fn = (stm) ->
    t = ntype stm

    -- expand decorated
    if t == "decorated"
      stm = scope.transform.statement stm
      t = ntype stm

    if types.cascading[t]
      is_top = false
      scope.transform.statement stm, fn
    elseif types.manual_return[t] or not types.is_value stm
      -- remove blank return statement
      if is_top and t == "return" and stm[2] == ""
        nil
      else
        stm
    else
      if t == "comprehension" and not types.comprehension_has_value stm
        stm
      else
        {"return", stm}

  fn

Value = Transformer {
  for: default_accumulator
  while: default_accumulator
  foreach: default_accumulator

  do: (node) =>
    build.block_exp node[2]

  decorated: (node) =>
    @transform.statement node

  class: (node) =>
    build.block_exp { node }

  string: (node) =>
    delim = node[2]

    convert_part = (part) ->
      if type(part) == "string" or part == nil
        {"string", delim, part or ""}
      else
        build.chain { base: "tostring", {"call", {part[2]}} }

    -- reduced to single item
    if #node <= 3
      return if type(node[3]) == "string"
        node
      else
        convert_part node[3]

    e = {"exp", convert_part node[3]}

    for i=4, #node
      insert e, ".."
      insert e, convert_part node[i]
    e

  comprehension: (node) =>
    a = Accumulator!
    node = @transform.statement node, (exp) ->
      a\mutate_body {exp}, false
    a\wrap node

  tblcomprehension: (node) =>
    _, explist, clauses = unpack node
    key_exp, value_exp = unpack explist

    accum = NameProxy "tbl"

    inner = if value_exp
      dest = build.chain { base: accum, {"index", key_exp} }
      { build.assign_one dest, value_exp }
    else
      -- If we only have single expression then
      -- unpack the result into key and value
      key_name, val_name = NameProxy"key", NameProxy"val"
      dest = build.chain { base: accum, {"index", key_name} }
      {
        build.assign names: {key_name, val_name}, values: {key_exp}
        build.assign_one dest, val_name
      }

    build.block_exp {
      build.assign_one accum, build.table!
      construct_comprehension inner, clauses
      accum
    }

  fndef: (node) =>
    smart_node node
    node.body = apply_to_last node.body, implicitly_return self
    node.body = {
      Run => @listen "varargs", -> -- capture event
      unpack node.body
    }

    node

  if: (node) => build.block_exp { node }
  unless: (node) =>build.block_exp { node }
  with: (node) => build.block_exp { node }
  switch: (node) =>
    build.block_exp { node }

  -- pull out colon chain
  chain: (node) =>
    stub = node[#node]

    -- escape lua keywords used in dot accessors
    for i=3,#node
      part = node[i]
      if ntype(part) == "dot" and data.lua_keywords[part[2]]
        node[i] = { "index", {"string", '"', part[2]} }

    if ntype(node[2]) == "string"
      -- add parens if callee is raw string
      node[2] = {"parens", node[2] }
    elseif type(stub) == "table" and stub[1] == "colon_stub"
      -- convert colon stub into code
      table.remove node, #node

      base_name = NameProxy "base"
      fn_name = NameProxy "fn"

      is_super = node[2] == "super"
      @transform.value build.block_exp {
        build.assign {
          names: {base_name}
          values: {node}
        }

        build.assign {
          names: {fn_name}
          values: {
            build.chain { base: base_name, {"dot", stub[2]} }
          }
        }

        build.fndef {
          args: {{"..."}}
          body: {
            build.chain {
              base: fn_name, {"call", {is_super and "self" or base_name, "..."}}
            }
          }
        }
      }

  block_exp: (node) =>
    _, body = unpack node

    fn = nil
    arg_list = {}

    fn = smart_node build.fndef body: {
      Run =>
        @listen "varargs", ->
          insert arg_list, "..."
          insert fn.args, {"..."}
          @unlisten "varargs"

      unpack body
    }

    build.chain { base: {"parens", fn}, {"call", arg_list} }
}

{ :Statement, :Value, :Run }

error: pathspec 'samples/MoonScript/transform.moon' did not match any file(s) known to git


Try out moon

Try out moon

fib = (n) ->
    lfib = (i=0, x, y) ->
        if i == 0 then return
        print(x)
        lfib(i - 1, y, x + y)
    lfib(n, 1, 1)

fib(42)

error: pathspec 'hello.moon' did not match any file(s) known to git


gay: new module

gay: new module

Former-commit-id: 59830ab6e2ceee034679cc4d9b6d023d5814779e [formerly ec27523424318dc9634dbf7b7276acdefbbce8ea]
Former-commit-id: 88e560ed365e7e138918eb7dae0d34571f47be81

FABULOUS_COLORS = {4, 7, 9, 10, 12, 13, 6}

i = 0

gayify = (s) ->
  if s == "" return ""
  s\gsub "[%z\1-\127\194-\244][\128-\191]*", (c) ->
    ret = string.format("\03%02d%s", FABULOUS_COLORS[i + 1], c)
    i = (i + 1) % #FABULOUS_COLORS
    return ret

PRIVMSG:
  '^%pgay (.+)$': (source, destination, arg) =>
      @Msg 'privmsg', destination, source, gayify arg

error: pathspec 'modules/gay.moon' did not match any file(s) known to git


Added tests for `is_array` function

Added tests for `is_array` function

remote: Support for password authentication was removed on August 13, 2021.
remote: Please see https://docs.github.com/en/get-started/getting-started-with-git/about-remote-repositories#cloning-with-https-urls for information on currently recommended modes of authentication.
fatal: Authentication failed for 'https://github.com/SuperPaintman/umka.git/'


添加助手函数，基本排序方法。

添加助手函数，基本排序方法。

selesort = (list)->
  len=#list
  for i=1,len
    max = list[i]
    for j=i+1, len do
      if list[j]>max then
        tmp=list[j]
        list[j]=max
        list[i]=tmp
        max=tmp
  return list

mapsort = (board)->
  b_len = 0
  for k,v in pairs(board)
    b_len = b_len + 1

  a1 = {}
  a2 = {}

  i = 0
  for k,v in pairs(board)
    i = i + 1
    a1[i] = k
    a2[i] = v

  for i = 1, b_len
     max = a2[i]
     for j = i + 1, b_len
       if a2[j] > max
         tmp = a2[j]
         a2[j] = max
         a2[i] = tmp
         max = tmp
         tmp1 = a1[j]
         a1[j]  = a1[i]
         a1[i] = tmp1

  ret = {}
  for k,v in ipairs(a1)
    ret[k] = {a1[k], a2[k]}
  return ret

error: pathspec 'libs/moon/HiHelper.moon' did not match any file(s) known to git


fork me ribbons

fork me ribbons

html = require "lapis.html"

class MainLayout extends html.Widget
  @include require "views.mixin.favicon"
  @include require "lazuli.modules.user_management.views.mixin_menu"
  content: =>
    html_5 ->
      head ->
        meta charset:"utf-8"
        meta name:"viewport", content:"width=device-width, initial-scale=1"
        link rel:"stylesheet", href:"http://yui.yahooapis.com/pure/0.6.0/pure-min.css"
        link rel:"stylesheet", href:"http://yui.yahooapis.com/pure/0.6.0/grids-responsive-min.css"
        link rel:"stylesheet", href:"/static/css/main_layout.css"
        link rel:"stylesheet", href:"//cdnjs.cloudflare.com/ajax/libs/github-fork-ribbon-css/0.1.1/gh-fork-ribbon.min.css"
        title @title or (@has_content_for("title") and @content_for("title")) or "Komischfarben"
        @mixFavicon!
      body ->
        div class:"github-fork-ribbon-wrapper left-bottom",->
          div class:"github-fork-ribbon",style:"background-color: #333;",->
            a href:"https://github.com/nonchip/komischfarben2", "Fork me on GitHub"
        div id:"layout",->
          a href:"#menu",id:"menuLink",class:"menu-link",->
            span ""
          div id:"menu",->
            div class:"pure-menu pure-menu-open",->
              a class:"pure-menu-heading", href:"/", "Komischfarben"
              @userManagementMixinMenu{ul:"pure-menu-list",li:"pure-menu-item",a:"pure-menu-link", active_li:"pure-menu-selected"}
              ul class:"pure-menu-list", ->
                --li class:"pure-menu-item",->
                --  a class:"pure-menu-link",href:"https://github.com/nonchip/komischfarben2",style:"color:#555",target:"_blank","Github"
                li class:"pure-menu-item",->
                  a class:"pure-menu-link",href:"https://github.com/nonchip/komischfarben2/issues",style:"color:#555",target:"_blank","Report issues"
          div id:"main",->
            if true or @has_content_for "header" then
              div class:"header",->
                tt "BETA"
                @content_for "header"
            div class:"content",->
              @content_for "inner"
        script src:"/static/js/main_layout.js"

html = require "lapis.html"

class MainLayout extends html.Widget
  @include require "views.mixin.favicon"
  @include require "lazuli.modules.user_management.views.mixin_menu"
  content: =>
    html_5 ->
      head ->
        meta charset:"utf-8"
        meta name:"viewport", content:"width=device-width, initial-scale=1"
        link rel:"stylesheet", href:"http://yui.yahooapis.com/pure/0.6.0/pure-min.css"
        link rel:"stylesheet", href:"http://yui.yahooapis.com/pure/0.6.0/grids-responsive-min.css"
        link rel:"stylesheet", href:"/static/css/main_layout.css"
        link rel:"stylesheet", href:"//cdnjs.cloudflare.com/ajax/libs/github-fork-ribbon-css/0.1.1/gh-fork-ribbon.min.css"
        title @title or (@has_content_for("title") and @content_for("title")) or "Komischfarben"
        @mixFavicon!
      body ->
        div class:"github-fork-ribbon-wrapper right",->
          div class:"github-fork-ribbon",->
            a href:"https://github.com/nonchip/komischfarben2", "Fork me on GitHub"
        div id:"layout",->
          a href:"#menu",id:"menuLink",class:"menu-link",->
            span ""
          div id:"menu",->
            div class:"pure-menu pure-menu-open",->
              a class:"pure-menu-heading", href:"/", "Komischfarben"
              @userManagementMixinMenu{ul:"pure-menu-list",li:"pure-menu-item",a:"pure-menu-link", active_li:"pure-menu-selected"}
              ul class:"pure-menu-list", ->
                --li class:"pure-menu-item",->
                --  a class:"pure-menu-link",href:"https://github.com/nonchip/komischfarben2",style:"color:#555",target:"_blank","Github"
                li class:"pure-menu-item",->
                  a class:"pure-menu-link",href:"https://github.com/nonchip/komischfarben2/issues",style:"color:#555",target:"_blank","Report issues"
          div id:"main",->
            if true or @has_content_for "header" then
              div class:"header",->
                tt "BETA"
                @content_for "header"
            div class:"content",->
              @content_for "inner"
        script src:"/static/js/main_layout.js"


Added utility to save models to a Collada format.

This only works for simple inputs.

-- This Source Code Form is subject to the terms of the Mozilla Public
-- License, v. 2.0. If a copy of the MPL was not distributed with this
-- file, You can obtain one at http://mozilla.org/MPL/2.0/.
--
-- Copyright 2016 [bk]door.maus

Model = require "glurg.resources.Model"
Mesh = require "glurg.resources.Mesh"
MeshBlobBuilder = require "glurg.resources.MeshBlobBuilder"
Trace = require "glurg.trace.Trace"

Args = { ... }
if #Args < 5
	io.stderr\write("save_models.moon <file.trace> <position-attrib> <normal-attrib> <texture-attrib> <output-dir>\n")

	return 1

local trace

Attribs =
	position: Args[2]
	normal: Args[3]
	texture: Args[4]

GLState =
	buffer: {}
	program: {}
	vao: {}
	current_program: 0
	current_vao: 0
	current_array_buffer: 0
	current_element_array_buffer: 0

get_buffer = (id) ->
	if GLState.buffer[id] == nil
		GLState.buffer[id] = {}

	return GLState.buffer[id]

update_buffer = (id, call) ->
	e = get_buffer(id)
	if e.call then
		trace\delete_call(e.call.index)
	e.call = call

delete_buffer = (id) ->
	if GLState.buffer[id]
		if GLState.buffer[id].call
			trace\delete_call(GLState.buffer[id].call.index)

	GLState.buffer[id] = nil

get_program = (id) ->
	if GLState.program[id] == nil
		GLState.program[id] = { attribs: {} }

	return GLState.program[id]

update_program = (id, call) ->
	e = get_program(id)

	index = call\get_argument_by_name("index")\query!
	name = call\get_argument_by_name("name")\query!
	e.attribs[index] = name
	e.attribs[name] = index

delete_program = (id) ->
	GLState.program[id] = nil

get_vao = (id) ->
	if GLState.vao[id] == nil
		GLState.vao[id] = { meshes: {} }

	return GLState.vao[id]

update_vao_attrib = (id, call) ->
	e = get_vao(id)

	index = call\get_argument_by_name("index")\query!

	if GLState.current_array_buffer != 0
		buffer = get_buffer(GLState.current_array_buffer)

		builder = MeshBlobBuilder!
		extracted_description = builder\extract_attrib_call(call)
		extracted_buffer = builder\extract_buffer_call(buffer.call)
		if extracted_description and extracted_buffer then
			blob = builder\build!
			e.meshes[index] = Mesh(blob)

delete_vao = (id) ->
	GLState.vao[id] = nil

save_model = (call) ->
	program = get_program(GLState.current_program)

	position_index = program.attribs[Attribs.position]
	normal_index = program.attribs[Attribs.normal]
	texture_index = program.attribs[Attribs.texture]

	if not position_index or not normal_index or not texture_index then
		io.stdout\write("program doesn't have matching attributes\n")
		return false

	vao = get_vao(GLState.current_vao)
	position_mesh = vao.meshes[position_index]
	normal_mesh = vao.meshes[normal_index]
	texture_mesh = vao.meshes[texture_index]

	print("num_components", texture_mesh.blob.vertex_description.num_components)
	print("texture_index", texture_index)

	if not position_mesh or not normal_mesh or not texture_mesh
		io.stdout\write("VAO doesn't have matching meshes\n")
		return false
	if not vao.elements then
		io.stdout\write("VAO doesn't have index data\n")
		return false

	model = Model(position_mesh)
	model.normals = normal_mesh
	model.texture_coordinates[1] = texture_mesh

	index_format = switch call\get_argument_by_name("type")\query!.value_name
		when 'GL_UNSIGNED_BYTE' then index_format = 'unsigned_byte'
		when 'GL_UNSIGNED_SHORT' then index_format = 'unsigned_short'
		when 'GL_UNSIGNED_INT' then index_format = 'unsigned_integer'
		else return false

	model\extract_index_data(index_format, get_buffer(vao.elements).call)

	print(call.index)
	model\save_dae(string.format("%s/%010d.dae", Args[5], call.index))

trace = Trace(Args[1])
while not trace.is_end_of_file
	event = trace\next_event!
	if event.type == 'leave'
		switch event.call.signature.name
			when "glBindBuffer"
				target = event.call\get_argument_by_name("target")\query!
				buffer = event.call\get_argument_by_name("buffer")\query!
				if target.value_name == "GL_ELEMENT_ARRAY_BUFFER"
					GLState.current_element_array_buffer = buffer
					if GLState.current_vao != 0 then
						vao = get_vao(GLState.current_vao)
						vao.elements = buffer
				elseif target.value_name == "GL_ARRAY_BUFFER"
					GLState.current_array_buffer = buffer
				trace\delete_call(event.call_index)
			when "glBindVertexArray"
				GLState.current_vao = event.call\get_argument_by_name("array")\query!
				trace\delete_call(event.call_index)
			when "glUseProgram"
				GLState.current_program = event.call\get_argument_by_name("program")\query!
			when "glBindAttribLocation"
				program = event.call\get_argument_by_name("program")\query!
				update_program(program, event.call)
			when "glVertexAttribPointer"
				update_vao_attrib(GLState.current_vao, event.call)
			when "glBufferData"
				target = event.call\get_argument_by_name("target")\query!
				if target.value_name == "GL_ELEMENT_ARRAY_BUFFER"
					update_buffer(GLState.current_element_array_buffer, event.call)
				elseif target.value_name == "GL_ARRAY_BUFFER"
					update_buffer(GLState.current_array_buffer, event.call)
			when "glDrawElements", "glDrawRangeElements"
				if save_model(event.call) then
					io.stdout\write("saved model from call #{event.call_index}\n")
				else
					io.stdout\write("couldn't save model from call #{event.call_index}\n")
				trace\delete_call(event.call_index)
			else
				trace\delete_call(event.call_index)

error: pathspec 'utilities/lua/save_models.moon' did not match any file(s) known to git


configre moon lint

configre moon lint

whitelist_globals: {
  ["."]: {
    "ngx"
    "port"
    "num_workers"
    "worker_connections"
    "code_cache"
    "postgresql_url"
  }

  ["spec/"]: {
    "it"
    "describe"
    "setup"
    "teardown"
  }

  ["views/"]: {
    "a"
    "abbr"
    "acronym"
    "address"
    "article"
    "aside"
    "body"
    "br"
    "button"
    "caption"
    "capture"
    "cite"
    "code"
    "col"
    "colgroup"
    "command"
    "dd"
    "div"
    "dl"
    "dt"
    "element"
    "em"
    "embed"
    "fieldset"
    "figure"
    "footer"
    "form"
    "h1"
    "h2"
    "h3"
    "h4"
    "h5"
    "h6"
    "head"
    "header"
    "hr"
    "html_5"
    "img"
    "input"
    "label"
    "legend"
    "li"
    "meta"
    "nav"
    "ol"
    "option"
    "p"
    "script"
    "section"
    "select"
    "span"
    "style"
    "table"
    "tbody"
    "td"
    "text"
    "textarea"
    "tfoot"
    "th"
    "thead"
    "title"
    "tr"
    "ul"
  }
}

error: pathspec 'lint_config.moon' did not match any file(s) known to git


illness: new module

illness: new module

Former-commit-id: 92d5816ca408c6cae2ba7bcd4ae7aba46c087978 [formerly 103befb81fb6884c36832c03e5e1e9805cf1e6eb]
Former-commit-id: c536b47aeecfdf8b39512a0a55998c339f32e0b5

-- thanks to byte[] for sicknesses :-)

illnesses = {
  [[nany vgpuvat]],
  [[nany cnva]],
  [[nfgvtzngvfz]],
  [[onq oerngu]],
  [[oryyl ohggba qvfpunetr]],
  [[oyheerq ivfvba pnhfrf]],
  [[oyheel ivfvba]],
  [[oyheel ivfvba]],
  [[objry vapbagvarapr]],
  [[pbyq fberf]],
  [[pbafgvcngvba]],
  [[peno yvpr]],
  [[qnaqehss]],
  [[qvfrnfrf bs gur rlr]],
  [[rwnphyngvba]],
  [[rerpgvba ceboyrzf]],
  [[rlr pbaqvgvbaf]],
  [[rlr qvfrnfrf]],
  [[rlr rkrepvfrf]],
  [[rlr synfurf]],
  [[rlr sybngref]],
  [[rlr urnygu]],
  [[rlr erqarff]],
  [[rlr fgenva flzcgbzf]],
  [[rlr ivfvba]],
  [[rlr ivgnzvaf]],
  [[srpny vapbagvarapr]],
  [[svffher]],
  [[synxl fxva]],
  [[sybngref va gur rlr]],
  [[travgny jnegf]],
  [[tynhpbzn flzcgbzf]],
  [[unve ybff]],
  [[urnq yvpr]],
  [[urzzbeubvqf]],
  [[uvpphcf]],
  [[ubj gb vzcebir rlrfvtug]],
  [[vagenbphyne cerffher]],
  [[veevgnoyr objry flaqebzr]],
  [[vgpuvat]],
  [[wbpx vgpu]],
  [[ynfre rlr fhetrel]],
  [[ynfre fhetrel sbe rlrf]],
  [[ynfvx]],
  [[ynfvx rlr fhetrel]],
  [[ynfvx fhetrel pbfg]],
  [[zna obbof]],
  [[zrzbel ceboyrzf]],
  [[zrabcnhfr]],
  [[zbhgu hypref]],
  [[anvy ovgvat]],
  [[beny frk]],
  [[birejrvtug]],
  [[cvyrf]],
  [[cbylcunfvp fyrrc]],
  [[cbea nqqvpgvba]],
  [[cerzrafgehny flaqebzr]],
  [[erq snpr]],
  [[erfgyrff yrtf]],
  [[ergvany qrgnpuzrag]],
  [[fpnyl fxva]],
  [[fpnef]],
  [[funxl unaqf]],
  [[fzryyl srrg]],
  [[fabevat]],
  [[fgnzzrevat]],
  [[fgergpu znexf]],
  [[fjrngvat]],
  [[gbranvy vasrpgvba]],
  [[gbathr ceboyrzf]],
  [[hevanel vapbagvarapr]],
  [[inevpbfr irvaf]],
  [[ireehpnf]],
  [[ivfvba gurencl]],
  [[jvaq]],
}

illness = ->
  ivar2.util.rot13(illnesses[math.random(1, #illnesses)])

PRIVMSG:
  '^%pillness (.+)$': (s, d, nick) =>
    say "#{nick}, afflicted by #{illness!}"
  '^%pillness$': (s, d) =>
    say illness!

error: pathspec 'modules/illness.moon' did not match any file(s) known to git


youtube: new module. thanks to @einarjh for idea

-- Module for announcing new videos to a youtube channel
-- Requires a developer API key set in configuration
{:simplehttp, :json, :urlEncode, :bold} = require'util'

moduleName = 'youtube'
key = moduleName
store = ivar2.persist

getItems = (channel, cb) ->
  url = "https://www.googleapis.com/youtube/v3/channels?part=contentDetails&forUsername=%s&key=%s"
  simplehttp url\format(urlEncode(channel), ivar2.config.youtubeAPIKey), (data) ->
    js = json.decode(data)
    if not (js and js.items and js.items[1] and js.items[1].contentDetails)
      ivar2\Log 'info', 'youtube: No items for youtube channel '..tostring(channel)
      return
    playlistId = js.items[1].contentDetails.relatedPlaylists.uploads
    if playlistId
      url = "https://www.googleapis.com/youtube/v3/playlistItems?part=snippet&playlistId=%s&key=%s"
      simplehttp url\format(playlistId, ivar2.config.youtubeAPIKey), (pldata) ->
        pljs = json.decode(pldata)
        cb(pljs.items)

getLatest = (source, destination, channel) =>
  getItems channel, (items) ->
    for item in *items
      if item.kind == "youtube#playlistItem"
        s = item.snippet
        videoId = s.resourceId.videoId
        say "[#{bold s.channelTitle}] http://youtu.be/#{videoId} #{s.title}"
        return

checkChannels = ->
  for c,_ in pairs(ivar2.channels)
    channelKey = key..':'..c
    channels = store[channelKey] or {}
    for name, channel in pairs(channels)
      lastKey = channelKey .. ':' .. name .. ':last'
      last = store[lastKey] or ''
      getItems name, (items) ->
        for item in *items
          if item.kind == "youtube#playlistItem"
            s = item.snippet
            videoId = s.resourceId.videoId
            -- Check if we already announced
            if videoId == last
              return
            -- Store first video in playlist as a marker
            if s.position == 0
              store[lastKey] = videoId
            ivar2\Msg 'privmsg', channel.channel, nil, "[#{bold s.channelTitle}] http://youtu.be/#{videoId} — #{s.title}"
            -- If we're newly subscribed, only tell once
            if last == ''
              return

subscribe = (source, destination, name) =>
  channelKey = key..':'..destination
  channels = store[channelKey] or {}
  channels[name] = {channel:destination, name:name}
  store[channelKey] = channels
  reply "Ok. Subscribed to #{bold name}"
  checkChannels()

unsubscribe = (source, destination, name) =>
  channelKey = key..':'..destination
  channels = store[channelKey] or {}
  unless channels[name]
    reply "Wasn't subscribed. But, sure."
  else
    channels[name] = nil
    store[channelKey] = channels
    reply "Ok. Stopped caring about #{bold name}"

list = (source, destination) =>
  channelKey = key..':'..destination
  channels = store[channelKey] or {}
  out = {}
  for name, game in pairs(channels)
      out[#out+1] = name
  say "Subscribed to: #{table.concat(out, ', ')}"

-- Start the subscribe poller
ivar2\Timer('youtube', 300, 300, checkChannels)

PRIVMSG:
  '^%pyoutube latest (.*)': getLatest
  '^%pyoutube subscribe (.*)': subscribe
  '^%pyoutube unsubscribe (.*)': unsubscribe
  '^%pyoutube list': list

error: pathspec 'modules/youtube.moon' did not match any file(s) known to git


anagram: new module

anagram: new module

Former-commit-id: 024e1814d686c9c03c198d2724e502c048d7afbb [formerly 6b9a33987da5ceed4808c9a7620e7d2b980351ba]
Former-commit-id: 38d425cb07092d28318ba383d0b8121ac5619265

html2unicode = require'html'

PRIVMSG:
  '^%panagram (.+)$': (s, d, arg) =>
    ivar2.util.simplehttp "http://www.wordsmith.org/anagram/anagram.cgi?t=50&anagram=" .. ivar2.util.urlEncode(arg), (s) ->
      s = s\match "(Displaying%s.-)</?[Pp][ />]"
      unless s
        return say "Did not find result"
      s = s\gsub "<[Bb][Rr][ />]", ", "
      -- Strip tags
      s = s\gsub "%b<>", ""
      say html2unicode(s)\gsub(":%s*,", ":")\gsub(",%s*$", "")

error: pathspec 'modules/anagram.moon' did not match any file(s) known to git


alias: new module

alias: new module

Former-commit-id: beec51cb4c1291b31373dd9f76f0ad6b4ab5bffa [formerly 02a78c94b5837006708dee0285d0b233ea09bd44]
Former-commit-id: 082f16dbf4451f5da86e8ba8efee42fa7c184fa4

moduleName = 'alias'
key = 'alias:aliases'

listAliases = =>
  out = {}
  for name,command in pairs(ivar2.persist[key] or {})
    table.insert(out, name)
  say "Aliases: #{table.concat(out, ', ')}"

showAlias = (s, d, name) =>
  c = ivar2.persist[key][name]
  if c then
    say "Alias: #{ivar2.util.bold name} => #{ivar2.util.bold c}"
  else
    reply "Noperino. Failerino."

aliasHelp = =>
  help = 'Usage: ¤alias add <name> <command>. Ex: ¤alias add ahelp alias help. Delete: ¤alias del <name>. List: ¤alias list. Show: ¤alias show <name>. Use § to denote | for piping commands."'
  patt = @ChannelCommandPattern('^%p', moduleName, destination)
  if patt == '^%p'
    patt = '!'
  help = help\gsub '¤', patt
  reply help

aliasHandler = (source, destination, pattern) =>
  return (source, destination, arg) =>
    patt = @ChannelCommandPattern('^%p', moduleName, destination)
    if patt == '^%p'
      patt = '!'
    pattern = pattern\gsub('§', '|'..patt)
    ivar2\DispatchCommand 'PRIVMSG', patt..pattern, source, destination

-- Register the command
regCommand = (source, destination, name, pattern) =>
  store = ivar2.persist[key] or {}
  store[name] = pattern
  ivar2.persist[key] = store
  patt = @ChannelCommandPattern('^%p', moduleName, destination)
  @RegisterCommand moduleName, patt..name, aliasHandler(@, source, destination, pattern)
  if source and destination
    reply "Registered new alias #{ivar2.util.bold name} for #{ivar2.util.bold pattern}"

-- Register aliases *after* module load
ivar2\Timer 'regAlias', 1, ->
  for name,command in pairs(ivar2.persist[key] or {})
    regCommand(ivar2, nil, nil, name, command)

delCommand = (source, destination, name) =>
  store = ivar2.persist[key] or {}
  store[name] = nil
  ivar2.persist[key] = store
  reply 'Ok, I guess'

PRIVMSG:
  '^%palias$': aliasHelp
  '^%palias help$': aliasHelp
  '^%palias add (%S+) (.+)$': regCommand
  '^%palias del (.+)$': delCommand
  '^%palias list$': listAliases
  '^%palias show (.+)$': showAlias

error: pathspec 'modules/alias.moon' did not match any file(s) known to git


ipa: new module international phoneitc alphabet

Former-commit-id: 9e34610e1b827f520b91ce498026116b20c3ea9d [formerly 5106049c3f67fc848b1f0e872773a1365c44bbe1]
Former-commit-id: f97a419bb58e2e4ac4b451af1ee93229f1b7a654

-- international phonetic alphabet
{:simplehttp, :urlEncode, :json} = require'util'
PRIVMSG:
  '^%pipa (.*)$': (source, destination, input) =>
    simplehttp "http://rhymebrain.com/talk?function=getWordInfo&word=#{urlEncode input}", (data) ->
      data = json.decode(data)
      say data.ipa

error: pathspec 'modules/ipa.moon' did not match any file(s) known to git


dota: new module to track live matches

dota: new module to track live matches

Former-commit-id: 6e34b081f27d690338c8c75648718729740e49cd
Former-commit-id: 3787c369f331f02cdcb7b0057792cd5e21440723

-- Module to follow and track DotA2 broadcasts

{:simplehttp, :json, :bold, :red, :green} = require 'util'

siValue = (val) ->
  val = tonumber(val)
  if(val >= 1e6) then
    ('%.1f')\format(val / 1e6)\gsub('%.', 'M')\gsub('M0', 'M')
  elseif(val >= 1e4) then
    ("%.1f")\format(val / 1e3)\gsub('%.', 'k')\gsub('k0', 'k')
  else
    val

dota = (source, destination, arg) =>
  url = 'http://www.trackdota.com/data/games_v2.json'
  simplehttp url, (data) ->
    js = json.decode data
    for _, m in ipairs(js.enhanced_matches)
      -- Use first game in series.
      _, g = next m.games
      -- team_tag
      --
      simplehttp "http://www.trackdota.com/data/game/#{g.id}/core.json", (core) ->
        cjs = json.decode core
        streams = cjs.streams
        -- Find English twitch and youtube stream with highest viewer count
        stream = ''
        count = 0
        youtubestream = ''
        for _, s in ipairs cjs.streams
          if s.provider == 'twitch' and s.language == 'en' and s.viewers > count
            stream = 'English stream: http://twitch.tv/'..s.channel
            count = s.viewers
          elseif s.provider == 'youtube' and s.language == 'en'
            youtubestream = 'https://gaming.youtube.com/watch?v='..s.embed_id
        say "[#{bold m.name}] #{green g.radiant_team.team_name} vs #{red g.dire_team.team_name} (#{siValue g.spectators} viewers). #{stream} #{youtubestream}"
      break

PRIVMSG:
  '^%pdota$': dota

error: pathspec 'modules/dota.moon' did not match any file(s) known to git


Alot: new module

Alot: new module

Former-commit-id: f794da92f4841b3882c9744cdc30362587d87f26 [formerly 6b8d28647f8f70770fb61152fcfae7dbc08f189a]
Former-commit-id: 12da5a06b1fc6e54e34cc11e128bee515c90e15c

alot = (source, destination) =>
  @Msg 'privmsg', destination, source, '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

error: pathspec 'modules/alot.moon' did not match any file(s) known to git


Refact tests for `split` function

Refact tests for `split` function


Added tests for `in_object` function

Added tests for `in_object` function


chopra: add the greatness of chopra to the bot

Former-commit-id: 847a043a96f4e8af349e2a2f721fc02c0013c643 [formerly 4c0761523a93826689790aebaa01695479db2ffb]
Former-commit-id: 0570040f5bd1c2a67c0041bc8c38f88b9ba1b144

simplehttp = require 'simplehttp'

PRIVMSG:
  '^%pchopra$': (source, destination) =>
    simplehttp 'http://www.wisdomofchopra.com/iframe.php', (html) ->
       quote = html\match [[<meta property="og:description" content="'(.+)' www.wisdomofchopra.com" />]]
       if quote
         say quote

error: pathspec 'modules/chopra.moon' did not match any file(s) known to git


Added tests for `merge` function

Added tests for `merge` function


naustver: Display weather from my own weatherstation

Former-commit-id: 0efb3978475ceea2c6bce2590f0d5c98f0e83363 [formerly 20faede1babd567e3a683aad52c2ec9f020e5826]
Former-commit-id: 89ef565b430477d7e530f8aeb59d0ab11af33866

simplehttp = require'simplehttp'
json = require'json'

APIBase = 'http://yr.hveem.no/api/now'

PRIVMSG:
  '^%pvêr$': (self, source, destination, input) ->
    simplehttp APIBase, (data) ->
      result = json.decode data
      if result
        result = result[1]
        temp = result.outtemp
        ws = result.windspeed
        rain = result.dayrain
        windtext = ''
        if ws > 32
            windtext = 'orkan!'
        elseif ws > 28.5
            windtext = 'sterk storm'
        elseif ws > 24.5
            windtext = 'full storm'
        elseif ws > 20.8
            windtext = 'liten storm'
        elseif ws > 17.2
            windtext = 'sterk kuling'
        elseif ws > 13.9
            windtext = 'stiv kuling'
        elseif ws > 10.8
            windtext = 'liten kuling'
        elseif ws > 8
            windtext = 'frisk bris'
        elseif ws > 5.5
            windtext = 'laber bris'
        elseif ws > 3.4
            windtext = 'lett bris'
        elseif ws > 1.5
            windtext = 'svak vind'
        elseif ws > 0.3
            windtext = 'flau vind'
        else
            windtext = 'vindstille'

        self\Msg 'privmsg', destination, source, '\002%.1f\002 °C, \002%.1f\002 m/s (%s), \002%.1f\002 mm nedbør', temp, ws, windtext, rain

error: pathspec 'modules/naustver.moon' did not match any file(s) known to git


weatherunderground: new module

weatherunderground: new module

-- Weather Underground API module, requires a key set in configuration
-- WeatherUndergroundKey

util = require'util'
os = require'os'

key = ivar2.config.WeatherUndergroundKey
unless key
  return {}

geoApiBase = 'http://api.geonames.org/searchJSON?name=%s&featureClass=P&username=haste'
wugeolookupurl = 'http://api.wunderground.com/api/%s/geolookup/q/59.788,5.7218.json'
language = 'NO'

tempColor = (v) ->
  v = tonumber(v)
  if v > 0
    color = util.red
    return color(v)
  else
    color = util.lightblue
    return color(v)

windSpeedToName = (ws) ->
  windtext = ''
  unless ws
    return windtext
  if ws > 32
    windtext = 'orkan!'
  elseif ws > 28.5
    windtext = 'sterk storm'
  elseif ws > 24.5
    windtext = 'full storm'
  elseif ws > 20.8
    windtext = 'liten storm'
  elseif ws > 17.2
    windtext = 'sterk kuling'
  elseif ws > 13.9
    windtext = 'stiv kuling'
  elseif ws > 10.8
    windtext = 'liten kuling'
  elseif ws > 8
    windtext = 'frisk bris'
  elseif ws > 5.5
    windtext = 'laber bris'
  elseif ws > 3.4
    windtext = 'lett bris'
  elseif ws > 1.5
    windtext = 'svak vind'
  elseif ws > 0.3
    windtext = 'flau vind'
  else
    windtext = 'vindstille'
  return windtext

findLocation = (source, destination, name, cb) =>
  gurl = geoApiBase\format(util.urlEncode(name))
  util.simplehttp gurl, (data) ->
    json = util.json.decode(data)
    if json.totalResultsCount == 0
      return @Msg('privmsg', destination, source, "Does that place even exist?")
    city = json.geonames[1]
    lat = city.lat
    lon = city.lng
    cb(lon, lat)

lookupConditions = (source, destination, input, pws) =>
  findLocation @, source, destination, input, (lon, lat) ->
    pws = pws or false
    if pws
      pws = 'pws:1/'
    else
      pws = 'pws:0/'
    url = "http://api.wunderground.com/api/#{key}/lang:#{language}/#{pws}conditions/q/#{lat},#{lon}.json"
    @Log 'debug', "Fetching WU URL: %s", url
    util.simplehttp url, (data) ->
      json = util.json.decode(data)
      if json.error ~= nil and json.error.description ~=nil
        return say json.error.description
      current = json.current_observation

      location = current.observation_location
      city = location.city

      weather = current.weather
      temp = current.temp_c
      feelsLike = current.feelslike_c

      windSpeedname = windSpeedToName(current.wind_kph/3.6)

      windDirection = current.wind_dir

      rain = current.precip_today_metric

      time = os.date('%H:%M', tonumber(current.observation_epoch))

      @Msg 'privmsg', destination, source, '%s, %s °C (følt %s °C), %s %s, %s mm, (%s, %s)', weather, tempColor(temp), tempColor(feelsLike), windDirection, windSpeedname, rain, city, time

lookupConditionsPWS = (source, destination, input) =>
  lookupConditions(@, source, destination, input, true)

PRIVMSG:
  '^%pwu (.+)$': lookupConditions
  '^%ppws (.+)$': lookupConditionsPWS

error: pathspec 'modules/weatherunderground.moon' did not match any file(s) known to git


Added inverse Mix Columns Tests

Added inverse Mix Columns Tests

describe 'Add Round Key Tests', ->
    secua = nil

    setup ->
        secua = (require 'secua')('secua')

    before_each ->
        stub _G, "print"

    it 'correctly performs the inverse mixColumns step', ->
        invMixColumns = secua.aes.steps.invMixColumns

        inputState = { 0x00, 0x01, 0x02, 0x03, 0x04, 0x05, 0x06, 0x07, 0x08, 0x09, 0x0a, 0x0b, 0x0c, 0x0d, 0x0e, 0x0f }
        expectedState = { 0x0a, 0x0f, 0x08, 0x0d, 0x0e, 0x0b, 0x0c, 0x09, 0x02, 0x07, 0x00, 0x05, 0x06, 0x03, 0x04, 0x01 }

        result = invMixColumns inputState
        assert.are.same expectedState, result

    it 'rejects a non existent state', ->
        invMixColumns = secua.aes.steps.invMixColumns

        assert.is_nil(invMixColumns nil)
        assert.stub(print).was.called!

    it 'rejects a non table state', ->
        invMixColumns = secua.aes.steps.invMixColumns

        assert.is_nil(invMixColumns 1243)
        assert.stub(print).was.called!

    it 'rejects a state that is not 16 bytes (128 bits) log', ->
        invMixColumns = secua.aes.steps.invMixColumns

        assert.is_nil(invMixColumns { 0x0f, 0x0a })
        assert.stub(print).was.called!

error: pathspec 'src/secua/aes/steps/invMixColumns_spec.moon' did not match any file(s) known to git


complete: new module

complete: new module

Former-commit-id: 638ffa56df6595b00eb8f876d3d4946ec9ff314d [formerly 0e4d2a24d90b1dfc2d78d5befacf73d1bf71e0e2]
Former-commit-id: d7e5bb3cae7d45cbbb4d54784a403d6acb7a369c

-- ported from luabot's complete module by anders. Thanks!
html2unicode = require'html'

PRIVMSG:
  '^%pcomplete (.+)$': (s, d, query) =>
    URL = "http://www.google.com/complete/search?client=serp&hl=en&xhr=t&q="..ivar2.util.urlEncode(query)
    ivar2.util.simplehttp URL, (response) ->
      d = json.decode(response)
      out = {}

      for k, v in ipairs(d[2]) do
        completion = v[1]\gsub("<.->", "")
        out[#out + 1] = html2unicode(completion).."?"

      if #out > 0
        say(table.concat(out, " "))

error: pathspec 'modules/complete.moon' did not match any file(s) known to git


add an example

add an example

# ------------------------------------------------------------------------------
# example config format
#
# this is a working draft of things that are valid in a new config language to
# replace json as a config language for Go projects.
#
# comments are a thing now!
# ------------------------------------------------------------------------------

# the whole document is implicitly a namespace, so you can set key value pairs
# at the top level.
first_name: "jordan"
last_name: "orelli"

# lists of things should be supported
items: [
    "one"
    2
    3.4
    ["five" 6 7.8]
]

# objects should be supported
hash: {key: "value" other_key: "other_value"}

other_hash: {
    key_1: "one"
    key_2: 2
    key_3: 3.4
    key_4: ["five" 6 7.8]
}

item_one: "this is item one"

# we should be able to reference variables defined earlier
# (this doesn't work yet)
# item_two: item_one

error: pathspec 'ex.moon' did not match any file(s) known to git


chopra: add the greatness of chopra to the bot

Former-commit-id: c3d0315097e16ed1074fb25ea730517637ee1ba6 [formerly 5ddaddb0f9d57d9fbc6151993c738989f1c474f0]
Former-commit-id: fabcc3091720a800c7739d7865bbb4bf0456d29e


autocycle: new module, try to regain op

autocycle: new module, try to regain op

cycleNeeded = (source, destination, arg) =>
  -- Don't care about our own events
  if source.nick == @config.nick
    return
  -- Check if we are the last nick in the channel
  counter = 0
  for nick, meta in pairs @channels[destination].nicks
    if nick == @config.nick
      -- Check if we have op
      for m in *meta.modes
        if m == 'o'
          return
    unless nick == @config.nick or nick == source.nick
      counter += 1

  if counter == 0
    @Log 'info', 'Cycling opless channel %s', destination
    @Part(destination)
    @Join(destination)

{
  PART: {
    cycleNeeded
  }
  QUIT: {
    cycleNeeded
  }
}

error: pathspec 'modules/autocycle.moon' did not match any file(s) known to git


duckduckgo: new module

duckduckgo: new module

Former-commit-id: 4a7dfea03f9da1ccc11b4cb6f6b5814e5cede048 [formerly 9590159b6697f797d37b3000351674c600829ce9]
Former-commit-id: b92331c66453186b87150c5f6bef8445e95614a4

json = require 'json'
simplehttp = require 'simplehttp'

urlEncode = (str, space) ->
  space = space or '+'

  str = str\gsub '([^%w ])', (c) ->
      string.format  "%%%02X", string.byte(c)
  return str\gsub(' ', space)

PRIVMSG:
  '^%pddg (.+)$': (source, destination, term) =>
    term = urlEncode term
    simplehttp "http://api.duckduckgo.com/?q=#{term}&format=json", (d) ->
      data = json.decode d
      out = {}
      topic = data.RelatedTopics[1] if data.RelatedTopics and #data.RelatedTopics>0
      if data.Heading == ''
        return
      table.insert out, "\002#{data.Heading}\002:"
      table.insert out, data.AbstractText unless data.AbstractText == ''
      table.insert out, data.Image unless data.Image == ''
      table.insert out, data.AbstractURL unless data.AbstractURL == ''
      table.insert out, data.Definition unless data.Definition == ''
      table.insert out, data.DefinitionText unless data.DefinitionText == ''
      if #out < 3
        table.insert out, topic.FirstURL
        table.insert out, topic.Text

      @Msg 'privmsg', destination, source, table.concat(out, ' ')

error: pathspec 'modules/duckduckgo.moon' did not match any file(s) known to git


clue: new module

clue: new module

-- Clue module requires clue data files and tosql.py from emnh/clue2

dbi = require 'DBI'
require'logging.console'
log = logging.console()
conn = false

attrs = {'grammar','reference', 'country', 'context', 'text'}

connect = ->
  conn, err = DBI.Connect('PostgreSQL', ivar2.config.dbname, ivar2.config.dbuser, ivar2.config.dbpass, ivar2.config.dbhost, ivar2.config.dbport)
  unless conn
    log\error "Unable to connect to DB: #{err}"
    return

  conn\autocommit(true)

dbh = ->
  connect! unless conn

  -- Check if connection is alive
  alive = conn\ping!
  connect! unless alive

  success, err = DBI.Do(conn, 'SELECT NOW()')
  unless success
    log\error "SQL Connection :#{err}"
    connect!

  return conn

lookup = (source, destination, lookup) =>
  stmt = dbh!\prepare [[
    SELECT
      tablename
    from
      pg_catalog.pg_tables
    where
      tablename like 'cl%'
    ]]
  stmt\execute!
  tables = {}
  for row in stmt\rows(true) -- true for column names
    tables[#tables+1] = row.tablename

  out = {}
  for dict in *tables
    out[dict] = {}
    stmt = dbh!\prepare [[
      SELECT
        *
      FROM
        ]]..dict..[[
      WHERE
        word = ?
      ]]
    stmt\execute lookup
    for row in stmt\rows(true) -- true for column names
      table.insert(out[dict], row)

  i = 0
  for dict, rows in pairs out
    res = {}
    dfrom = dict\sub(3, 4)
    to = dict\sub(5, 6)
    first = true
    for row in *rows
      i += 1
      if first
        table.insert(res, "[#{ivar2.util.bold row.word}]")
        table.insert(res, "(#{ivar2.util.italic dfrom}")
        table.insert(res, "-> #{ivar2.util.italic to})")
        first = false
      for attr in *attrs
        if row[attr] != nil and row[attr] != ''
          if attr == 'grammar'
            table.insert(res, "(#{row[attr]}. )")
          else
            table.insert(res, row[attr])
    say table.concat(res, ' ')
  if i == 0
    reply 'Nope.'

PRIVMSG:
  '^%pclue (.+)$': lookup

error: pathspec 'modules/clue.moon' did not match any file(s) known to git


naustver: rename and rework to support multiple

{:simplehttp, :json} = require'util'

windSpeedToName = (ws) ->
  windtext = ''
  unless ws
    return windtext
  if ws > 32
    windtext = 'orkan!'
  elseif ws > 28.5
    windtext = 'sterk storm'
  elseif ws > 24.5
    windtext = 'full storm'
  elseif ws > 20.8
    windtext = 'liten storm'
  elseif ws > 17.2
    windtext = 'sterk kuling'
  elseif ws > 13.9
    windtext = 'stiv kuling'
  elseif ws > 10.8
    windtext = 'liten kuling'
  elseif ws > 8
    windtext = 'frisk bris'
  elseif ws > 5.5
    windtext = 'laber bris'
  elseif ws > 3.4
    windtext = 'lett bris'
  elseif ws > 1.5
    windtext = 'svak vind'
  elseif ws > 0.3
    windtext = 'flau vind'
  else
    windtext = 'vindstille'
  return windtext

handleWeather = (source, destination, url) =>
    simplehttp url, (data) ->
      result = json.decode data
      if result
        result = result[1]
        temp = result.outtemp
        ws = result.windspeed / 3.6 -- correct unit
        rain = result.dayrain
        windtext = windSpeedToName(ws)
        wgs = windSpeedToName(result.windgust/3.6)
        if wgs ~= windtext
          wgs = ", #{wgs} i kasta"
        else
          wgs = ''

        time = result.datetime\sub(12, 16)

        msg = string.format '\002%.1f\002 °C, \002%.1f\002 m/s (%s%s), \002%.1f\002 mm nedbør (%s)', temp, ws, windtext, wgs, rain, time
        @Privmsg destination, msg

PRIVMSG:
  '^%pnaustvêr$': (source, destination) =>
    url = 'http://yr.hveem.no/api/now'
    handleWeather(@, source, destination, url)
  '^%pvêr$': (source, destination, input) =>
    url = 'http://yr.teigen.be/api/now'
    handleWeather(@, source, destination, url)

error: pathspec 'modules/amatyr.moon' did not match any file(s) known to git


Added tests for `merge_array` function

Added tests for `merge_array` function

remote: Support for password authentication was removed on August 13, 2021.
remote: Please see https://docs.github.com/en/get-started/getting-started-with-git/about-remote-repositories#cloning-with-https-urls for information on currently recommended modes of authentication.
fatal: Authentication failed for 'https://github.com/SuperPaintman/umka.git/'


purecss upgrade from 0.5.0 to 0.6.0

purecss upgrade from 0.5.0 to 0.6.0

html = require "lapis.html"

class MainLayout extends html.Widget
  @include require "views.mixin.favicon"
  @include require "lazuli.modules.user_management.views.mixin_menu"
  content: =>
    html_5 ->
      head ->
        meta charset:"utf-8"
        meta name:"viewport", content:"width=device-width, initial-scale=1"
        link rel:"stylesheet", href:"http://yui.yahooapis.com/pure/0.6.0/pure-min.css"
        link rel:"stylesheet", href:"http://yui.yahooapis.com/pure/0.6.0/grids-responsive-min.css"
        link rel:"stylesheet", href:"/static/css/main_layout.css"
        title @title or (@has_content_for("title") and @content_for("title")) or "Komischfarben"
        @mixFavicon!
      body ->
        div id:"layout",->
          a href:"#menu",id:"menuLink",class:"menu-link",->
            span ""
          div id:"menu",->
            div class:"pure-menu pure-menu-open",->
              a class:"pure-menu-heading", href:"/", "Komischfarben"
              @userManagementMixinMenu!
              ul ->
                li ->
                  a href:"https://github.com/nonchip/komischfarben2",style:"color:#555",target:"_blank","Github"
                li ->
                  a href:"https://github.com/nonchip/komischfarben2/issues",style:"color:#555",target:"_blank","Report issues"
          div id:"main",->
            if true or @has_content_for "header" then
              div class:"header",->
                tt "BETA"
                @content_for "header"
            div class:"content",->
              @content_for "inner"
        script src:"/static/js/main_layout.js"

html = require "lapis.html"

class MainLayout extends html.Widget
  @include require "views.mixin.favicon"
  @include require "lazuli.modules.user_management.views.mixin_menu"
  content: =>
    html_5 ->
      head ->
        meta charset:"utf-8"
        meta name:"viewport", content:"width=device-width, initial-scale=1"
        link rel:"stylesheet", href:"http://yui.yahooapis.com/pure/0.5.0/pure-min.css"
        link rel:"stylesheet", href:"http://yui.yahooapis.com/pure/0.5.0/grids-responsive-min.css"
        link rel:"stylesheet", href:"/static/css/main_layout.css"
        title @title or (@has_content_for("title") and @content_for("title")) or "Komischfarben"
        @mixFavicon!
      body ->
        div id:"layout",->
          a href:"#menu",id:"menuLink",class:"menu-link",->
            span ""
          div id:"menu",->
            div class:"pure-menu pure-menu-open",->
              a class:"pure-menu-heading", href:"/", "Komischfarben"
              @userManagementMixinMenu!
              ul ->
                li ->
                  a href:"https://github.com/nonchip/komischfarben2",style:"color:#555",target:"_blank","Github"
                li ->
                  a href:"https://github.com/nonchip/komischfarben2/issues",style:"color:#555",target:"_blank","Report issues"
          div id:"main",->
            if true or @has_content_for "header" then
              div class:"header",->
                tt "BETA"
                @content_for "header"
            div class:"content",->
              @content_for "inner"
        script src:"/static/js/main_layout.js"


Add a bit more for tests, but mark it as pending

require "busted"
require "tests.mock_love"

require "LunoPunk.Entity"
require "LunoPunk.Graphic"
require "LunoPunk.Mask"
require "LunoPunk.utils.moonscript"

describe "Entity", ->
	it "construct", ->
		e = Entity!
		assert.are.equal true, e.active
		assert.are.equal false, e.autoClear
		assert.are.equal 0, e\x!
		assert.are.equal 0, e\y!
		assert.are.equal 0, e.originX
		assert.are.equal 0, e.originY
		assert.are.equal 0, e.width
		assert.are.equal 0, e.height
		assert.are.equal true, e.visible
		assert.are.equal true, e.collidable
		assert.are.equal false, e.followCamera

		-- Follow Camera coverage
		e.followCamera = true
		assert.are.equal 0, e\x!
		assert.are.equal 0, e\y!
		LP.camera.x += 1
		assert.are.equal 1, e\x!
		assert.are.equal 0, e\y!
		LP.camera.y -= 1
		assert.are.equal -1, e\y!
		e.followCamera = false
		assert.are.equal 0, e\x!
		assert.are.equal 0, e\y!

		e = Entity 10, 15
		assert.are.equal 10, e\x!
		assert.are.equal 15, e\y!
		assert.are.equal 0, e.originX
		assert.are.equal 0, e.originY
		assert.are.equal 0, e.width
		assert.are.equal 0, e.height

		g = Graphic!
		g.x, g.y = 25, 30
		e = Entity 10, 15, g
		assert.are.equal g, e\graphic!
		assert.are.equal 10, e\x!
		assert.are.equal 15, e\y!
		assert.are.equal 0, e.originX
		assert.are.equal 0, e.originY
		assert.are.equal 0, e.width
		assert.are.equal 0, e.height

		m = Mask!
		e = Entity 10, 15, g, m
		assert.are.equal m, e\mask!
		assert.are.equal 10, e\x!
		assert.are.equal 15, e\y!
		assert.are.equal 0, e.originX
		assert.are.equal 0, e.originY
		assert.are.equal 0, e.width
		assert.are.equal 0, e.height

	-- TODO finish
	it "accessors", ->
		e = Entity!
		assert.are.equal 0, e\halfWidth!
		e.width = 10
		assert.are.equal 5, e\halfWidth!

		assert.are.equal 0, e\halfHeight!
		e.height = 20
		assert.are.equal 10, e\halfHeight!

		assert.are.equal 5, e\centerX!
		assert.are.equal 10, e\centerY!
		e.originX = 10
		assert.are.equal -5, e\centerX!
		e.originY = 10
		assert.are.equal 0, e\centerY!
		e\x 5
		assert.are.equal 0, e\centerX!
		e\y 10
		assert.are.equal 10, e\centerY!

		assert.are.equal -5, e\left!
		assert.are.equal 5, e\right!
		assert.are.equal 0, e\top!
		assert.are.equal 20, e\bottom!

	it "collidable", ->
		pending "Will write tests tomorrow"
		e = with Entity 5, 5
			.width = 10
			.height = 10

		-- Collider
		c = with Entity 5, 5
			.width = 10
			.height = 10

		-- No Scene
		assert.is.Nil e\collide c, 10, 10

		-- Not collidable
		e.collidable = false
		assert.is.Nil e\collide e, 10, 10

require "busted"
require "tests.mock_love"

require "LunoPunk.Entity"
require "LunoPunk.Graphic"
require "LunoPunk.Mask"
require "LunoPunk.utils.moonscript"

describe "Entity", ->
	it "construct", ->
		e = Entity!
		assert.are.equal true, e.active
		assert.are.equal false, e.autoClear
		assert.are.equal 0, e\x!
		assert.are.equal 0, e\y!
		assert.are.equal 0, e.originX
		assert.are.equal 0, e.originY
		assert.are.equal 0, e.width
		assert.are.equal 0, e.height
		assert.are.equal true, e.visible
		assert.are.equal true, e.collidable
		assert.are.equal false, e.followCamera

		-- Follow Camera coverage
		e.followCamera = true
		assert.are.equal 0, e\x!
		assert.are.equal 0, e\y!
		LP.camera.x += 1
		assert.are.equal 1, e\x!
		assert.are.equal 0, e\y!
		LP.camera.y -= 1
		assert.are.equal -1, e\y!
		e.followCamera = false
		assert.are.equal 0, e\x!
		assert.are.equal 0, e\y!

		e = Entity 10, 15
		assert.are.equal 10, e\x!
		assert.are.equal 15, e\y!
		assert.are.equal 0, e.originX
		assert.are.equal 0, e.originY
		assert.are.equal 0, e.width
		assert.are.equal 0, e.height

		g = Graphic!
		g.x, g.y = 25, 30
		e = Entity 10, 15, g
		assert.are.equal g, e\graphic!
		assert.are.equal 10, e\x!
		assert.are.equal 15, e\y!
		assert.are.equal 0, e.originX
		assert.are.equal 0, e.originY
		assert.are.equal 0, e.width
		assert.are.equal 0, e.height

		m = Mask!
		e = Entity 10, 15, g, m
		assert.are.equal m, e\mask!
		assert.are.equal 10, e\x!
		assert.are.equal 15, e\y!
		assert.are.equal 0, e.originX
		assert.are.equal 0, e.originY
		assert.are.equal 0, e.width
		assert.are.equal 0, e.height

	it "accessors", ->
		e = Entity!
		assert.are.equal 0, e\halfWidth!
		e.width = 10
		assert.are.equal 5, e\halfWidth!

		assert.are.equal 0, e\halfHeight!
		e.height = 20
		assert.are.equal 10, e\halfHeight!

		assert.are.equal 5, e\centerX!
		assert.are.equal 10, e\centerY!
		e.originX = 10
		assert.are.equal -5, e\centerX!
		e.originY = 10
		assert.are.equal 0, e\centerY!
		e\x 5
		assert.are.equal 0, e\centerX!
		e\y 10
		assert.are.equal 10, e\centerY!

		assert.are.equal -5, e\left!
		assert.are.equal 5, e\right!
		assert.are.equal 0, e\top!
		assert.are.equal 20, e\bottom!


excuse: new module

excuse: new module

PRIVMSG:
  '^%pexcuse': (source, destination) =>
    ivar2.util.simplehttp 'http://www.programmerexcuses.com/', (html) ->
      cruft, match = html\match[[(.-)]]
      if match
        say match

error: pathspec 'modules/excuse.moon' did not match any file(s) known to git


charset: new module to help people detect charset

Former-commit-id: 778c1ae5a528afe827c3003524034a63cb51701f
Former-commit-id: 30ac930e8b6ac75104f1d068b347aa2b777957b0

iconv = require'iconv'

iso2utf = iconv.new('UTF-8'.."//TRANSLIT", 'ISO-8859-1')
utf2iso = iconv.new('ISO-8859-1'.."//TRANSLIT", 'UTF-8')

toUtf = (s) ->
  iso2utf\iconv(s)

toIso = (s) ->
  utf2iso\iconv(s)

latinpattern = "^#{toIso'æøå'}%??"

PRIVMSG:
  '^æøå%??$': =>
    reply "UTF-8. Relax, you're doing fine."
  latinpattern: =>
    reply 'ISO-8559-1. Pls stahp.'
  '^Ã¦Ã¸Ã¥%??$': =>
    reply 'WTF8. Is very nice.'
  '^fxe%??$': =>
    reply 'fxe is Latin1 with 8th bit stripped'
  '^{%|}%??$': =>
    reply 'du sender NS-4551-1 (aka. ISO-646-NO).. og det er jo litt ut.'
  '^��?%??$': =>
    reply 'du sender replacement chars.'
  '^���?%??$': =>
    reply 'du sender replacement chars.'
  '^+AOYA+ADl?%??$': =>
    reply 'du sender UTF-7'
  '^%piconv (.+) (.+) (.+)$': (source, dest, frm, to, arg) =>
    convert = iconv.new("#{to\upper!}//TRANSLIT", frm\upper!)
    unless convert
      return say 'Invalid encoding given'
    nstr, err = convert\iconv(arg)
    if err
      return say err

    say nstr

error: pathspec 'modules/charset.moon' did not match any file(s) known to git


added incorrect rho function, cannot mutate state

return (path) ->
    return (state) ->
        x, y = 2, 1
        for t = 0, 23
            for z = 1, state.width
                state.setBit x, y, z, (state.getBit x, y, (((z - ((t + 1)(t + 2) / 2)) - 1 % state.width) + 1))
            x, y = y, (((2 * x) + (3 * y)) - 1 % 5) + 1
        return state

error: pathspec 'src/secua/sha3/steps/rho.moon' did not match any file(s) known to git


k: new module

k: new module

Former-commit-id: c40903ce06976a77d78890cbc321969c4f2d7986 [formerly 53638e292ed67909c77942ba49b071dee31079e8]
Former-commit-id: d8a8b0bde5f020ed61d52f75e52ca6ad801be9fa

-- k, reimplemented from dbot's k by byte[]
history = {}

PRIVMSG: {
  '^%pk$': (source, destination) =>
    t = {}
    max = 5
    for i = 1, max
      h = history[destination][math.random(#history[destination])]
      msg = h
      want = math.random(2, 4)
      getting = false
      msg = msg\gsub("\003%d?%d?,?%d?%d?", "")
      for w in msg\gmatch("[%w%[%]%{%}%|`%^_%-%|'\194-\244\128-\191]+") do
        if w == "http" or w == "https"
          break
        if not getting
          -- Less odds if not starting with letter.
          if w\find("^[%a']") or math.random(2) == 1
            if math.random(1, max + i + 1) <= i
              getting = true
        if getting
          if want > 0
            t[#t + 1] = w\lower()
          else
            break
          want = want - 1
    outline = table.concat(t, " ")
    @Msg 'privmsg', destination, source, outline
  (source, destination, argument) =>
    unless history[destination]
      history[destination] = {}
    table.insert history[destination], #history[destination]+1, argument
    if #history[destination] > 20
      table.remove history[destination], 1
}

error: pathspec 'modules/k.moon' did not match any file(s) known to git


github: add module for github web hooks

github: add module for github web hooks

Former-commit-id: 8cd8d4e9f37697b8598bf19d55eb91b9a0cf2c22 [formerly 2d4e3dcba210407c1c747ae12a729ca840dff128]
Former-commit-id: 2d4937e547021b0d52177043d2c52d525e1994aa

util = require 'util'

ivar2.webserver.regUrl '/github/(.*)', (req, res) ->
  destination = req.url\match('/github/(.+)/?$')
  destination = destination\gsub '%%23', '#'
  json = util.json.decode(req.body)

  repo = json.repository.full_name
  branch = json.ref\gsub 'refs/heads/', ''

  for c in *json.commits
    ivar2\Msg 'privmsg', destination, nil, "#{repo}: #{branch} #{c.author.username} #{c.message}"

  res\set_status(200)
  res\set_header('Content-Type', 'text/plain')
  res\set_body('ok')
  res\send()

return {}

error: pathspec 'modules/github.moon' did not match any file(s) known to git


markov: new module

markov: new module

brain = require 'brain' -- https://luarocks.org/modules/darkstalker/brain

-- Cache/Store bots
bots = {}

getbot = (destination) ->
  bot = bots[destination]
  if not bot then
    filename = "cache/markov.#{ivar2.network}.#{destination}.sqlite3"
    bot = brain.new(filename, 1)
    bots[destination] = bot

  return bot

markov = (source, destination, arg) =>
  brain = getbot(destination)
  say brain\reply(arg)

return {
  PRIVMSG: {
    '^%pm (.+)$': markov
    '^%pm$': markov
    (source, destination, arg) =>
      brain = getbot(destination)
      nick = source.nick
      if arg\sub(1,1) == '\001' and arg\sub(-1) == '\001'
        arg = arg\sub(9, -2)

      brain\begin_batch!
      brain\learn arg
      -- Close db (commit)
      brain\end_batch!
  }
}

error: pathspec 'modules/markov.moon' did not match any file(s) known to git


googleapi: new module

googleapi: new module

Former-commit-id: c2a374ae7eb64bd7d4469e1bfd8274717fc89518 [formerly 0c710a5f41e2f1e58fcae08437e0b97961c95fd5]
Former-commit-id: e5fbabe08d00dc0c4171619d68a7b304cdd2dab3

-- ported from luabot's google module. Thanks!
html2unicode = require'html'

PRIVMSG:
  '^%pg (.+)': (source, destination, query) =>
    data, err = ivar2.util.simplehttp 'http://ajax.googleapis.com/ajax/services/search/web?v=1.0&q='..ivar2.util.urlEncode(query), (data) ->

      if query == 'google'
        return reply "wow. That's really clever."

      unless data
        return reply "no response"

      if data\sub(1, 1) ~= '{' then
        return false, 'invalid reply?'

      resp = ivar2.util.json.decode(data)
      unless resp.responseData
        return reply 'no response data'

      res = resp.responseData.results
      if res[1]
        say(html2unicode(res[1].titleNoFormatting)..': '..res[1].unescapedUrl)
      else
        return reply 'no results'

error: pathspec 'modules/googleapi.moon' did not match any file(s) known to git


imageupload: new module for easy media sharing to IRC

Former-commit-id: ef85857840cdb2abe02929e38e2567f3a572ef25 [formerly 8208f1eebeac1ffc6f4cd995c8d27098ef8ecb23]
Former-commit-id: 17d4dd666d123277c15883ad58263ad8b34d4f3a

{:simplehttp, :json, :urlEncode} = require'util'
html2unicode = require 'html'
nixio = require'nixio'
ltn12 = require'ltn12'

urlbase = '/image/'

safe = (fn) ->
  f, ext = fn\match'^(.*)%.(.-)$'
  f = f\gsub '[^%w%-]', ''
  return f..'.'..ext

ivar2.webserver.regUrl "#{urlbase}(.*)$", (req, res) ->
  send = (body, code, content_type) ->
    if not code then code = 200
    if not content_type then content_type = 'text/html'
    res\set_status code
    res\set_header 'Content-Type', content_type
    res\set_header 'Content-Length', #body
    res\set_body body
    res\send!
    res.on_error = (res, req, err) ->
      ivar2\Log 'error', 'imageupload: error: %s', err

  file = req.url\match '/file/(.*)$'
  if file
    fn = "cache/images/#{safe file}"
    size = nixio.fs.stat(fn).size
    fd = ltn12.source.file(io.open fn, 'rb')
    --body = fd\read '*a'
    --fd\close!
    content_type = 'image/jpeg'
    if file\match '.png'
      content_type = 'image/png'
    if file\match '.svg'
      content_type = 'image/svg'
    if file\match '.mp4'
      content_type = 'video/mp4'
    if file\match '.mp3'
      content_type = 'audio/mp3'

    res\set_status code
    res\set_header 'Content-Type', content_type
    res\set_header 'Content-Length', size
    res\set_body fd
    res\send!
    ivar2\Log 'error', 'imageupload: serving: %s', fn

    return

  channel = req.url\match('channel=(.+)')
  unless channel
    send 'Invalid channel', 404
    return

  channel = html2unicode channel
  unescaped_channel = channel\gsub '%%23', '#'

  html = [[
  <!DOCTYPE html>

  <meta charset="utf-8">
  <meta http-equiv="X-UA-Compatible" content="IE=Edge">
  <meta name="viewport" content="width=device-width, initial-scale=1.0, maximum-scale=1.0, user-scalable=no">
  <meta name="mobile-web-app-capable" content="yes">
  <meta name="apple-mobile-web-app-status-bar-style" content="black">
  <meta name="apple-mobile-web-app-title" content="IRCSNAP ]]..unescaped_channel..[[">
  <meta name="theme-color" content="#8f4099">

  <title>IRCSNAP</title>

Share to IRC app - ]]..unescaped_channel..[[ edition

Fill in your nick/name and optional text and then click on one of the two yellow buttons to attach image. It will appear instantly on IRC!</p
    <form>

        <input id="sender" type="text" required>

        Your name

        <input id="text" type="text" required>

        Attach text

    </form>

Uploading. Percent complete: 0 %

      📷 Snap picture! <input type="file" accept="image/*" id="capturei" capture="camera" style="visibility:hidden;">
      📂 Browse gallery! <input type="file" accept="image/*" id="capturef" style="visibility:hidden;">

        What is this?

Share to IRC is a simple web app for sharing media directly from your device to an IRC channel.

Click Tools -> Add to homescreen to create a convenient shortcut for easy and fast access to sharing

  ]]
  if req.method == 'POST'
    fn = req.headers['X-Filename']
    sender = req.headers['X-Sender'] or ''
    text = req.headers['X-Text'] or ''
    file = req.body
    ivar2\Log 'info', "imageupload: Recieved file name: <#{fn}> datalen: #{#file}, sender: <#{sender}> text: <#{text}>, channel: <#{unescaped_channel}>"
    if fn and file
      html = 'Ok'
      realfn = "#{os.time!}-#{safe fn}"
      save = ->
        fd = nixio.open "cache/images/#{realfn}", 'w+', 0400
        written = fd\write file
        fd\sync!
        fd\close!
        if sender ~= ''
          sender = "<#{sender}> "
        msg = "[IRCSNAP] #{sender}#{text\sub(1, 100)} http://irc.lart.no:#{ivar2.config.webserverport}#{urlbase}file/#{realfn}"
        ivar2\Privmsg unescaped_channel, msg
      ok, err = pcall(save)
      unless ok
        ivar2\Log 'error', "imageupload: Error during saving upload: %s", err
    else
      html = 'Not OK'

  send html

-- Attempt to create the cache folder.
nixio.fs.mkdir('cache/images')

PRIVMSG:
  '^%pircsnap$': (source, destination) =>
    channel = urlEncode destination
    say "http://irc.lart.no:#{ivar2.config.webserverport}#{urlbase}?channel=#{channel}"

error: pathspec 'modules/imageupload.moon' did not match any file(s) known to git


user management added

user management added

lazuli = require "lazuli"
import cached from require "lapis.cache"

class extends lazuli.Application
  @include "lazuli.modules.user_management"
  layout: require "views.main_layout"
  [index: "/"]: cached exptime: 1, [1]: =>
    @html ->
      h1 "this page is still in development"
  handle_404: cached exptime: 1, [1]: =>
    @html ->
      div style:"margin:20%",->
        h1 "Oops!"
        h2 "That page just vanished..."

lazuli = require "lazuli"
import cached from require "lapis.cache"

class extends lazuli.Application
  layout: require "views.main_layout"
  [index: "/"]: cached exptime: 1, [1]: =>
    @html ->
      h1 "this page is still in development"
  handle_404: cached exptime: 1, [1]: =>
    @html ->
      div style:"margin:20%",->
        h1 "Oops!"
        h2 "That page just vanished..."


stonks: new module

stonks: new module

util = require'util'
simplehttp = util.simplehttp
json = util.json
urlEncode = util.urlEncode

quote = (s, d, a) =>
  data = simplehttp "https://query1.finance.yahoo.com/v10/finance/quoteSummary/#{a}?modules=price"
  data = json.decode(data)
  res = data['quoteSummary']['result'][1]
  price = res.price

  out = {}

  c =  price.currency
  if price.currencySymbol
    c = price.currencySymbol
  out[#out+1] = c
  out[#out+1] = price.regularMarketPrice.fmt
  per = price.regularMarketChangePercent.fmt
  if price.regularMarketChangePercent.raw > 0
    per = util.green per
  else
    per = util.red per
  out[#out+1] = "("..per ..")"

  if price.postMarketPrice.fmt
    out[#out+1] = "("..price.postMarketPrice.fmt..")"

  say table.concat(out, ' ')

PRIVMSG:
  '^%pq (.*)$': quote

error: pathspec 'modules/stonks.moon' did not match any file(s) known to git


wc2014: new module for World Cup 2014

wc2014: new module for World Cup 2014

Former-commit-id: 1b01bb73f01749b89d0304283e424eccde4cc32b [formerly 3f2606089ddf7990ebf2a33226e33b8def9a98b4]
Former-commit-id: 97425db822793f0692fd451a3da57a394fb3ccf7

URL = 'http://worldcup.sfg.io/matches/'

bold = ivar2.util.bold

formatMatch = (m) ->
  out = "#{m.home_team.country} #{bold m.home_team.goals} - #{bold m.away_team.goals} #{m.away_team.country}"
  if m.status == 'in progress'
    out ..= " " .. ivar2.util.underline "In progress!"
  return out

PRIVMSG:
  '^%pwc today$': =>
    ivar2.util.simplehttp URL..'today', (json) ->
      data = ivar2.util.json.decode(json)
      out = {}
      for _,m in pairs(data)
        out[#out+1] = "["..formatMatch(m).."]"
      say table.concat(out, ' ')
  '^%pwc tomorrow$': =>
    ivar2.util.simplehttp URL..'tomorrow', (json) ->
      data = ivar2.util.json.decode(json)
      out = {}
      for _,m in pairs(data)
        out[#out+1] = "["..formatMatch(m).."]"
      say table.concat(out, ' ')
  '^%pwc$': =>
    ivar2.util.simplehttp URL..'current', (json) ->
      data = ivar2.util.json.decode(json)
      m = data[1]
      unless m
        return reply "No match being played."
      else
        say "#{m.home_team.code} #{bold m.home_team.goals} - #{bold m.away_team.goals} #{m.away_team.code}"

error: pathspec 'modules/wc2014.moon' did not match any file(s) known to git


scale: new module

scale: new module

Former-commit-id: e06a21185653fbf4ad37647b8064e88c55543163 [formerly ab73dbd751dfac7bad6f93f1091d5da9cc44ce68]
Former-commit-id: 3ae12993aa44238e2eb2f46debe70b73935eb72a

scale = (s, d, val, max, frm, to) =>
    x1, x2 = '', ''
    max = ivar2.util.trim(max or '')
    val = tonumber(val)
    if tonumber(max)
      max = tonumber(max)
    else
      if #max
        to = max
        x2 = max
        max = 10

    pos = math.min(val, max)
    pos = math.max(pos, 0)

    unless frm
      x1 = ''
    else
      x1 = frm
    unless to
      x2 = ''
    else
      x2 = to

    unless to
      if frm ~= nil
        x2 = frm

    char = '█'

    left = char\rep(pos)
    right = char\rep(max-pos)

    say "#{x1}[#{ivar2.util.green left}#{ivar2.util.bold ivar2.util.italic ivar2.util.yellow 'X'}#{ivar2.util.red right}]#{x2}"

PRIVMSG:
  '^%pscale ([0-9]+)$': scale
  '^%pscale ([0-9]+) ([0-9]+)$': scale
  '^%pscale ([0-9]+) ([a-zA-ZæøåÆØÅ%-]+)$': scale
  '^%pscale ([0-9]+) ([0-9]+) ([a-zA-ZæøåÆØÅ#%-]+)$': scale
  '^%pscale ([0-9]+) ([0-9]+) ([a-zA-ZæøåÆØÅ#%-]+)$': scale
  '^%pscale ([0-9]+) ([0-9]+) (.+) (.+)$': scale

error: pathspec 'modules/scale.moon' did not match any file(s) known to git


benis: new module stolen.

benis: new module stolen.

-- code from q66's module from luabot ported from rfs benis module
reduce = (fun, list, def) ->
  for k, v in ipairs(list)
    def = fun(def, v)
  return def

rand = math.random
tconc = table.concat

benisify = (s) ->
  reduce (acc, f) ->
    f(acc),
    {
      (s) -> s\lower()
      (s) -> s\gsub('x', 'cks')
      (s) -> s\gsub('ing', 'in')
      (s) -> s\gsub('you', 'u')
      (s) -> s\gsub 'oo', ->
        ('u')\rep(rand(1, 5))
      (s) ->
        s\gsub '[%w_]%z', (x) ->
          x\sub(1, 1)\rep(rand(1, 2))
      (s) ->
        s\gsub 'ck', ->
          'g'\rep(rand(1, 5))
      (s) ->
        s\gsub '(t+)%f[aeiouys ]', (x) ->
          ('d')\rep(#x)
      (s) ->
        s\gsub '(t+)$', (x) ->
          ('d')\rep(#x)
      (s) -> s\gsub('p', 'b'),
      (s) -> s\gsub('%f[%w_]the%f[^%w_]', 'da'),
      (s) -> s\gsub('%f[%w_]c', 'g'),
      (s) -> s\gsub('%f[%w_]is%f[^%w_]', 'are'),
      (s) -> s\gsub '(c+)(.)', (x, y) ->
        (y == 'e' or y == 'i' or y == 'y') and (x .. y) or ('g')\rep(rand(1, 5)) ..  (y == 'c' and 'g' or y)
      (s) ->
        s\gsub '([qk]+)%f[aeiouy ]', ->
          ('g')\rep(rand(1, 5))
      (s) ->
        s\gsub '([qk]+)$', ->
          ('g')\rep(rand(1, 5))
      (s) ->
          endswith = s\find '[?!.]$'
          repf = (x) ->
              ret = x\rep(rand(2, 5)) .. ' '
              for i = 1, rand(2, 5)
                  ret = ret .. (':')\rep(rand(1, 2)) .. ('D')\rep(rand(1, 4))
              return ret
          unless endswith then return s\gsub('$', repf)
          s\gsub('[?!.]+', repf)
    }, s

PRIVMSG:
  '^%pbenis (.*)$':(source, destination, arg) =>
    say(benisify(arg))

error: pathspec 'modules/benis.moon' did not match any file(s) known to git


googleapi: new module

googleapi: new module


msbot: module for microsoft bot api

msbot: module for microsoft bot api

Former-commit-id: d054e331c4555141efeee3b7b645adc2830638e2
Former-commit-id: aeacd940cad4fcc99ed4019475cec8cc912f3d79

{:green, :red, :simplehttp, :json, :urlEncode} = require'util'

key2 = ivar2.config.msbotApiKey
_api = 'https://api.projectoxford.ai/vision/v1.0/'

phrases = {
  'That is'
  'That\'s',
  'I\'m looking at',
  'Looks like',
  'Probably',
  'I can see',
  'I\'m seeing'
}

PRIVMSG:
  '^%panalyze (.+)$': (source, destination, arg) =>
    simplehttp {url:_api..'analyze?visualFeatures=Categories,Tags,Description,Faces,ImageType,Color&details=Celebrities', data:json.encode({url:arg}), method:'POST', headers:{['Ocp-Apim-Subscription-Key']:key2}}, (res) ->
      say res
  '^%pocr (.+)$': (source, destination, arg) =>
    simplehttp {url:_api..'ocr?', data:json.encode({url:arg}), method:'POST', headers:{['Ocp-Apim-Subscription-Key']:key2}}, (res) ->
      d = json.decode res
      out = {}

      if not res or not d
        say 'Microsoftbot not clever enough.'

      for _, r in ipairs d.regions
        for _, w in ipairs r.lines
          for _, t in ipairs w.words
            out[#out+1] = t.text
          out[#out+1] = '\n'
        out[#out+1] = '\n'

      say table.concat(out, ' ')

  '^%pcaption (.+)$': (source, destination, arg) =>
    simplehttp {url:_api..'analyze?visualFeatures=Categories,Tags,Description,Faces,ImageType,Color&details=Celebrities', data:json.encode({url:arg}), method:'POST', headers:{['Ocp-Apim-Subscription-Key']:key2}}, (res) ->
      d = json.decode res
      out = {}

      description = d.description
      unless description
        say 'Microsoftbot not clever enough.'

      for _, t in ipairs(description.captions)
        out[#out+1] = t.text
        out[#out+1] = "I am #{math.floor t.confidence*100}% sure"

      if #out < 1 then
        out[#out+1] = 'no caption, but tags are '..table.concat(description.tags, ', ')

      for _, f in ipairs d.faces
        out[#out+1] = "face: age #{f.age}, #{f.gender}"

      choice = math.random(1, #phrases)
      phrase = phrases[choice]
      say phrase .. " " .. table.concat(out, ', ')

error: pathspec 'modules/msbot.moon' did not match any file(s) known to git


Added Moonscript version

Added Moonscript version

remote: Support for password authentication was removed on August 13, 2021.
remote: Please see https://docs.github.com/en/get-started/getting-started-with-git/about-remote-repositories#cloning-with-https-urls for information on currently recommended modes of authentication.
fatal: Authentication failed for 'https://github.com/evolbug/Comfy.git/'


insult: new module

insult: new module

Former-commit-id: 42a100040517593fbbb826fdcd322f63fc793872 [formerly 055e3c3236a61c8c94190a3c7ffa482a1b764776]
Former-commit-id: b0fb9c807ffb450397cd51cc625242dfcc3b4cc0

-- a port of https://github.com/LizardGamer/tumblr-argument-generator/blob/develop/app/assets/js/main.js
-- original by Lokaltog
-- github pls no delete

intro = {
  'burn in hell',
  'check your privilege',
  'fuck you',
  'fuck off',
  'please die',
  'rot in hell',
  'screw you',
  'shut the fuck up',
  'shut up',
  'kill yourself',
  'drop dead'
}
description = {
  'deluded',
  'fucking',
  'god damn',
  'judgemental',
  'worthless'
}
marginalized = {
  {
    'activist',
    'agender',
    'appearance',
    'asian',
    'attractive',
    'bi',
    'bigender',
    'black',
    'celestial',
    'chubby',
    'closet',
    'color',
    'curvy',
    'dandy',
    'deathfat',
    'demi',
    'differently abled',
    'disabled',
    'diversity',
    'dysphoria',
    'ethnic',
    'ethnicity',
    'fat love',
    'fat',
    'fatist',
    'fatty',
    'female',
    'feminist',
    'genderfuck',
    'genderless',
    'hair',
    'height',
    'indigenous',
    'intersectionality',
    'invisible',
    'kin',
    'lesbianism',
    'little person',
    'marginalized',
    'minority',
    'multigender',
    'non-gender',
    'non-white',
    'obesity',
    'otherkin',
    'pansexual',
    'polygender',
    'privilege',
    'prosthetic',
    'queer',
    'radfem',
    'skinny',
    'smallfat',
    'stretchmark',
    'thin',
    'third-gender',
    'trans*',
    'transfat',
    'transgender',
    'transman',
    'transwoman',
    'trigger',
    'two-spirit',
    'womyn',
    'poc',
    'woc'
  },
  {
    'chauvinistic',
    'misogynistic',
    'nphobic',
    'oppressive',
    'phobic',
    'shaming',
    'denying',
    'discriminating',
    'hypersexualizing',
    'intolerant',
    'racist',
    'sexualizing'
  }
}
privileged = {
  {
    'able-bodied',
    'appearance',
    'attractive',
    'binary',
    'bi',
    'cis',
    'cisgender',
    'cishet',
    'hetero',
    'male',
    'smallfat',
    'thin',
    'white'
  },
  {
    'ableist',
    'classist',
    'normative',
    'overprivileged',
    'patriarch',
    'sexist',
    'privileged'
  }
}
finisher = {
  'asshole',
  'bigot',
  'oppressor',
  'piece of shit',
  'rapist',
  'scum',
  'shitlord',
  'subhuman',
  'misogynist',
  'nazi'
}

getRandomItem = (array) ->
  array[math.random(1, #array)]

generateTerm = ->
  getRandomItem({
    'a',
    'bi',
    'dandy',
    'demi',
    'gender',
    'multi',
    'pan',
    'poly'
  }) .. getRandomItem({
    'amorous',
    'femme',
    'fluid',
    'queer',
    'romantic',
    'sexual',
  })

generateArgument = ->
  buf = {
    getRandomItem(intro),
    ', you ',
    getRandomItem(description),
    ' ',
    getRandomItem({
      generateTerm(),
      getRandomItem(marginalized[1])
    }),
    '-',
    getRandomItem(marginalized[2]),
    ', ',
    getRandomItem(privileged[1]),
    '-',
    getRandomItem(privileged[2]),
    ' ',
    getRandomItem(finisher),
    '.',
  }
  table.concat(buf)

PRIVMSG:
  '^%pinsult$': (source, destination) =>
    @Msg 'privmsg', destination, source, generateArgument()
  '^%pinsult (.+)$': (source, destination, arg) =>
    @Msg 'privmsg', destination, source, arg..": "..generateArgument()

error: pathspec 'modules/insult.moon' did not match any file(s) known to git


button class for CC computers.

button class for CC computers.

Still very much a wip.

class Button
	new: (@text, x, y, width, height) =>
		@corner_a = {
			x: x
			y: y
		}

		@corner_b = {
			x: x + string.len @text
			y: y + height
		}

		@text_pos = {
			x: @corner_1.x + 1
			y: (@corner_4.y - @corner_1.y) / 2
		}

	render: =>

		for i1=@corner_1.y, @corner_4.y

button1 = Button "foo", 2, 5, 10, 5

button2 = Button "bar", 15, 5, 10, 5

print ""

print "hello world"

print ""

print button1.text
print button1.corner_1.x, button1.corner_1.y
print button1.corner_2.x, button1.corner_2.y
print button1.corner_3.x, button1.corner_3.y
print button1.corner_4.x, button1.corner_4.y

print ""

print button2.text
print button2.corner_1.x, button2.corner_1.y
print button2.corner_2.x, button2.corner_2.y
print button2.corner_3.x, button2.corner_3.y
print button2.corner_4.x, button2.corner_4.y

print ""

error: pathspec 'scripts/button.moon' did not match any file(s) known to git


rss: simple module for subscribing to rss feeds

Former-commit-id: e22201d7b58f66b2c989fac35f55718de4cc8dcc [formerly c31ec1eac940826389ead3ee3c439dc568f7ea40]
Former-commit-id: caf719defdc9db36d2fe7f04dd526b3e26684c57

-- Module for subscribing to RSS and announce to channels
feedparser = require 'feedparser' -- http://feedparser.luaforge.net/
{:simplehttp, :json, :urlEncode, :bold} = require'util'
html2unicode = require 'html'

moduleName = 'rss'
key = moduleName
store = ivar2.persist

-- Function that add functionality to extract specific information for specific sites
feedSpecific = (link, id, entry) ->
  if link == 'https://comics.io/my/'

    title = entry.summary\match '
(.-)
' or ''
    img, alt = entry.summary\match '
'
    return "#{html2unicode title} #{img} #{html2unicode alt}"

  return "#{entry.title} - #{entry.link}"

poll = ->
  for c,_ in pairs(ivar2.channels)
    channelKey = key..':'..c
    channels = store[channelKey] or {}
    -- TODO separate channel logic for polling logic for the cases where
    -- two channels subscribe to the same feed
    for name, channel in pairs(channels)
      lastKey = channelKey .. ':' .. name .. ':last'
      lastmKey = lastKey..'modified'
      last = store[lastKey] or ''
      lastlastModified = store[lastmKey]
      simplehttp {url:channel.url,headers:{'If-Modified-Since':lastlastModified}}, (data, url, response) ->
        lastModified = response.headers['Last-Modified']
        if not lastModified
          lastModified = response.headers['Date']

        store[lastmKey] = lastModified

        -- Unmodified content
        if response.status_code == 304
          -- return from the simplehttp callback
          return

        feed, err = feedparser.parse data
        if err
          ivar2\Log 'error', "#{moduleName}: Error during parsing: <#{err}> data for feed: <#{name}> with URL <#{channel.url}>"
        else
          out = {}
          for i, e in pairs(feed.entries)
            -- Attempt to get a unique entry ID
            guid = e.guid
            if not guid then guid = e.id
            if not guid then guid = e.link
            if not guid
              ivar2\Log 'error', "#{moduleName}: No GUID when parsing entry: <#{e}> data for feed: <#{name}> with URL <#{channel.url}>"

            if guid == last
              break

            table.insert out, "[#{bold name}]: #{feedSpecific(feed.feed.id, feed.feed.link, e)}"

            -- First entry is the newest entry, look for that one on the
            -- next iteration
            if i == 1
              store[lastKey] = guid

            -- First time
            if last == ''
              break

          if #out > 0
            say "RSS "..table.concat(out, ', ')

subscribe = (source, destination, name, url) =>
  channelKey = key..':'..destination
  channels = store[channelKey] or {}
  channels[name] = {channel:destination, name:name, url:url}
  store[channelKey] = channels
  reply "Ok. Subscribed to #{bold name}"
  poll!

unsubscribe = (source, destination, name) =>
  channelKey = key..':'..destination
  channels = store[channelKey] or {}
  unless channels[name]
    reply "Wasn't subscribed. But, sure."
  else
    channels[name] = nil
    store[channelKey] = channels
    lastKey = channelKey .. ':' .. name .. ':last'
    store[lastKey] = nil
    reply "Ok. Stopped caring about #{bold name}"

list = (source, destination) =>
  channelKey = key..':'..destination
  channels = store[channelKey] or {}
  out = {}
  for name, game in pairs(channels)
      out[#out+1] = name
  say "Subscribed to: #{table.concat(out, ', ')}"

-- Start the subscribe poller
ivar2\Timer(moduleName, 300, 300, poll)

PRIVMSG:
  '^%prss latest (.*)': getLatest
  '^%prss subscribe (.-) (.*)': subscribe
  '^%prss unsubscribe (.*)': unsubscribe
  '^%prss list': list
  -- for debugging
  '^%prss poll': poll

error: pathspec 'modules/rss.moon' did not match any file(s) known to git


Added math.Vector

Added math.Vector

require "LunoPunk.geometry.Point"

export ^

class Vector extends Point
	new: (x = 0, y = 0) =>
		super x, y

	-- Calculates the dotProduct between two points
	--
	-- @param	p	The other point
	--
	-- @return The dot product.
	dot: (p) => @x * p.x + @y * p.y

	-- Calculates the cross product
	-- @return A Vector
	cross: => Vector @y, -@x

	toString: => string.format "<%f, %f>", @x, @y

	__tostring: => @toString!

error: pathspec 'LunoPunk/math/Vector.moon' did not match any file(s) known to git