require "json"

module Discord
  module SnowflakeConverter
    def self.from_json(parser : JSON::PullParser) : UInt64
      str = parser.read_string_or_null
      str.not_nil!.to_u64
    end

    def self.to_json(value : UInt64, io : IO)
      io.puts(value.to_s)
    end
  end

  module REST
    # A response to the Get Gateway REST API call.
    struct GatewayResponse
      JSON.mapping(
        url: String
      )
    end
  end

  module Gateway
    # TODO: Expand this
    struct ReadyPayload
      JSON.mapping(
        v: UInt8
      )
    end

    struct HelloPayload
      JSON.mapping(
        heartbeat_interval: UInt32,
        _trace: Array(String)
      )
    end
  end

  struct User
    JSON.mapping(
      username: String,
      id: {type: UInt64, converter: SnowflakeConverter},
      discriminator: String,
      avatar: String
    )
  end

  struct Message
    JSON.mapping(
      type: UInt8,
      content: String,
      id: {type: UInt64, converter: SnowflakeConverter},
      author: User
    )
  end
end

require "json"
require "time/format"

module Discord
  module SnowflakeConverter
    def self.from_json(parser : JSON::PullParser) : UInt64
      str = parser.read_string_or_null
      str.not_nil!.to_u64
    end

    def self.to_json(value : UInt64, io : IO)
      io.puts(value.to_s)
    end
  end

  module REST
    # A response to the Get Gateway REST API call.
    struct GatewayResponse
      JSON.mapping(
        url: String
      )
    end
  end

  module Gateway
    # TODO: Expand this
    struct ReadyPayload
      JSON.mapping(
        v: UInt8
      )
    end

    struct HelloPayload
      JSON.mapping(
        heartbeat_interval: UInt32,
        _trace: Array(String)
      )
    end
  end

  struct User
    JSON.mapping(
      username: String,
      id: {type: UInt64, converter: SnowflakeConverter},
      discriminator: String,
      avatar: String
    )
  end

  struct Message
    JSON.mapping(
      type: UInt8,
      content: String,
      id: {type: UInt64, converter: SnowflakeConverter},
      channel_id: {type: UInt64, converter: SnowflakeConverter},
      author: User,
      timestamp: {type: Time, converter: Time::Format::ISO_8601_DATE},
      tts: Bool,
      mention_everyone: Bool,
      mentions: Array(User)
    )
  end
end

Add some more fields to Message

Add some more fields to Message


require "./cartel/*"
require "http/server"

module Cartel
    view_handler = ViewHandler.new(File.join(__DIR__, "views"))

    server = HTTP::Server.new(8080) do |request|
        case request.path
        when "/"
            HTTP::Response.ok "text/html", view_handler.load("index.html")
        else
            HTTP::Response.not_found
        end
    end

    puts "Listening on :8080"
    server.listen
end

require "./cartel/*"
require "http/server"

module Cartel
    view_handler = ViewHandler.new(File.join(__DIR__, "views"))

    server = HTTP::Server.new(8080) do |request|
        puts "[#{request.method}] #{request.path}"

        case request.path
        when "/"
            HTTP::Response.ok "text/html", view_handler.load("index.html")
        else
            HTTP::Response.not_found
        end
    end

    puts "Listening on :8080"
    server.listen
end

Print requests to the console

Print requests to the console


module ControllerHelpers
  private def html(body : String)
    @response.set(200, body)
    @response.header("Content-Type", "text/html")
  end

  private def text(body : String)
    @response.set(200, body)
    @response.header("Content-Type", "text/plain")
  end

  private def json(body : String)
    @response.set(200, body)
    @response.header("Content-Type", "application/json")
  end

  private def redirect_to(location : String)
    @response.header "Location", location
    @response.status = 303
  end
end

module ControllerHelpers
  private def html(body : String, status=200)
    @response.set(status, body)
    @response.header("Content-Type", "text/html")
  end

  private def text(body : String, status=200)
    @response.set(status, body)
    @response.header("Content-Type", "text/plain")
  end

  private def json(body : String, status=200)
    @response.set(status, body)
    @response.header("Content-Type", "application/json")
  end

  private def redirect_to(location : String)
    @response.header "Location", location
    @response.status = 303
  end
end

Add status to controller helpers

Add status to controller helpers


require "./workspace"

module Scry

  class Initialize

    private getter workspace : Workspace

    def initialize(params : InitializeParams, @msg_id : Int32)
      @workspace = Workspace.new(
        root_path: params.root_path,
        process_id: params.process_id,
        max_number_of_problems: 100
      )
    end

    def run
      { @workspace, response }
    end

    private def response
      ServerCapabilities.new(@msg_id)
    end

  end

end

require "./workspace"

module Scry

  class Initialize

    private getter workspace : Workspace

    def initialize(params : InitializeParams, @msg_id : Int32)
      @workspace = Workspace.new(
        root_path: params.root_path,
        process_id: params.process_id,
        problems_limit: 100
      )
      ENV["CRYSTAL_PATH"] = %(#{@workspace.root_path}/lib:#{ENV["CRYSTAL_PATH"]})
    end

    def run
      { @workspace, response }
    end

    private def response
      ServerCapabilities.new(@msg_id)
    end

  end

end

Add current lib to CRYSTAL_PATH

Add current lib to CRYSTAL_PATH


require "./world"

class Play

  # @@ makes this a private class variable
  @@World_Width  = 150
  @@World_Height = 40

  def self.run
    world = World.new(
      width: @@World_Width,
      height: @@World_Height,
    )

    puts world.render

    total_tick = 0
    total_render = 0

    while true
      tick_start = Time.now.epoch_f
      world._tick
      tick_finish = Time.now.epoch_f
      tick_time = (tick_finish - tick_start)
      total_tick += tick_time
      avg_tick = (total_tick / world.tick)

      render_start = Time.now.epoch_f
      rendered = world.render
      render_finish = Time.now.epoch_f
      render_time = (render_finish - render_start)
      total_render += render_time
      avg_render = (total_render / world.tick)

      output = "##{world.tick}"
      output += " - World tick took #{_f(tick_time)} (#{_f(avg_tick)})"
      output += " - Rendering took #{_f(render_time)} (#{_f(avg_render)})";
      output += "\n#{rendered}"
      system("clear")
      puts output
    end
  end

  private def self._f(value)
    "%.5f" % value.round(5)
  end

end

Play.run

require "./world"

class Play

  # @@ makes this a private class variable
  @@World_Width  = 150
  @@World_Height = 40

  def self.run
    world = World.new(
      width: @@World_Width,
      height: @@World_Height,
    )

    puts world.render

    total_tick = 0
    total_render = 0

    while true
      tick_start = Time.now.to_unix_f
      world._tick
      tick_finish = Time.now.to_unix_f
      tick_time = (tick_finish - tick_start)
      total_tick += tick_time
      avg_tick = (total_tick / world.tick)

      render_start = Time.now.to_unix_f
      rendered = world.render
      render_finish = Time.now.to_unix_f
      render_time = (render_finish - render_start)
      total_render += render_time
      avg_render = (total_render / world.tick)

      output = "##{world.tick}"
      output += " - World tick took #{_f(tick_time)} (#{_f(avg_tick)})"
      output += " - Rendering took #{_f(render_time)} (#{_f(avg_render)})";
      output += "\n#{rendered}"
      system("clear")
      puts output
    end
  end

  private def self._f(value)
    "%.5f" % value.round(5)
  end

end

Play.run

Fix CrystalLang implementation on latest version


require "json"

module Discord
  module REST
    # A response to the Get Gateway REST API call.
    struct GatewayResponse
      JSON.mapping(
        url: String
      )
    end
  end

  module Gateway
    # TODO: Expand this
    struct ReadyPayload
      JSON.mapping(
        v: UInt8
      )
    end

    struct MessageCreatePayload
      JSON.mapping(
        type: UInt8,
        content: String,
        id: String,
        author: User
      )
    end

    struct HelloPayload
      JSON.mapping(
        heartbeat_interval: UInt32,
        _trace: Array(String)
      )
    end
  end

  struct User
    JSON.mapping(
      username: String,
      id: String,
      discriminator: String,
      avatar: String
    )
  end
end

require "json"

module Discord
  module SnowflakeConverter
    def self.from_json(parser : JSON::PullParser) : UInt64
    end

    def self.to_json(value : String, io : IO)
    end
  end

  module REST
    # A response to the Get Gateway REST API call.
    struct GatewayResponse
      JSON.mapping(
        url: String
      )
    end
  end

  module Gateway
    # TODO: Expand this
    struct ReadyPayload
      JSON.mapping(
        v: UInt8
      )
    end

    struct MessageCreatePayload
      JSON.mapping(
        type: UInt8,
        content: String,
        id: String,
        author: User
      )
    end

    struct HelloPayload
      JSON.mapping(
        heartbeat_interval: UInt32,
        _trace: Array(String)
      )
    end
  end

  struct User
    JSON.mapping(
      username: String,
      id: String,
      discriminator: String,
      avatar: String
    )
  end
end

Create a converter to convert strings to and from snowflakes


module Messenger::Bot
  # A wrapper for Facebook's Graph API
  class GraphAPI
    # Build an API instance with which you can send messages.
    # Pass *api_version* (current is 2.6) an *access_token*, which you need to
    # authenticate yourself to the Graph API
    def initialize(@api_version : String, @access_token : String)
    end

    # Deliver a pre-built JSON message *message* through the Graph API
    # Returns the delivered message's recipient id and message id
    # Error handling not really built-in.
    def deliver_message(message : String)
      response = HTTP::Client.post("https://graph.facebook.com/v#{@api_version}/me/messages?access_token=#{@access_token}",
        body: message,
        headers: HTTP::Headers{"Content-Type" => "application/json"})
      result = GraphResponseMessage.from_json(response.body)
      return result.recipient_id, result.message_id
    end
  end
end

module Messenger::Bot
  # A wrapper for Facebook's Graph API
  class GraphAPI
    # Build an API instance with which you can send messages.
    # Pass *api_version* (current is 2.6) an *access_token*, which you need to
    # authenticate yourself to the Graph API
    def initialize(@api_version : String, @access_token : String)
    end

    # Deliver a pre-built JSON message *message* through the Graph API
    # Returns the delivered message's recipient id and message id
    # Error handling not really built-in.
    def deliver_message(message : String)
      response = HTTP::Client.post("https://graph.facebook.com/v#{@api_version}/me/messages?access_token=#{@access_token}",
        body: message,
        headers: HTTP::Headers{"Content-Type" => "application/json"})
      begin
        result = GraphResponseMessage.from_json(response.body)
        return result.recipient_id, result.message_id
      rescue ex
        puts ex.message
        return 0, 0
      end
    end

    # Notify the user that we're working on a response, and then deliver the
    # final message once it's ready
    # This is still work in progress, reading up on https://crystal-lang.org/docs/guides/concurrency.html
    def deferred_send(recipient_id : Int64, channel : Channel(GraphAPI))
      spawn do
        message = Builder.new(recipient_id)
                  .build_wait_message
        deliver_message(message)
        channel.send(self)
      end
    end
  end
end

Handle message send exceptions, speculative deferred send


module Toro
  def self.drive(router)
    Driver.new(router)
  end

  def self.drive(router, method, path)
    drive(router).call(method, path)
  end

  class Driver
    getter router : Toro::Router.class

    def initialize(@router)
    end

    def call(req : HTTP::Request)
      io  = MemoryIO.new
      res = HTTP::Server::Response.new(io)

      @router.call(HTTP::Server::Context.new(req, res))

      res.close

      HTTP::Client::Response.from_io(io.rewind, decompress: false)
    end

    def call(method : String, path : String)
      call(HTTP::Request.new(method, path))
    end
  end
end

module Toro
  def self.drive(router)
    Driver.new(router)
  end

  def self.drive(router, method, path)
    drive(router).call(method, path)
  end

  class Driver
    getter router : Toro::Router.class

    def initialize(@router)
    end

    def call(req : HTTP::Request)
      io  = IO::Memory.new
      res = HTTP::Server::Response.new(io)

      @router.call(HTTP::Server::Context.new(req, res))

      res.close

      HTTP::Client::Response.from_io(io.rewind, decompress: false)
    end

    def call(method : String, path : String)
      call(HTTP::Request.new(method, path))
    end
  end
end

Use IO::Memory instead of MemoryIO

Use IO::Memory instead of MemoryIO


class AnnouncementForm < Announcement::BaseForm
  allow title
end

class AnnouncementForm < Announcement::BaseForm
  allow title

  def call
    validate_length title, min: 10
  end

  private def validate_length(field, min)
    if (field.value || "").size < min
      field.add_error "is too short"
    end
  end
end

Validate length of the title

Validate length of the title


require "../../spec_helper"

describe Isot::Parser do
  context "with a WSDL defining xs:schema without targetNamespace" do

    xml = <<-XML
      <definitions xmlns="http://schemas.xmlsoap.org/wsdl/"
        xmlns:xs="http://www.w3.org/2001/XMLSchema"
        targetNamespace="http://def.example.com">
        <types>
          <xs:schema elementFormDefault="qualified">
            <xs:element name="Save">
              <xs:complexType></xs:complexType>
            </xs:element>
          </xs:schema>
        </types>
      </definitions>
    XML

    subject = Isot::Parser.new(XML.parse(xml))
    subject.parse

    # Don"t know if real WSDL files omit targetNamespace from xs:schema,
    # but I suppose we should do something reasonable if they do.

    # it "defaults to the target namespace from xs:definitions" do
    #   subject.types["Save"][:namespace].should eq("http://def.example.com")
    # end

  end
end

require "../../spec_helper"

describe Isot::Parser do
  context "with a WSDL defining xs:schema without targetNamespace" do
    # xml = <<-XML
    #   <definitions xmlns="http://schemas.xmlsoap.org/wsdl/"
    #     xmlns:xs="http://www.w3.org/2001/XMLSchema"
    #     targetNamespace="http://def.example.com">
    #     <types>
    #       <xs:schema elementFormDefault="qualified">
    #         <xs:element name="Save">
    #           <xs:complexType></xs:complexType>
    #         </xs:element>
    #       </xs:schema>
    #     </types>
    #   </definitions>
    # XML

    # subject = Isot::Parser.new(XML.parse(xml))
    # subject.parse

    # Don"t know if real WSDL files omit targetNamespace from xs:schema,
    # but I suppose we should do something reasonable if they do.

    # it "defaults to the target namespace from xs:definitions" do
    #   subject.types["Save"][:namespace].should eq("http://def.example.com")
    # end

  end
end

Remove the spec for no target.

Remove the spec for no target.


require "./spec_helper"

describe Discord do
  # TODO: Write tests

  it "works" do
    false.should eq(true)
  end
end

require "./spec_helper"

describe Discord do
end

Remove the default test from the spec

Remove the default test from the spec


require "http/client"
require "openssl/ssl/context"

require "./mappings"
require "./version"

module Discord
  module REST
    SSL_CONTEXT = OpenSSL::SSL::Context::Client.new
    USER_AGENT  = "DiscordBot (discordcr - no URL yet, #{Discord::VERSION})"

    def request(endpoint_key : Symbol, method : String, url : String | URI, headers : HTTP::Headers, body : String | Nil)
      headers["Authorization"] = @token
      headers["User-Agent"] = USER_AGENT
      HTTP::Client.exec(method: method, url: url, headers: headers, body: body, tls: SSL_CONTEXT)
    end

    API_BASE = "https://discordapp.com/api/v6"

    GET  = "GET"
    POST = "POST"

    def gateway
      response = request(
        :gateway,
        GET,
        API_BASE + "/gateway",
        HTTP::Headers.new,
        nil
      )

      # TODO: Investigate failed nil assertion with body_io
      GatewayResponse.from_json(response.body)
    end

    def send_message(channel_id, content)
      response = request(
        :send_message,
        POST,
        API_BASE + "/channels/#{channel_id}/messages",
        HTTP::Headers{"Content-Type" => "application/json"},
        {content: content}.to_json
      )
    end
  end
end

require "http/client"
require "openssl/ssl/context"

require "./mappings"
require "./version"

module Discord
  module REST
    SSL_CONTEXT = OpenSSL::SSL::Context::Client.new
    USER_AGENT  = "DiscordBot (https://github.com/meew0/discordcr, #{Discord::VERSION})"

    def request(endpoint_key : Symbol, method : String, url : String | URI, headers : HTTP::Headers, body : String | Nil)
      headers["Authorization"] = @token
      headers["User-Agent"] = USER_AGENT
      HTTP::Client.exec(method: method, url: url, headers: headers, body: body, tls: SSL_CONTEXT)
    end

    API_BASE = "https://discordapp.com/api/v6"

    GET  = "GET"
    POST = "POST"

    def gateway
      response = request(
        :gateway,
        GET,
        API_BASE + "/gateway",
        HTTP::Headers.new,
        nil
      )

      # TODO: Investigate failed nil assertion with body_io
      GatewayResponse.from_json(response.body)
    end

    def send_message(channel_id, content)
      response = request(
        :send_message,
        POST,
        API_BASE + "/channels/#{channel_id}/messages",
        HTTP::Headers{"Content-Type" => "application/json"},
        {content: content}.to_json
      )
    end
  end
end

Add a correct URL to the User-Agent

Add a correct URL to the User-Agent


module Raven
  class Interface::HTTP < Interface
    property! url : String
    property! method : String

    any_json_property :data, :query_string, :cookies, :headers, :env

    def self.sentry_alias
      :request
    end
  end
end

module Raven
  class Interface::HTTP < Interface
    property! url : String
    property! method : String
    property query_string : String?
    property cookies : String?

    any_json_property :env, :headers, :data

    def self.sentry_alias
      :request
    end
  end
end

Change Interface::HTTP to match protocol spec


module Raven
  class Context
    # FIXME
    # @[ThreadLocal]
    @@current : self?

    def self.current
      @@current ||= new
    end

    def self.clear!
      @@current = nil
    end

    class_getter os_context : AnyHash::JSON do
      {
        name:           Raven.sys_command("uname -s"),
        version:        Raven.sys_command("uname -v"),
        build:          Raven.sys_command("uname -r"),
        kernel_version: Raven.sys_command("uname -a") || Raven.sys_command("ver"), # windows
      }.to_any_json
    end

    class_getter runtime_context : AnyHash::JSON do
      v = Raven.sys_command_compiled("crystal -v").match /^(.+) (\d+.*)$/
      _, name, version = v.not_nil!
      {
        name:    name,
        version: version,
      }.to_any_json
    end

    any_json_property :contexts, :extra, :tags, :user

    def initialize
      @contexts = {
        os:      self.class.os_context,
        runtime: self.class.runtime_context,
      }.to_any_json
    end
  end
end

module Raven
  class Context
    # FIXME
    # @[ThreadLocal]
    @@current : self?

    def self.current
      @@current ||= new
    end

    def self.clear!
      @@current = nil
    end

    class_getter os_context : AnyHash::JSON do
      {
        name:           Raven.sys_command("uname -s"),
        version:        Raven.sys_command("uname -v"),
        build:          Raven.sys_command("uname -r"),
        kernel_version: Raven.sys_command("uname -a") || Raven.sys_command("ver"), # windows
      }.to_any_json
    end

    class_getter runtime_context : AnyHash::JSON do
      v = Raven.sys_command_compiled("crystal -v").match /^(.+?) (\d+.*)$/
      _, name, version = v.not_nil!
      {
        name:    name,
        version: version,
      }.to_any_json
    end

    any_json_property :contexts, :extra, :tags, :user

    def initialize
      @contexts = {
        os:      self.class.os_context,
        runtime: self.class.runtime_context,
      }.to_any_json
    end
  end
end

Fix Regex matching Crystal version

Fix Regex matching Crystal version


module Command
  command "suggest", "suggest someone to contact" do |_args, repository|
    confirmed = Proc(String, Bool).new do |answer|
      answer == "Y" || answer.empty?
    end

    contacts = repository.all.sort

    suggested_contact = contacts.first

    last_contact = suggested_contact.last_contacted

    days = if last_contact == :NEVER
             Float32::INFINITY
           else
             time_since_last_contact = Time.now - last_contact.as(Time)

             time_since_last_contact.total_days.ceil.to_i
           end

    suggestion_message = if last_contact == :NEVER
                "You haven't sent #{suggested_contact.name} an encouragement before! Would you like to do that now? [Y/n]"
              else
                "It's been #{days} day(s) since you last contacted\n#{suggested_contact.name}. Want to send them a quick encouragement now? [Y/n]"
              end

    print suggestion_message

    answer = gets.as(String).chomp.upcase

    if confirmed.call(answer)
      puts "Type your message to #{suggested_contact.name}, then press ENTER:"

      message_to_send = gets.as(String).strip

      MessageSender.send(suggested_contact, message_to_send)
    end

    :OK
  end
end

module Command
  command "suggest", "suggest someone to contact" do |_args, repository|
    confirmed = Proc(String, Bool).new do |answer|
      answer == "Y" || answer.empty?
    end

    contacts = repository.all.sort

    suggested_contact = contacts.first

    last_contact = suggested_contact.last_contacted

    days = if last_contact == :NEVER
             Float32::INFINITY
           else
             time_since_last_contact = Time.now - last_contact.as(Time)

             time_since_last_contact.total_days.ceil.to_i
           end

    suggestion_message = if last_contact == :NEVER
                "You haven't sent #{suggested_contact.name} an encouragement before! Would you like to do that now? [Y/n]"
              else
                "It's been #{days} day(s) since you last contacted\n#{suggested_contact.name}. Want to send them a quick encouragement now? [Y/n]"
              end

    print suggestion_message

    answer = gets.as(String).chomp.upcase

    if confirmed.call(answer)
      puts "Type your message to #{suggested_contact.name}, then press ENTER:"

      message_to_send = gets.as(String).strip

      sent_successfully = MessageSender.send(suggested_contact, message_to_send)

      repository.mark_as_contacted(suggested_contact) if sent_successfully
    end

    :OK
  end
end

Update contacted_at if message sent successfully


require "http/client"

module Discordcr
  module REST
    def request(endpoint_key : Symbol, method : String, url : String | URI, headers : HTTP::Headers | Nil, body : String | Nil)
      HTTP::Client.exec(method: method, url: url, headers: headers, body: body, tls: true)
    end
  end
end

require "http/client"

module Discordcr
  module REST
    def request(endpoint_key : Symbol, method : String, url : String | URI, headers : HTTP::Headers | Nil, body : String | Nil)
      headers["Authorization"] = @token
      HTTP::Client.exec(method: method, url: url, headers: headers, body: body, tls: true)
    end
  end
end

Add an Authorization header to the request method


module Amber
  module Exceptions
    class DuplicateRouteError < Exception
      def initialize(route : Route)
        super("Route: #{route.verb} #{route.resource} is duplicated.")
      end
    end

    class RouteNotFound < Exception
      def initialize(request)
        super("The request was not found.")
      end
    end

    class Forbidden < Exception
      def initialize(message)
        super("The request was not found.")
      end
    end
  end
end

module Amber
  module Exceptions
    class DuplicateRouteError < Exception
      def initialize(route : Route)
        super("Route: #{route.verb} #{route.resource} is duplicated.")
      end
    end

    class RouteNotFound < Exception
      def initialize(request)
        super("The request was not found.")
      end
    end

    class Forbidden < Exception
      def initialize(message)
        super("The request was not found.")
      end
    end

    module Validator
      class MissingValidationRules < Exception
        def initialize
          super("No validation rules defined for this validator.")
        end
      end

      class ValidationFailed < Exception
        def initialize(errors)
          super("Validation failed. #{errors}s")
        end
      end
    end
  end
end

Add Exceptions for param validations

Add Exceptions for param validations


require "file_utils"

FileUtils.rm_rf "test_tables"
FileUtils.mkdir_p "test_tables"

module Storage
  module TableManager
    @@tables = {} of String => Table

    def self.find(name)
      if table = @@tables[name]?
        table
      else
        @@tables[name] = Table.create("test_tables/" + name)
      end
    end
  end
end

at_exit do
  FileUtils.rm_rf "test_tables"
end

require "file_utils"

FileUtils.mkdir_p "/tmp/rethinkdb-lite/test_tables"

module Storage
  module TableManager
    @@tables = {} of String => Table

    def self.find(name)
      if table = @@tables[name]?
        table
      else
        @@tables[name] = Table.create("/tmp/rethinkdb-lite/test_tables/" + name)
      end
    end
  end
end

Move temporary path to /tmp

Move temporary path to /tmp


require "json"

module Discordcr
  module REST
    # A response to the Get Gateway REST API call.
    struct GatewayResponse
      JSON.mapping(
        url: String
      )
    end
  end

  struct GatewayPacket
    JSON.mapping(
      op: UInt8,
      d: JSON::Any,
      s: UInt32 | Nil,
      t: String | Nil
    )
  end
end

require "json"

module Discordcr
  module REST
    # A response to the Get Gateway REST API call.
    struct GatewayResponse
      JSON.mapping(
        url: String
      )
    end
  end

  struct GatewayPacket
    JSON.mapping(
      op: UInt8,
      d: JSON::Any,
      s: UInt32 | Nil,
      t: String | Nil
    )
  end

  struct MessageCreatePayload
    JSON.mapping(
      type: UInt8,
      content: String,
      id: String,
      author: User
    )
  end
end

Create a mapping for MESSAGE_CREATE events


require "json"

module Discordcr
  module REST
    # A response to the Get Gateway REST API call.
    struct GatewayResponse
      JSON.mapping(
        url: String
      )
    end
  end

  module Gateway
    struct MessageCreatePayload
      JSON.mapping(
        type: UInt8,
        content: String,
        id: String,
        author: User
      )
    end
  end

  struct User
    JSON.mapping(
      username: String,
      id: String,
      discriminator: String,
      avatar: String
    )
  end
end

require "json"

module Discordcr
  module REST
    # A response to the Get Gateway REST API call.
    struct GatewayResponse
      JSON.mapping(
        url: String
      )
    end
  end

  module Gateway
    struct MessageCreatePayload
      JSON.mapping(
        type: UInt8,
        content: String,
        id: String,
        author: User
      )
    end

    struct HelloPayload
      JSON.mapping(
        heartbeat_interval: UInt32,
        _trace: Array(String)
      )
    end
  end

  struct User
    JSON.mapping(
      username: String,
      id: String,
      discriminator: String,
      avatar: String
    )
  end
end

Define a mapping for the op10 payload

Define a mapping for the op10 payload


require "http/client"
require "openssl/ssl/context"

require "./mappings"

module Discordcr
  module REST
    SSL_CONTEXT = OpenSSL::SSL::Context::Client.new

    def request(endpoint_key : Symbol, method : String, url : String | URI, headers : HTTP::Headers | Nil, body : String | Nil)
      HTTP::Client.exec(method: method, url: url, headers: headers, body: body, tls: SSL_CONTEXT)
    end

    API_BASE = "https://discordapp.com/api/v6"

    GET = "GET"

    def gateway
      response = request(
        :gateway,
        GET,
        API_BASE + "/gateway",
        HTTP::Headers.new,
        nil
      )

      puts response.body

      GatewayResponse.from_json(response.body_io)
    end
  end
end

require "http/client"
require "openssl/ssl/context"

require "./mappings"

module Discordcr
  module REST
    SSL_CONTEXT = OpenSSL::SSL::Context::Client.new

    def request(endpoint_key : Symbol, method : String, url : String | URI, headers : HTTP::Headers | Nil, body : String | Nil)
      HTTP::Client.exec(method: method, url: url, headers: headers, body: body, tls: SSL_CONTEXT)
    end

    API_BASE = "https://discordapp.com/api/v6"

    GET = "GET"

    def gateway
      response = request(
        :gateway,
        GET,
        API_BASE + "/gateway",
        HTTP::Headers.new,
        nil
      )

      GatewayResponse.from_json(response.body_io)
    end
  end
end

Remove an unused debug call

Remove an unused debug call


module Caoutchouc
  module Shell
    class Autocomplete
      getter :text, :position, :buffer

      def self.initialize_autocomplete!
        Readline.autocomplete do |text|
          new(text).complete
        end
      end

      def initialize(@text, @position = nil, @buffer = nil)
        @buffer ||= Readline.line_buffer
        @position ||= Readline.point
      end

      def complete : Array(String)
        if beginning_of_line?
          complete_for_command
        else
          complete_for_arguments(find_command)
        end
      end

      private def complete_for_command : Array(String)
        result = [] of String
        Command.all.each do |cmd|
          if cmd.name.starts_with?(text)
            result << cmd.name
          end
        end
        if result.count > 1
          result.insert(0, common_denominator(result))
        end
        return result
      end

      private def complete_for_arguments(command) : Array(String)
        return [] of String
      end

      private def beginning_of_line?
        position == 0
      end

      private def find_command : Command|Nil
        if text == ""
          nil
        else
          command_name = text.split.first
          Command.find(command_name)
        end
      end

      def common_denominator(results : Array(String))
        candidate = text
        max_size = results.map(&.length).min
        (text.length..max_size).each do |size|
          if results.map{|res| res[0..size]}.uniq.length == 1
            candidate = results[0][0..size]
          end
        end
        candidate
      end
    end
  end
end

module Caoutchouc
  module Shell
    class Autocomplete
      getter :text, :position, :buffer

      def self.initialize_autocomplete!
        Readline.autocomplete do |text|
          new(text).complete
        end
      end

      def initialize(@text, @position = nil, @buffer = nil)
        @buffer ||= Readline.line_buffer
        @position ||= Readline.point
      end

      def complete : Array(String)
        if beginning_of_line?
          complete_for_command
        else
          complete_for_arguments(find_command)
        end
      end

      private def complete_for_command : Array(String)
        result = [] of String
        Command.all.each do |cmd|
          if cmd.name.starts_with?(text)
            result << cmd.name
          end
        end
        if result.count > 1
          result.insert(0, common_denominator(result))
        end
        return result
      end

      private def complete_for_arguments(command) : Array(String)
        return [] of String
      end

      private def beginning_of_line?
        text == buffer
      end

      private def find_command : Command|Nil
        if text == ""
          nil
        else
          command_name = text.split.first
          Command.find(command_name)
        end
      end

      def common_denominator(results : Array(String))
        candidate = text
        max_size = results.map(&.length).min
        (text.length..max_size).each do |size|
          if results.map{|res| res[0..size]}.uniq.length == 1
            candidate = results[0][0..size]
          end
        end
        candidate
      end
    end
  end
end

Repair autocompletion system broken with 3c18e1feb that was a bit too naive

We should try to autocomplete on command as long as current buffer
contains only one word, and is the currently selected text...


require "http/web_socket"
require "json"

require "./rest"

module Discordcr
  class Client
    include REST

    def initialize(@token : String, @client_id : UInt64)
    end

    def run
      url = URI.parse(gateway.url)
      @websocket = websocket = HTTP::WebSocket.new(
        host: url.host.not_nil!,
        path: "#{url.path}/?encoding=json&v=6",
        port: 443,
        tls: true
      )

      websocket.on_message(&->on_message(String))
      websocket.on_close(&->on_close(String))
      websocket.run
    end

    private def on_close(message : String)
      # TODO: make more sophisticated
      puts "Closed with: " + message
    end

    OP_HELLO = 10

    private def on_message(message : String)
      json = JSON.parse(message)

      case json["op"]
      when OP_HELLO
        handle_hello(json["d"]["heartbeat_interval"])
      else
        puts "Unsupported message: #{message}"
      end

      nil
    end

    private def handle_hello(heartbeat_interval)
      spawn do
        loop do
          puts "Sending heartbeat"
          @websocket.not_nil!.send({op: 1, d: 0}.to_json)
          sleep heartbeat_interval.as_i.milliseconds
        end
      end
    end
  end
end

require "http/web_socket"
require "json"

require "./rest"

module Discordcr
  class Client
    include REST

    def initialize(@token : String, @client_id : UInt64)
    end

    def run
      url = URI.parse(gateway.url)
      @websocket = websocket = HTTP::WebSocket.new(
        host: url.host.not_nil!,
        path: "#{url.path}/?encoding=json&v=6",
        port: 443,
        tls: true
      )

      websocket.on_message(&->on_message(String))
      websocket.on_close(&->on_close(String))
      websocket.run
    end

    private def on_close(message : String)
      # TODO: make more sophisticated
      puts "Closed with: " + message
    end

    OP_DISPATCH = 0
    OP_HELLO = 10

    private def on_message(message : String)
      json = JSON.parse(message)

      case json["op"]
      when OP_HELLO
        handle_hello(json["d"]["heartbeat_interval"])
      when OP_DISPATCH
        handle_dispatch(json["t"], json["d"])
      else
        puts "Unsupported message: #{message}"
      end

      nil
    end

    private def handle_hello(heartbeat_interval)
      spawn do
        loop do
          puts "Sending heartbeat"
          @websocket.not_nil!.send({op: 1, d: 0}.to_json)
          sleep heartbeat_interval.as_i.milliseconds
        end
      end
    end

    private def handle_dispatch(type, data)
    end
  end
end

Add a handler for dispatch events

Add a handler for dispatch events


require "./BenningtonWebsite/*"
require "kemal"

get "/" do
	if Time.now.month === (5..10)
		next_term = "Fall#{Time.now.year}"
	else
		next_term = "Spring#{Time.now.year + 1}"
	end
	curriculum_link = "http://curriculum.bennington.edu/#{next_term}/"
	render "src/views/index.html.ecr"
end

get "/map.pdf" do |env|
	# TODO Figure out why this isn't working, there is a post up on SO.
	env.response.headers["Content-Type"] = "application/pdf"
	env.response.headers["Content-Disposition"] = %(inline;filename="myfile.pdf")
end

Kemal.run

require "./BenningtonWebsite/*"
require "kemal"

get "/" do
	if (5..10).includes? Time.now.month
		next_term = "Fall#{Time.now.year}"
	else
		next_term = "Spring#{Time.now.year + 1}"
	end
	curriculum_link = "http://curriculum.bennington.edu/#{next_term}/"
	render "src/views/index.html.ecr"
end

get "/map.pdf" do |env|
	# TODO Figure out why this isn't working, there is a post up on SO.
	env.response.headers["Content-Type"] = "application/pdf"
	env.response.headers["Content-Disposition"] = %(inline;filename="myfile.pdf")
end

Kemal.run

Fix bug where date curriculum dates were incorrect


require "../spec_helper"
require "random/secure"

module Franklin
  describe Item do
    subject { Item.new(id, title, author, format) }
    let(:id) { Random::Secure.hex }
    let(:title) { "Ender's Game" }
    let(:author) { "Orson Scott Card" }
    let(:format) { "eBook" }

    it "has an id" do
      expect(subject.id).to eq(id)
    end

    it "has a title" do
      expect(subject.title).to eq(title)
    end

    it "has an author" do
      expect(subject.author).to eq(author)
    end

    it "has a format" do
      expect(subject.format).to eq(format)
    end

    it "can be compared to other items by value" do
      expect(subject).not_to eq(described_class.new(Random::Secure.hex, title, author, format))
      expect(subject).to eq(described_class.new(id, title, author, format))
    end
  end
end

require "../spec_helper"
require "random/secure"

module Franklin
  describe Item do
    title = "Ender's Game"
    author = "Orson Scott Card"
    format = "eBook"

    it "has an id" do
      id = Random::Secure.hex
      subject = Item.new(id, title, author, format)

      subject.id.should eq(id)
    end

    it "has a title" do
      id = Random::Secure.hex
      subject = Item.new(id, title, author, format)

      subject.title.should eq(title)
    end

    it "has an author" do
      id = Random::Secure.hex
      subject = Item.new(id, title, author, format)

      subject.author.should eq(author)
    end

    it "has a format" do
      id = Random::Secure.hex
      subject = Item.new(id, title, author, format)

      subject.author.should eq(author)
    end

    it "can be compared to other items by value" do
      id = Random::Secure.hex
      subject = Item.new(id, title, author, format)

      subject.should_not eq(Item.new(Random::Secure.hex, title, author, format))
      subject.should eq(Item.new(id, title, author, format))
    end
  end
end

Use variables instead of let and subject


macro generate_abstract_def(signature)
  def {{signature.id}}
    fail "Abstract method \#{{signature.id}} called on #{self.class}"
  end
end

module Generate
  struct View
    property config
    property logger

    generate_abstract_def render
    generate_abstract_def log

    def initialize(@config, @logger)
    end

    def render_with_log
      log
      render
    end
  end

  struct FileView < View
    generate_abstract_def path
    generate_abstract_def to_s(io)
    generate_abstract_def full_path

    def render
      File.write(full_path, to_s)
    end

    def log
      logger.info("File #{full_path}")
    end
  end
end

macro generate_template(name, base, template, path)
  struct {{name.id}} < {{base.id}}
    ecr_file "{{__DIR__.id}}/{{template.id}}"

    def path
      {{path}}
    end
  end
end

module Generate
  struct View
    property config
    property logger

    abstract def render
    abstract def log

    def initialize(@config, @logger)
    end

    def render_with_log
      log
      render
    end
  end

  struct FileView < View
    abstract def path
    abstract def to_s(io)
    abstract def full_path

    def render
      File.write(full_path, to_s)
    end

    def log
      logger.info("File #{full_path}")
    end
  end
end

macro generate_template(name, base, template, path)
  struct {{name.id}} < {{base.id}}
    ecr_file "{{__DIR__.id}}/{{template.id}}"

    def path
      {{path}}
    end
  end
end

Use abstract methods from language

Use abstract methods from language


require "json"

module Discordcr
  module REST
    # A response to the Get Gateway REST API call.
    struct GatewayResponse
      JSON.mapping(
        url: String
      )
    end
  end

  struct GatewayPacket
    JSON.mapping(
      op: UInt8,
      d: Nil,
      s: UInt32 | Nil,
      t: String | Nil
    )
  end
end

require "json"

module Discordcr
  module REST
    # A response to the Get Gateway REST API call.
    struct GatewayResponse
      JSON.mapping(
        url: String
      )
    end
  end

  struct GatewayPacket
    JSON.mapping(
      op: UInt8,
      d: JSON::Any,
      s: UInt32 | Nil,
      t: String | Nil
    )
  end
end

Use JSON::Any for the payload type in GatewayPacket


require "http/websocket"

require "./rest"

module Discordcr
  class Client
    include REST

    def initialize(@token : String, @client_id : UInt64)
    end

    def run
      url = gateway.url
      @websocket = HTTP::WebSocket.new(URI.parse(url))
    end
  end
end

require "http/websocket"

require "./rest"

module Discordcr
  class Client
    include REST

    def initialize(@token : String, @client_id : UInt64)
    end

    def run
      url = gateway.url
      @websocket = HTTP::WebSocket.new(URI.parse(url))
    end

    private def on_message(String)
    end
  end
end

Define a handler method for gateway messages


# This is the default error serializer generated by Lucky.
# Feel free to customize it in any way you like.
class ErrorSerializer < BaseSerializer
  def initialize(
    @message : String,
    @details : String? = nil,
    @param : String? = nil # If there was a problem with a specific param
  )
  end

  def render
    {message: @message, param: @param, details: @details}
  end
end

# This is the default error serializer generated by Lucky.
# Feel free to customize it in any way you like.
class ErrorSerializer < BaseSerializer
  def initialize(
    @message : String,
    @details : String? = nil,
    @param : String? = nil # so you can track which param (if any) caused the problem
  )
  end

  def render
    {message: @message, param: @param, details: @details}
  end
end

Clarify param arg in ErrorSerializer

Clarify param arg in ErrorSerializer


require "http/client"

module Discordcr
  module REST
    def request(endpoint_key : Symbol, method : String, url : String | URI, headers : HTTP::Headers | Nil, body : String | Nil)
      headers["Authorization"] = @token
      HTTP::Client.exec(method: method, url: url, headers: headers, body: body, tls: true)
    end
  end
end

require "http/client"

require "./mappings"

module Discordcr
  module REST
    def request(endpoint_key : Symbol, method : String, url : String | URI, headers : HTTP::Headers | Nil, body : String | Nil)
      headers["Authorization"] = @token
      HTTP::Client.exec(method: method, url: url, headers: headers, body: body, tls: true)
    end

    API_BASE = "https://discordapp.com/api/v6"

    GET = "get"

    def gateway
      response = request(
        :gateway,
        GET,
        API_BASE + "/gateway",
        HTTP::Headers.new,
        nil
      )

      puts response.body

      GatewayResponse.from_json(response.body_io)
    end
  end
end

Implement the Get Gateway API call

Implement the Get Gateway API call


require "./converters"

module Discord
  module REST
    # A response to the Get Gateway REST API call.
    struct GatewayResponse
      JSON.mapping(
        url: String
      )
    end
  end
end

require "./converters"

module Discord
  module REST
    # A response to the Get Gateway REST API call.
    struct GatewayResponse
      JSON.mapping(
        url: String
      )
    end

    # A response to the Get Guild Prune Count REST API call.
    struct PruneCountResponse
      JSON.mapping(
        pruned: UInt32
      )
    end
  end
end

Add a mapping for the response of the prune count endpoint


module Jennifer::Model
  module OptimisticLocking
    macro with_optimistic_lock(locking_column = lock_version)
      def self.locking_column
        {{locking_column.stringify}}
      end

      def locking_column
        self.class.locking_column
      end

      {% if locking_column != "lock_version" %}
        def lock_version
          {{locking_column}}
        end

        def self._lock_version
          _{{locking_column}}
        end
      {% end %}

      def increase_lock_version!
        self.{{locking_column}} = {{locking_column}} + 1
      end

      def reset_lock_version!
        @{{locking_column}} -= 1
        @{{locking_column}}_changed = false
      end
    end
  end
end

module Jennifer::Model
  module OptimisticLocking
    # Add optimistic locking to model
    # By default, it uses column `lock_version : Int32` as lock
    # The column used as lock must be type `Int32` or `Int64` and the default value of 0
    # You can use a different column name as lock by passing the column name to the method.
    #
    # ```
    # class MyModel < Jennifer::Model::Base
    #   with_optimistic_lock
    #
    #   mapping(
    #     id: {type: Int32, primary: true},
    #     lock_version: {type: Int32, default: 0},
    #   )
    # end
    # ```
    # Or use a custom column name for the locking column:
    # ```
    # class MyModel < Jennifer::Model::Base
    #   with_optimistic_lock(custom_lock)
    #
    #   mapping(
    #     id: {type: Int32, primary: true},
    #     custom_lock: {type: Int32, default: 0},
    #   )
    # end
    # ```
    macro with_optimistic_lock(locking_column = lock_version)
      def self.locking_column
        {{locking_column.stringify}}
      end

      def locking_column
        self.class.locking_column
      end

      {% if locking_column != "lock_version" %}
        def lock_version
          {{locking_column}}
        end

        def self._lock_version
          _{{locking_column}}
        end
      {% end %}

      def increase_lock_version!
        self.{{locking_column}} = {{locking_column}} + 1
      end

      def reset_lock_version!
        @{{locking_column}} -= 1
        @{{locking_column}}_changed = false
      end
    end
  end
end

Add documentation for the macro

Add documentation for the macro


require "http/client"

require "./mappings"

module Discordcr
  module REST
    def request(endpoint_key : Symbol, method : String, url : String | URI, headers : HTTP::Headers | Nil, body : String | Nil)
      headers["Authorization"] = @token
      HTTP::Client.exec(method: method, url: url, headers: headers, body: body, tls: true)
    end

    API_BASE = "https://discordapp.com/api/v6"

    GET = "get"

    def gateway
      response = request(
        :gateway,
        GET,
        API_BASE + "/gateway",
        HTTP::Headers.new,
        nil
      )

      puts response.body

      GatewayResponse.from_json(response.body_io)
    end
  end
end

require "http/client"
require "openssl/ssl/context"

require "./mappings"

module Discordcr
  module REST
    SSL_CONTEXT = OpenSSL::SSL::Context::Client.new

    def request(endpoint_key : Symbol, method : String, url : String | URI, headers : HTTP::Headers | Nil, body : String | Nil)
      HTTP::Client.exec(method: method, url: url, headers: headers, body: body, tls: SSL_CONTEXT)
    end

    API_BASE = "https://discordapp.com/api/v6"

    GET = "get"

    def gateway
      response = request(
        :gateway,
        GET,
        API_BASE + "/gateway",
        HTTP::Headers.new,
        nil
      )

      puts response.body

      GatewayResponse.from_json(response.body_io)
    end
  end
end

Use a proper SSL context instead of `true`


require "mysql"
require "pool/connection"
require "http"

macro conn
  env.mysql.connection
end

macro release
  env.mysql.release
end

def mysql_connect(options, capacity = 25, timeout = 0.1)
  Kemal.config.add_handler Kemal::MySQL.new(options, capacity, timeout)
end

class HTTP::Server::Context
  @mysql : ConnectionPool(MySQL::Connection) | Nil
  property! mysql
end

class Kemal::MySQL < HTTP::Handler
  @mysql : ConnectionPool(MySQL::Connection)
  getter mysql

  def initialize(options={} of String => String, capacity = 25, timeout = 0.1)
    @mysql = ConnectionPool.new(capacity: capacity, timeout: timeout) do
      ::MySQL.connect(options["host"], options["user"], options["password"], options["db"], 3306_u16, nil)
    end
  end

  def call(context)
    context.mysql = @mysql
    call_next(context)
  end
end

require "mysql"
require "pool/connection"
require "http"

macro conn
  env.mysql.connection
end

macro release
  env.mysql.release
end

def mysql_connect(options, capacity = 25, timeout = 0.1)
  Kemal.config.add_handler Kemal::MySQL.new(options, capacity, timeout)
end

class HTTP::Server::Context
  @mysql : ConnectionPool(MySQL::Connection) | Nil
  property! mysql
end

class Kemal::MySQL < HTTP::Handler
  @mysql : ConnectionPool(MySQL::Connection)
  getter mysql

  def initialize(options={} of String => String, capacity = 25, timeout = 0.1)
    @mysql = ConnectionPool(MySQL::Connection).new(capacity: capacity, timeout: timeout) do
      ::MySQL.connect(options["host"], options["user"], options["password"], options["db"], 3306_u16, nil)
    end
  end

  def call(context)
    context.mysql = @mysql
    call_next(context)
  end
end

Fix ConnectionPool(T) error on assigment


require "./rest"

module Discordcr
  class Client
    include REST

    def initialize(@token : String, @client_id : UInt64)
    end

    def run
      url = gateway.url
    end
  end
end

require "http/websocket"

require "./rest"

module Discordcr
  class Client
    include REST

    def initialize(@token : String, @client_id : UInt64)
    end

    def run
      url = gateway.url
      @websocket = HTTP::WebSocket.new(URI.parse(url))
    end
  end
end

Create a WebSocket from the URL

Create a WebSocket from the URL


system "rm -rf test_tables"
system "mkdir test_tables"

module Storage
  module TableManager
    @@tables = {} of String => Table

    def self.find(name)
      if table = @@tables[name]?
        table
      else
        @@tables[name] = Table.create("test_tables/" + name)
      end
    end
  end
end

at_exit do
  system "rm -rf test_tables"
end

require "file_utils"

FileUtils.rm_rf "test_tables"
FileUtils.mkdir_p "test_tables"

module Storage
  module TableManager
    @@tables = {} of String => Table

    def self.find(name)
      if table = @@tables[name]?
        table
      else
        @@tables[name] = Table.create("test_tables/" + name)
      end
    end
  end
end

at_exit do
  FileUtils.rm_rf "test_tables"
end

Move from system to file_utils

Move from system to file_utils


require "./environment/**"
require "./support/file_encryptor"

module Amber::Environment
  alias EnvType = String | Symbol

  macro included
    AMBER_ENV = "AMBER_ENV"
    CURRENT_ENVIRONMENT = ENV[AMBER_ENV] ||= "development"
    class_property environment_path : String = "./config/environments/"

    @@settings : Settings?

    def self.settings
      @@settings ||= Loader.new(env.to_s, environment_path).settings
    end

    def self.logger
      settings.logger
    end

    def self.env=(env : EnvType)
      ENV[AMBER_ENV] = env.to_s
      @@settings = Loader.new(env, environment_path).settings
    end

    def self.env
      @@env ||= Env.new(CURRENT_ENVIRONMENT)
    end
  end
end

require "./environment/**"
require "./support/file_encryptor"

module Amber::Environment
  alias EnvType = String | Symbol

  macro included
    AMBER_ENV = "AMBER_ENV"
    class_property environment_path : String = "./config/environments/"

    @@settings : Settings?

    def self.settings
      @@settings ||= Loader.new(env.to_s, environment_path).settings
    end

    def self.logger
      settings.logger
    end

    def self.env=(env : EnvType)
      ENV[AMBER_ENV] = env.to_s
      @@settings = Loader.new(env, environment_path).settings
    end

    def self.env
      @@env ||= Env.new(current_environment)
    end

    def self.current_environment
      ENV[AMBER_ENV]? || "development"
    end
  end
end

Move Current Environment to a method

[Environment] Move Current Environment to a method

Currently environment is a constant and when no environment is
set it defaults to development. After the environment is set and the
constant is loaded the environment would not change.

- Makes current_environment a method instead of a constant
- Does not default the environment value to development

This change makes it possible to get the environment or default to the
development environment.


# #######################################################################
#
# Author: Brian Hood
# Name: Crystal bindings for MonetDB
# Codename: Dagobert I
# Description:
#   Tools library
#
#
# #######################################################################

require "progress"

struct Number
  def times_with_progress(&block)
    bar = ProgressBar.new
    bar.total = self
    bar.incomplete = "."
    bar.complete = "o"
    self.times do |i|
      yield i
      bar.inc
    end
  end

  {% for method in %w(upto downto) %}
    def {{ method.id }}_with_progress(hdl)
      bar = ProgressBar.new
      bar.total = self
      bar.incomplete = "."
      bar.complete = "o"
      self.times do |i|
        yield i
        bar.inc
      end
    end
  {% end %}
end

class Timers
  property? start : Time
  property? finish : Time

  def initialize
    @start = Time.now
    @finish = Time.now
  end

  def start
    @start = Time.now
  end

  def stop
    @finish = Time.now
  end

  def stats
    duration = @finish - @start
    return "Start: #{@start} Finish: #{@finish} Duration: #{duration.to_s}"
  end
end

# #######################################################################
#
# Author: Brian Hood
# Name: Crystal bindings for MonetDB
# Codename: Dagobert I
# Description:
#   Tools library
#
#
# #######################################################################

require "progress"

struct Number
  def times_with_progress(&block)
    bar = ProgressBar.new
    bar.total = self
    bar.incomplete = "."
    bar.complete = "o"
    self.times do |i|
      yield i
      bar.inc
    end
  end

  {% for method in %w(upto downto) %}
    def {{ method.id }}_with_progress(num)
      bar = ProgressBar.new
      bar.total = self
      bar.incomplete = "."
      bar.complete = "o"
      self.{{ method.id }}(num) do |i|
        yield i
        bar.inc
      end
    end
  {% end %}
end

class Timers
  property? start : Time
  property? finish : Time

  def initialize
    @start = Time.now
    @finish = Time.now
  end

  def start
    @start = Time.now
  end

  def stop
    @finish = Time.now
  end

  def stats
    duration = @finish - @start
    return "Start: #{@start} Finish: #{@finish} Duration: #{duration.to_s}"
  end
end

Fix for upto / downto

Fix for upto / downto


module Mocks
  module BaseMock
    macro mock(method)
      {% method_name = method.name.stringify %}
      {% method_name = "self.#{method_name.id}" if method.receiver.stringify == "self" %}
      {% method_name = method_name.id %}

      def {{method_name}}({{method.args.argify}})
        %method = ::Mocks::Registry.for(@@__mocks_name).fetch_method({{method_name.stringify}})
        {% if method.args.empty? %}
          %result = %method.call(::Mocks::Registry::ObjectId.build(self))
        {% else %}
          %result = %method.call(::Mocks::Registry::ObjectId.build(self), {{method.args}})
        {% end %}

        if %result.call_original
          previous_def
        else
          if %result.value.is_a?(typeof(previous_def))
            %result.value as typeof(previous_def)
          else
            raise ::Mocks::UnexpectedMethodCall.new(
              {% if method.args.empty? %}
                "#{self.inspect} received unexpected method call {{method_name}}[]"
              {% else %}
                "#{self.inspect} received unexpected method call {{method_name}}#{[{{method.args.argify}}]}"
              {% end %}
            )
          end
        end
      end
    end
  end
end

module Mocks
  module BaseMock
    macro mock(method)
      {% method_name = method.name.stringify %}
      {% method_name = "self.#{method_name.id}" if method.receiver.stringify == "self" %}
      {% method_name = method_name.id %}

      def {{method_name}}({{method.args.argify}})
        %mock_name = @@__mocks_name
        unless %mock_name
          raise "Assertion failed (mocks.cr): @@__mocks_name can not be nil"
        end

        %method = ::Mocks::Registry.for(%mock_name).fetch_method({{method_name.stringify}})
        {% if method.args.empty? %}
          %result = %method.call(::Mocks::Registry::ObjectId.build(self))
        {% else %}
          %result = %method.call(::Mocks::Registry::ObjectId.build(self), {{method.args}})
        {% end %}

        if %result.call_original
          previous_def
        else
          if %result.value.is_a?(typeof(previous_def))
            %result.value as typeof(previous_def)
          else
            raise ::Mocks::UnexpectedMethodCall.new(
              {% if method.args.empty? %}
                "#{self.inspect} received unexpected method call {{method_name}}[]"
              {% else %}
                "#{self.inspect} received unexpected method call {{method_name}}#{[{{method.args.argify}}]}"
              {% end %}
            )
          end
        end
      end
    end
  end
end

Add guard statement for @@__mocks_name being nil


require "./mappings/*"

module Discord
  class Cache
    def initialize(@client : Client)
      @users = Hash(UInt64, User).new
      @channels = Hash(UInt64, Channel).new
    end

    def resolve_user(id : UInt64) : User
      @users.fetch(id) { @users[id] = @client.get_user(id) }
    end

    def resolve_channel(id : UInt64) : Channel
      @channels.fetch(id) { @channels[id] = @client.get_channel(id) }
    end

    def cache(user : User)
      @users[user.id] = user
    end

    def cache(channel : Channel)
      @channels[channel.id] = channel
    end
  end
end

require "./mappings/*"

module Discord
  class Cache
    def initialize(@client : Client)
      @users = Hash(UInt64, User).new
      @channels = Hash(UInt64, Channel).new
      @guilds = Hash(UInt64, Guild).new
    end

    def resolve_user(id : UInt64) : User
      @users.fetch(id) { @users[id] = @client.get_user(id) }
    end

    def resolve_channel(id : UInt64) : Channel
      @channels.fetch(id) { @channels[id] = @client.get_channel(id) }
    end

    def resolve_guild(id : UInt64) : Guild
      @guilds.fetch(id) { @guilds[id] = @client.get_guild(id) }
    end

    def cache(user : User)
      @users[user.id] = user
    end

    def cache(channel : Channel)
      @channels[channel.id] = channel
    end

    def cache(guild : Guild)
      @guilds[guild.id] = guild
    end
  end
end

Add support for guild caching

Add support for guild caching


require "colorize"
require "./lucky_cli"
require "./generators/*"
require "./dev"
require "./ensure_process_runner_installed"

include LuckyCli::TextHelpers

args = ARGV.join(" ")

if ARGV.first? == "dev"
  LuckyCli::Dev.new.call
elsif ARGV.first? == "ensure_process_runner_installed"
  LuckyCli::EnsureProcessRunnerInstalled.new.call
elsif File.exists?("./tasks.cr")
  Process.run "crystal run ./tasks.cr --no-debug -- #{args}",
    shell: true,
    output: STDOUT,
    error: STDERR
elsif ARGV.first? == "init"
  LuckyCli::InitQuestions.run
elsif ["-v", "--version"].includes?(ARGV.first?)
  puts LuckyCli::VERSION
else
  puts <<-MISSING_TASKS_FILE

  #{"Missing tasks.cr file".colorize(:red)}

  Try this...

    #{red_arrow} Change to the directory with the tasks.cr file,
      usually your project root
    #{red_arrow} If this is a new project, run #{"lucky init".colorize(:green)} to
      create a default tasks.cr file

  MISSING_TASKS_FILE
end

require "colorize"
require "./lucky_cli"
require "./generators/*"
require "./dev"
require "./ensure_process_runner_installed"

include LuckyCli::TextHelpers

args = ARGV.join(" ")

if ARGV.first? == "dev"
  LuckyCli::Dev.new.call
elsif ARGV.first? == "ensure_process_runner_installed"
  LuckyCli::EnsureProcessRunnerInstalled.new.call
elsif File.exists?("./tasks.cr")
  exit Process.run(
    "crystal run ./tasks.cr -- #{args}",
    shell: true,
    output: STDOUT,
    error: STDERR
  ).exit_status
elsif ARGV.first? == "init"
  LuckyCli::InitQuestions.run
elsif ["-v", "--version"].includes?(ARGV.first?)
  puts LuckyCli::VERSION
else
  puts <<-MISSING_TASKS_FILE

  #{"Missing tasks.cr file".colorize(:red)}

  Try this...

    #{red_arrow} Change to the directory with the tasks.cr file,
      usually your project root
    #{red_arrow} If this is a new project, run #{"lucky init".colorize(:green)} to
      create a default tasks.cr file

  MISSING_TASKS_FILE
end

Exit with the exit status of the task that ran

Closes #156


require "http/web_socket"

require "./rest"

module Discordcr
  class Client
    include REST

    def initialize(@token : String, @client_id : UInt64)
    end

    def run
      url = gateway.url
      url += "?v=6&encoding=json"
      @websocket = websocket = HTTP::WebSocket.new(URI.parse(url))
      websocket.on_message(&->on_message(String))
      websocket.on_close(&->on_close(String))
      websocket.run
    end

    private def on_close(message : String)
      # TODO: make more sophisticated
      puts "Closed with: " + message
    end

    private def on_message(message : String)
    end
  end
end

require "http/web_socket"

require "./rest"

module Discordcr
  class Client
    include REST

    def initialize(@token : String, @client_id : UInt64)
    end

    def run
      url = URI.parse(gateway.url)
      @websocket = websocket = HTTP::WebSocket.new(
        host: url.host.not_nil!,
        path: "#{url.path}/?encoding=json&v=6",
        port: 443,
        tls: true
      )

      websocket.on_message(&->on_message(String))
      websocket.on_close(&->on_close(String))
      websocket.run
    end

    private def on_close(message : String)
      # TODO: make more sophisticated
      puts "Closed with: " + message
    end

    private def on_message(message : String)
      puts message
    end
  end
end

Use the manual initializer for WebSocket

Otherwise the query string would get omitted and we would land on gateway v3.
(Apparently that still works...)


ENV["LUCKY_ENV"] = "test"
require "spec"
require "../src/app"
require "./support/**"

Spec.after_each do
  LuckyRecord::Repo.truncate
end

Clear db after each test

Clear db after each test

Closes #103


require "./converters"

module Discord
  # :nodoc:
  module VWS
    struct IdentifyPacket
      def initialize(server_id, user_id, session_id, token)
        @op = Discord::VoiceClient::OP_IDENTIFY
        @d = IdentifyPayload.new(server_id, user_id, session_id, token)
      end

      JSON.mapping(
        op: Int32,
        d: IdentifyPayload
      )
    end

    struct IdentifyPayload
      def initialize(@server_id, @user_id, @session_id, @token)
      end

      JSON.mapping(
        server_id: UInt64,
        user_id: UInt64,
        session_id: String,
        token: String
      )
    end

    struct SelectProtocolPacket
      def initialize(protocol, data)
        @op = Discord::VoiceClient::OP_SELECT_PROTOCOL
        @d = SelectProtocolPayload.new(protocol, data)
      end

      JSON.mapping(
        op: Int32,
        d: SelectProtocolPayload
      )
    end

    struct SelectProtocolPayload
      def initialize(@protocol, @data)
      end

      JSON.mapping(
        protocol: String,
        data: ProtocolData
      )
    end

    struct ProtocolData
      def initialize(@address, @port, @mode)
      end

      JSON.mapping(
        address: String,
        port: UInt16,
        mode: String
      )
    end

    struct ReadyPayload
      JSON.mapping(
        ssrc: Int32,
        port: Int32,
        modes: Array(String),
        heartbeat_interval: Int32
      )
    end

    struct SessionDescriptionPayload
      JSON.mapping(
        secret_key: Array(UInt8)
      )
    end

    struct SpeakingPacket
      def initialize(speaking, delay)
        @op = Discord::VoiceClient::OP_SPEAKING
        @d = SpeakingPayload.new(speaking, delay)
      end

      JSON.mapping(
        op: Int32,
        d: SpeakingPayload
      )
    end

    struct SpeakingPayload
      def initialize(@speaking, @delay)
      end

      JSON.mapping(
        speaking: Bool,
        delay: Int32
      )
    end

    struct HelloPayload
      JSON.mapping(
        heartbeat_interval: Int32
      )
    end
  end
end

Add mappings for the VWS payloads

Add mappings for the VWS payloads

The voice web socket (VWS) uses the same packet format as the gateway,
but has different opcodes and payloads. These need to be mapped just
like they are for the gateway.


# ac-library.cr by hakatashi https://github.com/google/ac-library.cr
#
# Copyright 2021 Google LLC
#
# Licensed under the Apache License, Version 2.0 (the "License");
# you may not use this file except in compliance with the License.
# You may obtain a copy of the License at
#
#      https://www.apache.org/licenses/LICENSE-2.0
#
# Unless required by applicable law or agreed to in writing, software
# distributed under the License is distributed on an "AS IS" BASIS,
# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
# See the License for the specific language governing permissions and
# limitations under the License.

# verify-helper: PROBLEM https://judge.yosupo.jp/problem/enumerate_primes

require "./Prime.cr"

n, a, b = read_line.split.map(&.to_i64)
ans = Array(Int64).new(1_000_000_i64)
pi = 0_i64
AtCoder::Prime.each_with_index do |prime, i|
  break if prime > n
  pi += 1
  if i >= b && (i - b) % a == 0
    ans << prime
  end
end

puts "#{pi} #{ans.size}"
puts ans.join(' ')

Add verifier for prime enumeration

Add verifier for prime enumeration


require "benchmark"
require "matrix"

def faster_matrix_multiply(a, b)
  raise "Dimensions don't match" unless a.columns.count == b.rows.count
  matrix = Matrix(typeof(a[0] * b[0])).new(a.rows.count, b.columns.count)
  pos = -1
  b_columns = b.columns
  initial_sum = typeof(a[0] * b[0]).cast(0.0)
  a.rows.each do |a_row|
    b_columns.each do |b_column|
      matrix[pos += 1] = typeof(a[0] * b[0]).cast((0...a_row.length).inject(0) do |memo, i|
                           memo + a_row[i] * b_column[i]
      end)
    end
  end
  matrix
end

def main()
  r = Random.new
  a = Matrix.new(1000, 1000, 0)
  b = Matrix.new(1000, 1000, 0)

  (0..999).each do |i|
    (0..999).each do |j|
      a[i,j] = r.next_int
      b[i,j] = r.next_int
    end
  end

  c = a * b
  d = faster_matrix_multiply(a, b)
  raise "Not equal!" unless c == d

  puts "New:"
  puts Benchmark.measure { faster_matrix_multiply(a, b) }
  puts "Current:"
  puts Benchmark.measure { a * b }
end

main()

Add test code for faster matrix multiplication.


require "./types.cr"

def memcpy(dst : Pointer(_), src : Pointer(_), n : USize) : Pointer
    dst_sig = dst
    dst = dst.to_byte_ptr
    src = src.to_byte_ptr
    i = 0
    while i < n
        dst[i] = src[i]
        i += 1
    end
    dst_sig
end

def memmove(dst : Pointer(_), src : Pointer(_), n : USize) : Pointer
    dst_sig = dst
    dst = dst.to_byte_ptr
    src = src.to_byte_ptr
    if src < dst
        i = n
        while i != 0
            i -= 1
            dst[i] = src[i]
        end
    else
        i = 0
        while i < n
            dst[i] = src[i]
            i += 1
        end
    end
    dst_sig
end

def memset(dst : Pointer(_), c : UInt8, n : USize) : Pointer
    ptr = dst.to_byte_ptr
    i = 0
    while i < n
        ptr[i] = c
    end
    dst
end

def memcmp(ptr_a : Pointer(_), ptr_b : Pointer(_), n : USize)
    ptr_a = ptr_a.to_byte_ptr
    ptr_b = ptr_b.to_byte_ptr
    i = 0
    while i < n
        a = ptr_a[i]
        b = ptr_b[i]
        i += 1
        return a.to_i32 - b.to_i32 if a != b
    end
    0
end

Add memcpy, memmove, memset, memcmp

Add memcpy, memmove, memset, memcmp


module Crustache
  class Engine
    def initialize(@basedir : String, @cache = false)
      self.initialize ViewLoader.new @basedir, @cache
    end

    def initialize(@fs : FileSystem); end

    # It renders a template loaded from `filename` with `model`
    # and it returns rendered string.
    # If `filename` is not found, it returns `nil`, but it dosen't raise an error.
    def render(filename : String, model)
      @fs.load(filename).try{|tmpl| self.render tmpl, model}
    end

    def render(filename : String, model, output : IO)
      @fs.load(filename).try{|tmpl| self.render tmpl, model, output}
    end

    # It is a strict version `Engine#render`.
    # If `filename` is not found, it raise an error.
    def render!(filename : String, model)
      @fs.load!(filename).try{|tmpl| self.render tmpl, model}
    end

    def render!(filename : String, model, output : IO)
      @fs.load!(filename).try{|tmpl| self.render tmpl, model, output}
    end

    def render(tmpl : Template, model)
      Crustache.render tmpl, model, @fs
    end

    def render(tmpl : Template, model, output : IO)
      Crustache.render tmpl, model, @fs, io
    end
  end
end

Add Crustache::Engine class for typical using


require "./spec_helper"

describe Crustache::Engine do
  describe "#initialize" do
    it "should return a new instance" do
      Crustache::Engine.new(Crustache::HashFileSystem.new).should be_truthy
      Crustache::Engine.new("", true).should be_truthy
    end
  end

  describe "#render" do
    it "should render a template" do
      engine = Crustache::Engine.new Crustache::HashFileSystem.new
      engine.render(Crustache.parse("Test {{.}}"), "Test").should eq("Test Test")
    end

    it "should render a template" do
      fs = Crustache::HashFileSystem.new
      fs.register "test", Crustache.parse "Test {{.}}"
      engine = Crustache::Engine.new fs
      engine.render("test", "Test").should eq("Test Test")
    end

    it "should render a template with output IO object" do
      fs = Crustache::HashFileSystem.new
      fs.register "test", Crustache.parse "Test {{.}}"
      engine = Crustache::Engine.new fs
      output = StringIO.new
      engine.render("test", "Test", output)
      output.to_s.should eq("Test Test")
    end
  end

  describe "#render!" do
    it "should render a template" do
      fs = Crustache::HashFileSystem.new
      fs.register "test", Crustache.parse "Test {{.}}"
      engine = Crustache::Engine.new fs
      engine.render!("test", "Test").should eq("Test Test")
    end

    it "should raise an error" do
      fs = Crustache::HashFileSystem.new
      engine = Crustache::Engine.new fs
      expect_raises do
        engine.render!("test", "Test")
      end
    end
  end
end

Add a spec for Engine class

Add a spec for Engine class


gcfile = File.new("chry_multiplied.fa")
gcfile.buffer_size = ARGV[0].to_i

at = 0
gc = 0

while true
  # Peek the IO's buffer
  peek = gcfile.peek

  # If there's nothing else, we reached the end
  break if peek.empty?

  # If the line starts with '>' it's a comment
  if peek[0] === '>'
    while true
      # See where the line ends
      newline_index = peek.index('\n'.ord)

      # If we find an end, skip until past the newline and continue analyzing
      if newline_index
        gcfile.skip(newline_index + 1)
        break
      end

      # Otherwise we must continue looking for that newline,
      # so we skip the entire peek buffer and read more
      gcfile.skip(peek.size)
      peek = gcfile.peek

      # Maybe we reached the end?
      break if peek.empty?
    end

    # Here we found the newline, so we analyze the next line
    next
  end

  # This is not a comment line so we read until the next line
  while true
    # See where the line ends
    newline_index = peek.index('\n'.ord)

    # How many bytes we need to analyze: either until the newline or the entire buffer
    analyze_size = newline_index || peek.size

    # Analyze the bytes
    peek[0, analyze_size].each do |byte|
      case byte
      when 'A', 'T'
        at += 1
      when 'G', 'C'
        gc += 1
      end
    end

    # If we found a newline, we are done
    if newline_index
      gcfile.skip(newline_index + 1)
      break
    end

    # Otherwise we are still in a non-comment line
    gcfile.skip(peek.size)
    peek = gcfile.peek

    # Maybe we reached the end?
    break if peek.empty?
  end
end

gcfile.close

gcfrac = gc / (gc + at)
puts "GC fraction: #{gcfrac}"

Add Crystal version that avoids allocating strings


class HTTP::StaticFileHandler
  private def mime_type(path)
    case File.extname(path)
    when ".txt"          then "text/plain"
    when ".htm", ".html" then "text/html"
    when ".css"          then "text/css"
    when ".js"           then "application/javascript"
    when ".svg"          then "image/svg+xml"
    when ".svgz"         then "image/svg+xml"
    else                      "application/octet-stream"
    end
  end
end

Add StaticFileHandler with SVG mime type


require "../spec_helper"

module MyModule
  def self.exists?(name)
    false
  end
end

create_module_mock MyModule do
  mock self.exists?(name)
end

create_mock File do
  mock self.exists?(name)
end

describe "create module mock macro" do
  it "does not fail with Nil errors" do
    allow(MyModule).to receive(self.exists?("hello")).and_return(true)
    MyModule.exists?("world").should eq(false)
    MyModule.exists?("hello").should eq(true)
  end

  it "does not fail with Nil errors for stdlib class" do
    allow(File).to receive(self.exists?("hello")).and_return(true)
    File.exists?("world").should eq(false)
    File.exists?("hello").should eq(true)
  end
end

Add some more specs for create_module_mock


describe Caoutchouc::Elasticsearch::Health do
  describe ".from_json" do
    it "parses json correctly" do
      response = <<-EOF
        {
          "cluster_name": "elasticsearch",
          "status": "green",
          "timed_out": false,
          "number_of_nodes": 4,
          "number_of_data_nodes": 2,
          "active_primary_shards": 384,
          "active_shards": 768,
          "relocating_shards": 0,
          "initializing_shards": 0,
          "unassigned_shards": 0
        }
      EOF
      res = Caoutchouc::Elasticsearch::Health.from_json(response)
      res.status.should eq("green")
      res.active_shards.should eq(768)
    end
  end
end

Add some specs for Caoutchouc::Elasticsearch::Health