text:
using Test
using HTTP

module ParserTest

const strict = false

using ..Test

import ..HTTP

using HTTP.Messages
using HTTP.Parsers
using HTTP.Strings: tocameldash

import Base.==

const Headers = Vector{Pair{String,String}}

==(a::Request,b::Request) = (a.method         == b.method)    &&
                            (a.version        == b.version)   &&
                            (a.headers        == b.headers)   &&
                            (a.body           == b.body)

macro errmsg(expr)
    esc(quote
        try
            $expr
        catch e
            sprint(show, e)
        end
    end)
end

mutable struct Message
  name::String
  raw::String
  method::String
  status_code::Int
  response_status::String
  request_path::String
  request_url::String
  fragment::String
  query_string::String
  body::String
  body_size::Int
  host::String
  userinfo::String
  port::String
  num_headers::Int
  headers::Headers
  should_keep_alive::Bool
  upgrade::String
  http_major::Int
  http_minor::Int

  Message(name::String) = new(name, "", "GET", 200, "", "", "", "", "", "", 0, "", "", "", 0, Headers(), true, "", 1, 1)
end

function Message(; name::String="", kwargs...)
  m = Message(name)
  for (k, v) in kwargs
      try
          setfield!(m, k, v)
      catch e
          error("error setting k=$k, v=$v")
      end
  end
  return m
end

#= * R E Q U E S T S * =#
const requests = Message[
Message(name= "curl get"
,raw= "GET /test HTTP/1.1\r\n" *
       "User-Agent: curl/7.18.0 (i486-pc-linux-gnu) libcurl/7.18.0 OpenSSL/0.9.8g zlib/1.2.3.3 libidn/1.1\r\n" *
       "Host:0.0.0.0=5000\r\n" * # missing space after colon
       "Accept: */*\r\n" *
       "\r\n"
,should_keep_alive= true
,http_major= 1
,http_minor= 1
,method= "GET"
,query_string= ""
,fragment= ""
,request_path= "/test"
,request_url= "/test"
,num_headers= 3
,headers=[
    "User-Agent"=> "curl/7.18.0 (i486-pc-linux-gnu) libcurl/7.18.0 OpenSSL/0.9.8g zlib/1.2.3.3 libidn/1.1"
  , "Host"=> "0.0.0.0=5000"
  , "Accept"=> "*/*"
  ]
,body= ""
), Message(name= "firefox get"
,raw= "GET /favicon.ico HTTP/1.1\r\n" *
       "Host: 0.0.0.0=5000\r\n" *
       "User-Agent: Mozilla/5.0 (X11; U; Linux i686; en-US; rv:1.9) Gecko/2008061015 Firefox/3.0\r\n" *
       "Accept: text/html,application/xhtml+xml,application/xml;q=0.9,*/*;q=0.8\r\n" *
       "Accept-Language: en-us,en;q=0.5\r\n" *
       "Accept-Encoding: gzip,deflate\r\n" *
       "Accept-Charset: ISO-8859-1,utf-8;q=0.7,*;q=0.7\r\n" *
       "Keep-Alive: 300\r\n" *
       "Connection: keep-alive\r\n" *
       "\r\n"
,should_keep_alive= true
,http_major= 1
,http_minor= 1
,method= "GET"
,query_string= ""
,fragment= ""
,request_path= "/favicon.ico"
,request_url= "/favicon.ico"
,num_headers= 8
,headers=[
    "Host"=> "0.0.0.0=5000"
  , "User-Agent"=> "Mozilla/5.0 (X11; U; Linux i686; en-US; rv:1.9) Gecko/2008061015 Firefox/3.0"
  , "Accept"=> "text/html,application/xhtml+xml,application/xml;q=0.9,*/*;q=0.8"
  , "Accept-Language"=> "en-us,en;q=0.5"
  , "Accept-Encoding"=> "gzip,deflate"
  , "Accept-Charset"=> "ISO-8859-1,utf-8;q=0.7,*;q=0.7"
  , "Keep-Alive"=> "300"
  , "Connection"=> "keep-alive"
]
,body= ""
), Message(name= "abcdefgh"
,raw= "GET /abcdefgh HTTP/1.1\r\n" *
       "aaaaaaaaaaaaa:++++++++++\r\n" *
       "\r\n"
,should_keep_alive= true
,http_major= 1
,http_minor= 1
,method= "GET"
,query_string= ""
,fragment= ""
,request_path= "/abcdefgh"
,request_url= "/abcdefgh"
,num_headers= 1
,headers=[
    "Aaaaaaaaaaaaa"=>  "++++++++++"
]
,body= ""
), Message(name= "fragment in url"
,raw= "GET /forums/1/topics/2375?page=1#posts-17408 HTTP/1.1\r\n" *
       "\r\n"
,should_keep_alive= true
,http_major= 1
,http_minor= 1
,method= "GET"
,query_string= "page=1"
,fragment= "posts-17408"
,request_path= "/forums/1/topics/2375"
#= XXX request url does include fragment? =#
,request_url= "/forums/1/topics/2375?page=1#posts-17408"
,num_headers= 0
,body= ""
), Message(name= "get no headers no body"
,raw= "GET /get_no_headers_no_body/world HTTP/1.1\r\n" *
       "\r\n"
,should_keep_alive= true
,http_major= 1
,http_minor= 1
,method= "GET"
,query_string= ""
,fragment= ""
,request_path= "/get_no_headers_no_body/world"
,request_url= "/get_no_headers_no_body/world"
,num_headers= 0
,body= ""
), Message(name= "get one header no body"
,raw= "GET /get_one_header_no_body HTTP/1.1\r\n" *
       "Accept: */*\r\n" *
       "\r\n"
,should_keep_alive= true
,http_major= 1
,http_minor= 1
,method= "GET"
,query_string= ""
,fragment= ""
,request_path= "/get_one_header_no_body"
,request_url= "/get_one_header_no_body"
,num_headers= 1
,headers=[
     "Accept" => "*/*"
]
,body= ""
), Message(name= "get funky content length body hello"
,raw= "GET /get_funky_content_length_body_hello HTTP/1.0\r\n" *
       "conTENT-Length: 5\r\n" *
       "\r\n" *
       "HELLO"
,should_keep_alive= false
,http_major= 1
,http_minor= 0
,method= "GET"
,query_string= ""
,fragment= ""
,request_path= "/get_funky_content_length_body_hello"
,request_url= "/get_funky_content_length_body_hello"
,num_headers= 1
,headers=[
     "Content-Length" => "5"
]
,body= "HELLO"
), Message(name= "post identity body world"
,raw= "POST /post_identity_body_world?q=search#hey HTTP/1.1\r\n" *
       "Accept: */*\r\n" *
       "Transfer-Encoding: identity\r\n" *
       "Content-Length: 5\r\n" *
       "\r\n" *
       "World"
,should_keep_alive= true
,http_major= 1
,http_minor= 1
,method= "POST"
,query_string= "q=search"
,fragment= "hey"
,request_path= "/post_identity_body_world"
,request_url= "/post_identity_body_world?q=search#hey"
,num_headers= 3
,headers=[
    "Accept"=> "*/*"
  , "Transfer-Encoding"=> "identity"
  , "Content-Length"=> "5"
]
,body= "World"
), Message(name= "post - chunked body: all your base are belong to us"
,raw= "POST /post_chunked_all_your_base HTTP/1.1\r\n" *
       "Transfer-Encoding: chunked\r\n" *
       "\r\n" *
       "1e\r\nall your base are belong to us\r\n" *
       "0\r\n" *
       "\r\n"
,should_keep_alive= true
,http_major= 1
,http_minor= 1
,method= "POST"
,query_string= ""
,fragment= ""
,request_path= "/post_chunked_all_your_base"
,request_url= "/post_chunked_all_your_base"
,num_headers= 1
,headers=[
    "Transfer-Encoding" => "chunked"
]
,body= "all your base are belong to us"
), Message(name= "two chunks ; triple zero ending"
,raw= "POST /two_chunks_mult_zero_end HTTP/1.1\r\n" *
       "Transfer-Encoding: chunked\r\n" *
       "\r\n" *
       "5\r\nhello\r\n" *
       "6\r\n world\r\n" *
       "000\r\n" *
       "\r\n"
,should_keep_alive= true
,http_major= 1
,http_minor= 1
,method= "POST"
,query_string= ""
,fragment= ""
,request_path= "/two_chunks_mult_zero_end"
,request_url= "/two_chunks_mult_zero_end"
,num_headers= 1
,headers=[
    "Transfer-Encoding"=> "chunked"
]
,body= "hello world"
), Message(name= "chunked with trailing headers. blech."
,raw= "POST /chunked_w_trailing_headers HTTP/1.1\r\n" *
       "Transfer-Encoding: chunked\r\n" *
       "\r\n" *
       "5\r\nhello\r\n" *
       "6\r\n world\r\n" *
       "0\r\n" *
       "Vary: *\r\n" *
       "Content-Type: text/plain\r\n" *
       "\r\n"
,should_keep_alive= true
,http_major= 1
,http_minor= 1
,method= "POST"
,query_string= ""
,fragment= ""
,request_path= "/chunked_w_trailing_headers"
,request_url= "/chunked_w_trailing_headers"
,num_headers= 3
,headers=[
    "Transfer-Encoding"=>  "chunked"
  , "Vary"=> "*"
  , "Content-Type"=> "text/plain"
]
,body= "hello world"
), Message(name= "with excessss after the length"
,raw= "POST /chunked_w_excessss_after_length HTTP/1.1\r\n" *
       "Transfer-Encoding: chunked\r\n" *
       "\r\n" *
       "5; ihatew3;whattheheck=aretheseparametersfor\r\nhello\r\n" *
       "6; blahblah; blah\r\n world\r\n" *
       "0\r\n" *
       "\r\n"
,should_keep_alive= true
,http_major= 1
,http_minor= 1
,method= "POST"
,query_string= ""
,fragment= ""
,request_path= "/chunked_w_excessss_after_length"
,request_url= "/chunked_w_excessss_after_length"
,num_headers= 1
,headers=[
    "Transfer-Encoding"=> "chunked"
]
,body= "hello world"
), Message(name= "with quotes"
,raw= "GET /with_\"stupid\"_quotes?foo=\"bar\" HTTP/1.1\r\n\r\n"
,should_keep_alive= true
,http_major= 1
,http_minor= 1
,method= "GET"
,query_string= "foo=\"bar\""
,fragment= ""
,request_path= "/with_\"stupid\"_quotes"
,request_url= "/with_\"stupid\"_quotes?foo=\"bar\""
,num_headers= 0
,headers=Headers()
,body= ""
), Message(name = "apachebench get"
,raw= "GET /test HTTP/1.0\r\n" *
       "Host: 0.0.0.0:5000\r\n" *
       "User-Agent: ApacheBench/2.3\r\n" *
       "Accept: */*\r\n\r\n"
,should_keep_alive= false
,http_major= 1
,http_minor= 0
,method= "GET"
,query_string= ""
,fragment= ""
,request_path= "/test"
,request_url= "/test"
,num_headers= 3
,headers=[ "Host"=> "0.0.0.0:5000"
           , "User-Agent"=> "ApacheBench/2.3"
           , "Accept"=> "*/*"
         ]
,body= ""
), Message(name = "query url with question mark"
,raw= "GET /test.cgi?foo=bar?baz HTTP/1.1\r\n\r\n"
,should_keep_alive= true
,http_major= 1
,http_minor= 1
,method= "GET"
,query_string= "foo=bar?baz"
,fragment= ""
,request_path= "/test.cgi"
,request_url= "/test.cgi?foo=bar?baz"
,num_headers= 0
,headers=Headers()
,body= ""
), Message(name = "newline prefix get"
,raw= "\r\nGET /test HTTP/1.1\r\n\r\n"
,should_keep_alive= true
,http_major= 1
,http_minor= 1
,method= "GET"
,query_string= ""
,fragment= ""
,request_path= "/test"
,request_url= "/test"
,num_headers= 0
,headers=Headers()
,body= ""
), Message(name = "upgrade request"
,raw= "GET /demo HTTP/1.1\r\n" *
       "Host: example.com\r\n" *
       "Connection: Upgrade\r\n" *
       "Sec-WebSocket-Key2: 12998 5 Y3 1  .P00\r\n" *
       "Sec-WebSocket-Protocol: sample\r\n" *
       "Upgrade: WebSocket\r\n" *
       "Sec-WebSocket-Key1: 4 @1  46546xW%0l 1 5\r\n" *
       "Origin: http://example.com\r\n" *
       "\r\n" *
       "Hot diggity dogg"
,should_keep_alive= true
,http_major= 1
,http_minor= 1
,method= "GET"
,query_string= ""
,fragment= ""
,request_path= "/demo"
,request_url= "/demo"
,num_headers= 7
,upgrade="Hot diggity dogg"
,headers=[ "Host"=> "example.com"
           , "Connection"=> "Upgrade"
           , "Sec-Websocket-Key2"=> "12998 5 Y3 1  .P00"
           , "Sec-Websocket-Protocol"=> "sample"
           , "Upgrade"=> "WebSocket"
           , "Sec-Websocket-Key1"=> "4 @1  46546xW%0l 1 5"
           , "Origin"=> "http://example.com"
         ]
,body= ""
), Message(name = "connect request"
,raw= "CONNECT 0-home0.netscape.com:443 HTTP/1.0\r\n" *
       "User-agent: Mozilla/1.1N\r\n" *
       "Proxy-authorization: basic aGVsbG86d29ybGQ=\r\n" *
       "\r\n" *
       "some data\r\n" *
       "and yet even more data"
,should_keep_alive= false
,http_major= 1
,http_minor= 0
,method= "CONNECT"
,query_string= ""
,fragment= ""
,request_path= ""
,host="0-home0.netscape.com"
,port="443"
,request_url= "0-home0.netscape.com:443"
,num_headers= 2
,upgrade="some data\r\nand yet even more data"
,headers=[ "User-Agent"=> "Mozilla/1.1N"
           , "Proxy-Authorization"=> "basic aGVsbG86d29ybGQ="
         ]
,body= ""
), Message(name= "report request"
,raw= "REPORT /test HTTP/1.1\r\n" *
       "\r\n"
,should_keep_alive= true
,http_major= 1
,http_minor= 1
,method= "REPORT"
,query_string= ""
,fragment= ""
,request_path= "/test"
,request_url= "/test"
,num_headers= 0
,headers=Headers()
,body= ""
#=
), Message(name= "request with no http version"
,raw= "GET /\r\n" *
       "\r\n"
,should_keep_alive= false
,http_major= 0
,http_minor= 9
,method= "GET"
,query_string= ""
,fragment= ""
,request_path= "/"
,request_url= "/"
,num_headers= 0
,headers=Headers()
,body= ""
=#
), Message(name= "m-search request"
,raw= "M-SEARCH * HTTP/1.1\r\n" *
       "HOST: 239.255.255.250:1900\r\n" *
       "MAN: \"ssdp:discover\"\r\n" *
       "ST: \"ssdp:all\"\r\n" *
       "\r\n"
,should_keep_alive= true
,http_major= 1
,http_minor= 1
,method= "M-SEARCH"
,query_string= ""
,fragment= ""
,request_path= "*"
,request_url= "*"
,num_headers= 3
,headers=[ "Host"=> "239.255.255.250:1900"
           , "Man"=> "\"ssdp:discover\""
           , "St"=> "\"ssdp:all\""
         ]
,body= ""
), Message(name= "host terminated by a query string"
,raw= "GET http://hypnotoad.org?hail=all HTTP/1.1\r\n" *
       "\r\n"
,should_keep_alive= true
,http_major= 1
,http_minor= 1
,method= "GET"
,query_string= "hail=all"
,fragment= ""
,request_path= ""
,request_url= "http://hypnotoad.org?hail=all"
,host= "hypnotoad.org"
,num_headers= 0
,headers=Headers()
,body= ""
), Message(name= "host:port terminated by a query string"
,raw= "GET http://hypnotoad.org:1234?hail=all HTTP/1.1\r\n" *
       "\r\n"
,should_keep_alive= true
,http_major= 1
,http_minor= 1
,method= "GET"
,query_string= "hail=all"
,fragment= ""
,request_path= ""
,request_url= "http://hypnotoad.org:1234?hail=all"
,host= "hypnotoad.org"
,port= "1234"
,num_headers= 0
,headers=Headers()
,body= ""
), Message(name= "host:port terminated by a space"
,raw= "GET http://hypnotoad.org:1234 HTTP/1.1\r\n" *
       "\r\n"
,should_keep_alive= true
,http_major= 1
,http_minor= 1
,method= "GET"
,query_string= ""
,fragment= ""
,request_path= ""
,request_url= "http://hypnotoad.org:1234"
,host= "hypnotoad.org"
,port= "1234"
,num_headers= 0
,headers=Headers()
,body= ""
), Message(name = "PATCH request"
,raw= "PATCH /file.txt HTTP/1.1\r\n" *
       "Host: www.example.com\r\n" *
       "Content-Type: application/example\r\n" *
       "If-Match: \"e0023aa4e\"\r\n" *
       "Content-Length: 10\r\n" *
       "\r\n" *
       "cccccccccc"
,should_keep_alive= true
,http_major= 1
,http_minor= 1
,method= "PATCH"
,query_string= ""
,fragment= ""
,request_path= "/file.txt"
,request_url= "/file.txt"
,num_headers= 4
,headers=[ "Host"=> "www.example.com"
           , "Content-Type"=> "application/example"
           , "If-Match"=> "\"e0023aa4e\""
           , "Content-Length"=> "10"
         ]
,body= "cccccccccc"
), Message(name = "connect caps request"
,raw= "CONNECT HOME0.NETSCAPE.COM:443 HTTP/1.0\r\n" *
       "User-agent: Mozilla/1.1N\r\n" *
       "Proxy-authorization: basic aGVsbG86d29ybGQ=\r\n" *
       "\r\n"
,should_keep_alive= false
,http_major= 1
,http_minor= 0
,method= "CONNECT"
,query_string= ""
,fragment= ""
,request_path= ""
,request_url= "HOME0.NETSCAPE.COM:443"
,host="HOME0.NETSCAPE.COM"
,port="443"
,num_headers= 2
,upgrade=""
,headers=[ "User-Agent"=> "Mozilla/1.1N"
           , "Proxy-Authorization"=> "basic aGVsbG86d29ybGQ="
         ]
,body= ""
), Message(name= "utf-8 path request"
,raw= "GET /δ¶/δt/pope?q=1#narf HTTP/1.1\r\n" *
       "Host: github.com\r\n" *
       "\r\n"
,should_keep_alive= true
,http_major= 1
,http_minor= 1
,method= "GET"
,query_string= "q=1"
,fragment= "narf"
,request_path= "/δ¶/δt/pope"
,request_url= "/δ¶/δt/pope?q=1#narf"
,num_headers= 1
,headers=["Host" => "github.com"]
,body= ""
), Message(name = "hostname underscore"
,raw= "CONNECT home_0.netscape.com:443 HTTP/1.0\r\n" *
       "User-agent: Mozilla/1.1N\r\n" *
       "Proxy-authorization: basic aGVsbG86d29ybGQ=\r\n" *
       "\r\n"
,should_keep_alive= false
,http_major= 1
,http_minor= 0
,method= "CONNECT"
,query_string= ""
,fragment= ""
,request_path= ""
,request_url= "home_0.netscape.com:443"
,host="home_0.netscape.com"
,port="443"
,num_headers= 2
,upgrade=""
,headers=[ "User-Agent"=> "Mozilla/1.1N"
           , "Proxy-Authorization"=> "basic aGVsbG86d29ybGQ="
         ]
,body= ""
), Message(name = "eat CRLF between requests, no \"Connection: close\" header"
,raw= "POST / HTTP/1.1\r\n" *
       "Host: www.example.com\r\n" *
       "Content-Type: application/x-www-form-urlencoded\r\n" *
       "Content-Length: 4\r\n" *
       "\r\n" *
       "q=42\r\n" #= note the trailing CRLF =#
,should_keep_alive= true
,http_major= 1
,http_minor= 1
,method= "POST"
,query_string= ""
,fragment= ""
,request_path= "/"
,request_url= "/"
,num_headers= 3
,upgrade= ""
,headers=[ "Host"=> "www.example.com"
           , "Content-Type"=> "application/x-www-form-urlencoded"
           , "Content-Length"=> "4"
         ]
,body= "q=42"
), Message(name = "eat CRLF between requests even if \"Connection: close\" is set"
,raw= "POST / HTTP/1.1\r\n" *
       "Host: www.example.com\r\n" *
       "Content-Type: application/x-www-form-urlencoded\r\n" *
       "Content-Length: 4\r\n" *
       "Connection: close\r\n" *
       "\r\n" *
       "q=42\r\n" #= note the trailing CRLF =#
,should_keep_alive= false
,http_major= 1
,http_minor= 1
,method= "POST"
,query_string= ""
,fragment= ""
,request_path= "/"
,request_url= "/"
,num_headers= 4
,upgrade= ""
,headers=[ "Host"=> "www.example.com"
           , "Content-Type"=> "application/x-www-form-urlencoded"
           , "Content-Length"=> "4"
           , "Connection"=> "close"
         ]
,body= "q=42"
), Message(name = "PURGE request"
,raw= "PURGE /file.txt HTTP/1.1\r\n" *
       "Host: www.example.com\r\n" *
       "\r\n"
,should_keep_alive= true
,http_major= 1
,http_minor= 1
,method= "PURGE"
,query_string= ""
,fragment= ""
,request_path= "/file.txt"
,request_url= "/file.txt"
,num_headers= 1
,headers=[ "Host"=> "www.example.com" ]
,body= ""
), Message(name = "SEARCH request"
,raw= "SEARCH / HTTP/1.1\r\n" *
       "Host: www.example.com\r\n" *
       "\r\n"
,should_keep_alive= true
,http_major= 1
,http_minor= 1
,method= "SEARCH"
,query_string= ""
,fragment= ""
,request_path= "/"
,request_url= "/"
,num_headers= 1
,headers=[ "Host"=> "www.example.com"]
,body= ""
), Message(name= "host:port and basic_auth"
,raw= "GET http://a%12:b!&*\$@hypnotoad.org:1234/toto HTTP/1.1\r\n" *
       "\r\n"
,should_keep_alive= true
,http_major= 1
,http_minor= 1
,method= "GET"
,fragment= ""
,request_path= "/toto"
,request_url= "http://a%12:b!&*\$@hypnotoad.org:1234/toto"
,host= "hypnotoad.org"
,userinfo= "a%12:b!&*\$"
,port= "1234"
,num_headers= 0
,headers=Headers()
,body= ""
), Message(name = "upgrade post request"
,raw= "POST /demo HTTP/1.1\r\n" *
       "Host: example.com\r\n" *
       "Connection: Upgrade\r\n" *
       "Upgrade: HTTP/2.0\r\n" *
       "Content-Length: 15\r\n" *
       "\r\n" *
       "sweet post body" *
       "Hot diggity dogg"
,should_keep_alive= true
,http_major= 1
,http_minor= 1
,method= "POST"
,request_path= "/demo"
,request_url= "/demo"
,num_headers= 4
,upgrade="Hot diggity dogg"
,headers=[ "Host"=> "example.com"
           , "Connection"=> "Upgrade"
           , "Upgrade"=> "HTTP/2.0"
           , "Content-Length"=> "15"
         ]
,body= "sweet post body"
), Message(name = "connect with body request"
,raw= "CONNECT foo.bar.com:443 HTTP/1.0\r\n" *
       "User-agent: Mozilla/1.1N\r\n" *
       "Proxy-authorization: basic aGVsbG86d29ybGQ=\r\n" *
       "Content-Length: 10\r\n" *
       "\r\n" *
       "blarfcicle"
,should_keep_alive= false
,http_major= 1
,http_minor= 0
,method= "CONNECT"
,request_url= "foo.bar.com:443"
,host="foo.bar.com"
,port="443"
,num_headers= 3
,upgrade=""
,headers=[ "User-Agent"=> "Mozilla/1.1N"
           , "Proxy-Authorization"=> "basic aGVsbG86d29ybGQ="
           , "Content-Length"=> "10"
         ]
,body= "blarfcicle"
), Message(name = "link request"
,raw= "LINK /images/my_dog.jpg HTTP/1.1\r\n" *
       "Host: example.com\r\n" *
       "Link: <http://example.com/profiles/joe>; rel=\"tag\"\r\n" *
       "Link: <http://example.com/profiles/sally>; rel=\"tag\"\r\n" *
       "\r\n"
,should_keep_alive= true
,http_major= 1
,http_minor= 1
,method= "LINK"
,request_path= "/images/my_dog.jpg"
,request_url= "/images/my_dog.jpg"
,query_string= ""
,fragment= ""
,num_headers= 2
,headers=[ "Host"=> "example.com"
           , "Link"=> "<http://example.com/profiles/joe>; rel=\"tag\", <http://example.com/profiles/sally>; rel=\"tag\""
         ]
,body= ""
), Message(name = "link request"
,raw= "UNLINK /images/my_dog.jpg HTTP/1.1\r\n" *
       "Host: example.com\r\n" *
       "Link: <http://example.com/profiles/sally>; rel=\"tag\"\r\n" *
       "\r\n"
,should_keep_alive= true
,http_major= 1
,http_minor= 1
,method= "UNLINK"
,request_path= "/images/my_dog.jpg"
,request_url= "/images/my_dog.jpg"
,query_string= ""
,fragment= ""
,num_headers= 2
,headers=[ "Host"=> "example.com"
     , "Link"=> "<http://example.com/profiles/sally>; rel=\"tag\""
         ]
,body= ""
), Message(name = "multiple connection header values with folding"
,raw= "GET /demo HTTP/1.1\r\n" *
       "Host: example.com\r\n" *
       "Connection: Something,\r\n" *
       " Upgrade, ,Keep-Alive\r\n" *
       "Sec-WebSocket-Key2: 12998 5 Y3 1  .P00\r\n" *
       "Sec-WebSocket-Protocol: sample\r\n" *
       "Upgrade: WebSocket\r\n" *
       "Sec-WebSocket-Key1: 4 @1  46546xW%0l 1 5\r\n" *
       "Origin: http://example.com\r\n" *
       "\r\n" *
       "Hot diggity dogg"
,should_keep_alive= true
,http_major= 1
,http_minor= 1
,method= "GET"
,query_string= ""
,fragment= ""
,request_path= "/demo"
,request_url= "/demo"
,num_headers= 7
,upgrade="Hot diggity dogg"
,headers=[ "Host"=> "example.com"
           , "Connection"=> "Something, Upgrade, ,Keep-Alive"
           , "Sec-Websocket-Key2"=> "12998 5 Y3 1  .P00"
           , "Sec-Websocket-Protocol"=> "sample"
           , "Upgrade"=> "WebSocket"
           , "Sec-Websocket-Key1"=> "4 @1  46546xW%0l 1 5"
           , "Origin"=> "http://example.com"
         ]
,body= ""
), Message(name= "line folding in header value"
,raw= "GET / HTTP/1.1\r\n" *
       "Line1:   abc\r\n" *
       "\tdef\r\n" *
       " ghi\r\n" *
       "\t\tjkl\r\n" *
       "  mno \r\n" *
       "\t \tqrs\r\n" *
       "Line2: \t line2\t\r\n" *
       "Line3:\r\n" *
       " line3\r\n" *
       "Line4: \r\n" *
       " \r\n" *
       "Connection:\r\n" *
       " close\r\n" *
       "\r\n"
,should_keep_alive= false
,http_major= 1
,http_minor= 1
,method= "GET"
,query_string= ""
,fragment= ""
,request_path= "/"
,request_url= "/"
,num_headers= 5
,headers=[ "Line1"=> "abc\tdef ghi\t\tjkl  mno \t \tqrs"
           , "Line2"=> "line2"
           , "Line3"=> " line3"
           , "Line4"=> " "
           , "Connection"=> " close"
         ]
,body= ""
), Message(name = "multiple connection header values with folding and lws"
,raw= "GET /demo HTTP/1.1\r\n" *
       "Connection: keep-alive, upgrade\r\n" *
       "Upgrade: WebSocket\r\n" *
       "\r\n" *
       "Hot diggity dogg"
,should_keep_alive= true
,http_major= 1
,http_minor= 1
,method= "GET"
,query_string= ""
,fragment= ""
,request_path= "/demo"
,request_url= "/demo"
,num_headers= 2
,upgrade="Hot diggity dogg"
,headers=[ "Connection"=> "keep-alive, upgrade"
           , "Upgrade"=> "WebSocket"
         ]
,body= ""
), Message(name = "multiple connection header values with folding and lws"
,raw= "GET /demo HTTP/1.1\r\n" *
       "Connection: keep-alive, \r\n upgrade\r\n" *
       "Upgrade: WebSocket\r\n" *
       "\r\n" *
       "Hot diggity dogg"
,should_keep_alive= true
,http_major= 1
,http_minor= 1
,method= "GET"
,query_string= ""
,fragment= ""
,request_path= "/demo"
,request_url= "/demo"
,num_headers= 2
,upgrade="Hot diggity dogg"
,headers=[ "Connection"=> "keep-alive,  upgrade"
           , "Upgrade"=> "WebSocket"
         ]
,body= ""
), Message(name= "line folding in header value"
,raw= "GET / HTTP/1.1\n" *
       "Line1:   abc\n" *
       "\tdef\n" *
       " ghi\n" *
       "\t\tjkl\n" *
       "  mno \n" *
       "\t \tqrs\n" *
       "Line2: \t line2\t\n" *
       "Line3:\n" *
       " line3\n" *
       "Line4: \n" *
       " \n" *
       "Connection:\n" *
       " close\n" *
       "\n"
,should_keep_alive= false
,http_major= 1
,http_minor= 1
,method= "GET"
,query_string= ""
,fragment= ""
,request_path= "/"
,request_url= "/"
,num_headers= 5
,headers=[ "Line1"=> "abc\tdef ghi\t\tjkl  mno \t \tqrs"
           , "Line2"=> "line2"
           , "Line3"=> " line3"
           , "Line4"=> " "
           , "Connection"=> " close"
         ]
,body= ""
)
]

#= * R E S P O N S E S * =#
const responses = Message[
  Message(name= "google 301"
,raw= "HTTP/1.1 301 Moved Permanently\r\n" *
       "Location: http://www.google.com/\r\n" *
       "Content-Type: text/html; charset=UTF-8\r\n" *
       "Date: Sun, 26 Apr 2009 11:11:49 GMT\r\n" *
       "Expires: Tue, 26 May 2009 11:11:49 GMT\r\n" *
       "X-\$PrototypeBI-Version: 1.6.0.3\r\n" * #= $ char in header field =#
       "Cache-Control: public, max-age=2592000\r\n" *
       "Server: gws\r\n" *
       "Content-Length:  219  \r\n" *
       "\r\n" *
       "<HTML><HEAD><meta http-equiv=\"content-type\" content=\"text/html;charset=utf-8\">\n" *
       "<TITLE>301 Moved</TITLE></HEAD><BODY>\n" *
       "<H1>301 Moved</H1>\n" *
       "The document has moved\n" *
       "<A HREF=\"http://www.google.com/\">here</A>.\r\n" *
       "</BODY></HTML>\r\n"
,should_keep_alive= true
,http_major= 1
,http_minor= 1
,status_code= 301
,response_status= "Moved Permanently"
,num_headers= 8
,headers=[
    "Location"=> "http://www.google.com/"
  , "Content-Type"=> "text/html; charset=UTF-8"
  , "Date"=> "Sun, 26 Apr 2009 11:11:49 GMT"
  , "Expires"=> "Tue, 26 May 2009 11:11:49 GMT"
  , "X-\$prototypebi-Version"=> "1.6.0.3"
  , "Cache-Control"=> "public, max-age=2592000"
  , "Server"=> "gws"
  , "Content-Length"=> "219"
]
,body= "<HTML><HEAD><meta http-equiv=\"content-type\" content=\"text/html;charset=utf-8\">\n" *
        "<TITLE>301 Moved</TITLE></HEAD><BODY>\n" *
        "<H1>301 Moved</H1>\n" *
        "The document has moved\n" *
        "<A HREF=\"http://www.google.com/\">here</A>.\r\n" *
        "</BODY></HTML>\r\n"
), Message(name= "no content-length response"
,raw= "HTTP/1.1 200 OK\r\n" *
       "Date: Tue, 04 Aug 2009 07:59:32 GMT\r\n" *
       "Server: Apache\r\n" *
       "X-Powered-By: Servlet/2.5 JSP/2.1\r\n" *
       "Content-Type: text/xml; charset=utf-8\r\n" *
       "Connection: close\r\n" *
       "\r\n" *
       "<?xml version=\"1.0\" encoding=\"UTF-8\"?>\n" *
       "<SOAP-ENV:Envelope xmlns:SOAP-ENV=\"http://schemas.xmlsoap.org/soap/envelope/\">\n" *
       "  <SOAP-ENV:Body>\n" *
       "    <SOAP-ENV:Fault>\n" *
       "       <faultcode>SOAP-ENV:Client</faultcode>\n" *
       "       <faultstring>Client Error</faultstring>\n" *
       "    </SOAP-ENV:Fault>\n" *
       "  </SOAP-ENV:Body>\n" *
       "</SOAP-ENV:Envelope>"
,should_keep_alive= false
,http_major= 1
,http_minor= 1
,status_code= 200
,response_status= "OK"
,num_headers= 5
,headers=[
    "Date"=> "Tue, 04 Aug 2009 07:59:32 GMT"
  , "Server"=> "Apache"
  , "X-Powered-By"=> "Servlet/2.5 JSP/2.1"
  , "Content-Type"=> "text/xml; charset=utf-8"
  , "Connection"=> "close"
]
,body= "<?xml version=\"1.0\" encoding=\"UTF-8\"?>\n" *
        "<SOAP-ENV:Envelope xmlns:SOAP-ENV=\"http://schemas.xmlsoap.org/soap/envelope/\">\n" *
        "  <SOAP-ENV:Body>\n" *
        "    <SOAP-ENV:Fault>\n" *
        "       <faultcode>SOAP-ENV:Client</faultcode>\n" *
        "       <faultstring>Client Error</faultstring>\n" *
        "    </SOAP-ENV:Fault>\n" *
        "  </SOAP-ENV:Body>\n" *
        "</SOAP-ENV:Envelope>"
), Message(name= "404 no headers no body"
,raw= "HTTP/1.1 404 Not Found\r\n\r\n"
,should_keep_alive= false
,http_major= 1
,http_minor= 1
,status_code= 404
,response_status= "Not Found"
,num_headers= 0
,headers=Headers()
,body_size= 0
,body= ""
), Message(name= "301 no response phrase"
,raw= "HTTP/1.1 301\r\n\r\n"
,should_keep_alive = false
,http_major= 1
,http_minor= 1
,status_code= 301
,response_status= "Moved Permanently"
,num_headers= 0
,headers=Headers()
,body= ""
), Message(name="200 trailing space on chunked body"
,raw= "HTTP/1.1 200 OK\r\n" *
       "Content-Type: text/plain\r\n" *
       "Transfer-Encoding: chunked\r\n" *
       "\r\n" *
       "25  \r\n" *
       "This is the data in the first chunk\r\n" *
       "\r\n" *
       "1C\r\n" *
       "and this is the second one\r\n" *
       "\r\n" *
       "0  \r\n" *
       "\r\n"
,should_keep_alive= true
,http_major= 1
,http_minor= 1
,status_code= 200
,response_status= "OK"
,num_headers= 2
,headers=[
    "Content-Type"=> "text/plain"
  , "Transfer-Encoding"=> "chunked"
]
,body_size = 37+28
,body =
       "This is the data in the first chunk\r\n" *
       "and this is the second one\r\n"
), Message(name="no carriage ret"
,raw= "HTTP/1.1 200 OK\n" *
       "Content-Type: text/html; charset=utf-8\n" *
       "Connection: close\n" *
       "\n" *
       "these headers are from http://news.ycombinator.com/"
,should_keep_alive= false
,http_major= 1
,http_minor= 1
,status_code= 200
,response_status= "OK"
,num_headers= 2
,headers=[
    "Content-Type"=> "text/html; charset=utf-8"
  , "Connection"=> "close"
]
,body= "these headers are from http://news.ycombinator.com/"
), Message(name="proxy connection"
,raw= "HTTP/1.1 200 OK\r\n" *
       "Content-Type: text/html; charset=UTF-8\r\n" *
       "Content-Length: 11\r\n" *
       "Proxy-Connection: close\r\n" *
       "Date: Thu, 31 Dec 2009 20:55:48 +0000\r\n" *
       "\r\n" *
       "hello world"
,should_keep_alive= false
,http_major= 1
,http_minor= 1
,status_code= 200
,response_status= "OK"
,num_headers= 4
,headers=[
    "Content-Type"=> "text/html; charset=UTF-8"
  , "Content-Length"=> "11"
  , "Proxy-Connection"=> "close"
  , "Date"=> "Thu, 31 Dec 2009 20:55:48 +0000"
]
,body= "hello world"
), Message(name="underscore header key"
,raw= "HTTP/1.1 200 OK\r\n" *
       "Server: DCLK-AdSvr\r\n" *
       "Content-Type: text/xml\r\n" *
       "Content-Length: 0\r\n" *
       "DCLK_imp: v7;x;114750856;0-0;0;17820020;0/0;21603567/21621457/1;;~okv=;dcmt=text/xml;;~cs=o\r\n\r\n"
,should_keep_alive= true
,http_major= 1
,http_minor= 1
,status_code= 200
,response_status= "OK"
,num_headers= 4
,headers=[
    "Server"=> "DCLK-AdSvr"
  , "Content-Type"=> "text/xml"
  , "Content-Length"=> "0"
  , "Dclk_imp"=> "v7;x;114750856;0-0;0;17820020;0/0;21603567/21621457/1;;~okv=;dcmt=text/xml;;~cs=o"
]
,body= ""
), Message(name= "bonjourmadame.fr"
,raw= "HTTP/1.0 301 Moved Permanently\r\n" *
       "Date: Thu, 03 Jun 2010 09:56:32 GMT\r\n" *
       "Server: Apache/2.2.3 (Red Hat)\r\n" *
       "Cache-Control: public\r\n" *
       "Pragma: \r\n" *
       "Location: http://www.bonjourmadame.fr/\r\n" *
       "Vary: Accept-Encoding\r\n" *
       "Content-Length: 0\r\n" *
       "Content-Type: text/html; charset=UTF-8\r\n" *
       "Connection: keep-alive\r\n" *
       "\r\n"
,should_keep_alive= true
,http_major= 1
,http_minor= 0
,status_code= 301
,response_status= "Moved Permanently"
,num_headers= 9
,headers=[
    "Date"=> "Thu, 03 Jun 2010 09:56:32 GMT"
  , "Server"=> "Apache/2.2.3 (Red Hat)"
  , "Cache-Control"=> "public"
  , "Pragma"=> ""
  , "Location"=> "http://www.bonjourmadame.fr/"
  , "Vary"=>  "Accept-Encoding"
  , "Content-Length"=> "0"
  , "Content-Type"=> "text/html; charset=UTF-8"
  , "Connection"=> "keep-alive"
]
,body= ""
), Message(name= "field underscore"
,raw= "HTTP/1.1 200 OK\r\n" *
       "Date: Tue, 28 Sep 2010 01:14:13 GMT\r\n" *
       "Server: Apache\r\n" *
       "Cache-Control: no-cache, must-revalidate\r\n" *
       "Expires: Mon, 26 Jul 1997 05:00:00 GMT\r\n" *
       ".et-Cookie: PlaxoCS=1274804622353690521; path=/; domain=.plaxo.com\r\n" *
       "Vary: Accept-Encoding\r\n" *
       "_eep-Alive: timeout=45\r\n" * #= semantic value ignored =#
       "_onnection: Keep-Alive\r\n" * #= semantic value ignored =#
       "Transfer-Encoding: chunked\r\n" *
       "Content-Type: text/html\r\n" *
       "Connection: close\r\n" *
       "\r\n" *
       "0\r\n\r\n"
,should_keep_alive= false
,http_major= 1
,http_minor= 1
,status_code= 200
,response_status= "OK"
,num_headers= 11
,headers=[
    "Date"=> "Tue, 28 Sep 2010 01:14:13 GMT"
  , "Server"=> "Apache"
  , "Cache-Control"=> "no-cache, must-revalidate"
  , "Expires"=> "Mon, 26 Jul 1997 05:00:00 GMT"
  , ".et-Cookie"=> "PlaxoCS=1274804622353690521; path=/; domain=.plaxo.com"
  , "Vary"=> "Accept-Encoding"
  , "_eep-Alive"=> "timeout=45"
  , "_onnection"=> "Keep-Alive"
  , "Transfer-Encoding"=> "chunked"
  , "Content-Type"=> "text/html"
  , "Connection"=> "close"
]
,body= ""
), Message(name= "non-ASCII in status line"
,raw= "HTTP/1.1 500 Oriëntatieprobleem\r\n" *
       "Date: Fri, 5 Nov 2010 23:07:12 GMT+2\r\n" *
       "Content-Length: 0\r\n" *
       "Connection: close\r\n" *
       "\r\n"
,should_keep_alive= false
,http_major= 1
,http_minor= 1
,status_code= 500
,response_status= "Internal Server Error"
,num_headers= 3
,headers=[
    "Date"=> "Fri, 5 Nov 2010 23:07:12 GMT+2"
  , "Content-Length"=> "0"
  , "Connection"=> "close"
]
,body= ""
), Message(name= "http version 0.9"
,raw= "HTTP/0.9 200 OK\r\n" *
       "\r\n"
,should_keep_alive= false
,http_major= 0
,http_minor= 9
,status_code= 200
,response_status= "OK"
,num_headers= 0
,headers=Headers()
,body= ""
), Message(name= "neither content-length nor transfer-encoding response"
,raw= "HTTP/1.1 200 OK\r\n" *
       "Content-Type: text/plain\r\n" *
       "\r\n" *
       "hello world"
,should_keep_alive= false
,http_major= 1
,http_minor= 1
,status_code= 200
,response_status= "OK"
,num_headers= 1
,headers=[
    "Content-Type"=> "text/plain"
]
,body= "hello world"
), Message(name= "HTTP/1.0 with keep-alive and EOF-terminated 200 status"
,raw= "HTTP/1.0 200 OK\r\n" *
       "Connection: keep-alive\r\n" *
       "\r\n"
,should_keep_alive= false
,http_major= 1
,http_minor= 0
,status_code= 200
,response_status= "OK"
,num_headers= 1
,headers=[
    "Connection"=> "keep-alive"
]
,body_size= 0
,body= ""
), Message(name= "HTTP/1.0 with keep-alive and a 204 status"
,raw= "HTTP/1.0 204 No content\r\n" *
       "Connection: keep-alive\r\n" *
       "\r\n"
,should_keep_alive= true
,http_major= 1
,http_minor= 0
,status_code= 204
,response_status= "No Content"
,num_headers= 1
,headers=[
    "Connection"=> "keep-alive"
]
,body_size= 0
,body= ""
), Message(name= "HTTP/1.1 with an EOF-terminated 200 status"
,raw= "HTTP/1.1 200 OK\r\n" *
       "\r\n"
,should_keep_alive= false
,http_major= 1
,http_minor= 1
,status_code= 200
,response_status= "OK"
,num_headers= 0
,headers=Headers()
,body_size= 0
,body= ""
), Message(name= "HTTP/1.1 with a 204 status"
,raw= "HTTP/1.1 204 No content\r\n" *
       "\r\n"
,should_keep_alive= true
,http_major= 1
,http_minor= 1
,status_code= 204
,response_status= "No Content"
,num_headers= 0
,headers=Headers()
,body_size= 0
,body= ""
), Message(name= "HTTP/1.1 with a 204 status and keep-alive disabled"
,raw= "HTTP/1.1 204 No content\r\n" *
       "Connection: close\r\n" *
       "\r\n"
,should_keep_alive= false
,http_major= 1
,http_minor= 1
,status_code= 204
,response_status= "No Content"
,num_headers= 1
,headers=[
    "Connection"=> "close"
]
,body_size= 0
,body= ""
), Message(name= "HTTP/1.1 with chunked endocing and a 200 response"
,raw= "HTTP/1.1 200 OK\r\n" *
       "Transfer-Encoding: chunked\r\n" *
       "\r\n" *
       "0\r\n" *
       "\r\n"
,should_keep_alive= true
,http_major= 1
,http_minor= 1
,status_code= 200
,response_status= "OK"
,num_headers= 1
,headers=[
    "Transfer-Encoding"=> "chunked"
]
,body_size= 0
,body= ""
#=
No reference to source of this was provided when requested here:
https://github.com/nodejs/http-parser/pull/64#issuecomment-2042429
), Message(name= "field space"
,raw= "HTTP/1.1 200 OK\r\n" *
       "Server: Microsoft-IIS/6.0\r\n" *
       "X-Powered-By: ASP.NET\r\n" *
       "en-US Content-Type: text/xml\r\n" * #= this is the problem =#
       "Content-Type: text/xml\r\n" *
       "Content-Length: 16\r\n" *
       "Date: Fri, 23 Jul 2010 18:45:38 GMT\r\n" *
       "Connection: keep-alive\r\n" *
       "\r\n" *
       "<xml>hello</xml>" #= fake body =#
,should_keep_alive= true
,http_major= 1
,http_minor= 1
,status_code= 200
,response_status= "OK"
,num_headers= 7
,headers=[
    "Server"=>  "Microsoft-IIS/6.0"
  , "X-Powered-By"=> "ASP.NET"
  , "En-Us content-Type"=> "text/xml"
  , "Content-Type"=> "text/xml"
  , "Content-Length"=> "16"
  , "Date"=> "Fri, 23 Jul 2010 18:45:38 GMT"
  , "Connection"=> "keep-alive"
]
,body= "<xml>hello</xml>"
=#
), Message(name= "amazon.com"
,raw= "HTTP/1.1 301 MovedPermanently\r\n" *
       "Date: Wed, 15 May 2013 17:06:33 GMT\r\n" *
       "Server: Server\r\n" *
       "x-amz-id-1: 0GPHKXSJQ826RK7GZEB2\r\n" *
       "p3p: policyref=\"http://www.amazon.com/w3c/p3p.xml\",CP=\"CAO DSP LAW CUR ADM IVAo IVDo CONo OTPo OUR DELi PUBi OTRi BUS PHY ONL UNI PUR FIN COM NAV INT DEM CNT STA HEA PRE LOC GOV OTC \"\r\n" *
       "x-amz-id-2: STN69VZxIFSz9YJLbz1GDbxpbjG6Qjmmq5E3DxRhOUw+Et0p4hr7c/Q8qNcx4oAD\r\n" *
       "Location: http://www.amazon.com/Dan-Brown/e/B000AP9DSU/ref=s9_pop_gw_al1?_encoding=UTF8&refinementId=618073011&pf_rd_m=ATVPDKIKX0DER&pf_rd_s=center-2&pf_rd_r=0SHYY5BZXN3KR20BNFAY&pf_rd_t=101&pf_rd_p=1263340922&pf_rd_i=507846\r\n" *
       "Vary: Accept-Encoding,User-Agent\r\n" *
       "Content-Type: text/html; charset=ISO-8859-1\r\n" *
       "Transfer-Encoding: chunked\r\n" *
       "\r\n" *
       "1\r\n" *
       "\n\r\n" *
       "0\r\n" *
       "\r\n"
,should_keep_alive= true
,http_major= 1
,http_minor= 1
,status_code= 301
,response_status= "Moved Permanently"
,num_headers= 9
,headers=[ "Date"=> "Wed, 15 May 2013 17:06:33 GMT"
           , "Server"=> "Server"
           , "X-Amz-Id-1"=> "0GPHKXSJQ826RK7GZEB2"
           , "P3p"=> "policyref=\"http://www.amazon.com/w3c/p3p.xml\",CP=\"CAO DSP LAW CUR ADM IVAo IVDo CONo OTPo OUR DELi PUBi OTRi BUS PHY ONL UNI PUR FIN COM NAV INT DEM CNT STA HEA PRE LOC GOV OTC \""
           , "X-Amz-Id-2"=> "STN69VZxIFSz9YJLbz1GDbxpbjG6Qjmmq5E3DxRhOUw+Et0p4hr7c/Q8qNcx4oAD"
           , "Location"=> "http://www.amazon.com/Dan-Brown/e/B000AP9DSU/ref=s9_pop_gw_al1?_encoding=UTF8&refinementId=618073011&pf_rd_m=ATVPDKIKX0DER&pf_rd_s=center-2&pf_rd_r=0SHYY5BZXN3KR20BNFAY&pf_rd_t=101&pf_rd_p=1263340922&pf_rd_i=507846"
           , "Vary"=> "Accept-Encoding,User-Agent"
           , "Content-Type"=> "text/html; charset=ISO-8859-1"
           , "Transfer-Encoding"=> "chunked"
         ]
,body= "\n"
), Message(name= "empty reason phrase after space"
,raw= "HTTP/1.1 200 \r\n" *
       "\r\n"
,should_keep_alive= false
,http_major= 1
,http_minor= 1
,status_code= 200
,response_status= "OK"
,num_headers= 0
,headers=Headers()
,body= ""
), Message(name= "Content-Length-X"
,raw= "HTTP/1.1 200 OK\r\n" *
       "Content-Length-X: 0\r\n" *
       "Transfer-Encoding: chunked\r\n" *
       "\r\n" *
       "2\r\n" *
       "OK\r\n" *
       "0\r\n" *
       "\r\n"
,should_keep_alive= true
,http_major= 1
,http_minor= 1
,status_code= 200
,response_status= "OK"
,num_headers= 2
,headers=[ "Content-Length-X"=> "0"
           , "Transfer-Encoding"=> "chunked"
         ]
,body= "OK"
)
]

@testset "HTTP.parse" begin

  @testset "HTTP.parse(HTTP.Request, str)" begin
      for req in requests

          println("TEST - parser.jl - Request $(req.name)")
          r = parse(Request, req.raw)
          if r.method == "CONNECT"
              host, port = split(r.target, ":")
              @test host == req.host
              @test port == req.port
          else
              if r.target == "*"
                  @test r.target == req.request_path
              else
                  target = parse(HTTP.URI, r.target)
                  @test target.query == req.query_string
                  @test target.fragment == req.fragment
                  @test target.path == req.request_path
                  @test target.host == req.host
                  @test target.userinfo == req.userinfo
                  @test target.port in (req.port, "80", "443")
                  @test string(target) == req.request_url
              end
          end
          @test r.version.major == req.http_major
          @test r.version.minor == req.http_minor
          @test r.method == string(req.method)
          @test length(r.headers) == req.num_headers
          r_headers = [tocameldash(n) => String(v) for (n,v) in r.headers]
          @test r_headers == req.headers
          @test String(r.body) == req.body
# FIXME          @test HTTP.http_should_keep_alive(HTTP.DEFAULT_PARSER) == req.should_keep_alive
# FIXME          @test String(collect(upgrade[])) == req.upgrade
      end

      reqstr = "GET http://www.techcrunch.com/ HTTP/1.1\r\n" *
               "Host: www.techcrunch.com\r\n" *
               "User-Agent: Fake\r\n" *
               "Accept: text/html,application/xhtml+xml,application/xml;q=0.9,*/*;q=0.8\r\n" *
               "Accept-Language: en-us,en;q=0.5\r\n" *
               "Accept-Encoding: gzip,deflate\r\n" *
               "Accept-Charset: ISO-8859-1,utf-8;q=0.7,*;q=0.7\r\n" *
               "Keep-Alive: 300\r\n" *
               "Content-Length: 7\r\n" *
               "Proxy-Connection: keep-alive\r\n\r\n1234567"

      req = Request("GET", "http://www.techcrunch.com/",
                    ["Host"=>"www.techcrunch.com","User-Agent"=>"Fake","Accept"=>"text/html,application/xhtml+xml,application/xml;q=0.9,*/*;q=0.8","Accept-Language"=>"en-us,en;q=0.5","Accept-Encoding"=>"gzip,deflate","Accept-Charset"=>"ISO-8859-1,utf-8;q=0.7,*;q=0.7","Keep-Alive"=>"300","Content-Length"=>"7","Proxy-Connection"=>"keep-alive"])
      req.body = HTTP.bytes("1234567")

      @test parse(Request,reqstr).headers == req.headers
      @test parse(Request,reqstr) == req

      reqstr = "GET / HTTP/1.1\r\n" *
               "Host: foo.com\r\n\r\n"

      req = Request("GET", "/", ["Host"=>"foo.com"])

      @test parse(Request, reqstr) == req

      reqstr = "GET //user@host/is/actually/a/path/ HTTP/1.1\r\n" *
               "Host: test\r\n\r\n"

      req = Request("GET", "//user@host/is/actually/a/path/",
                   ["Host"=>"test"])

      @test parse(Request, reqstr) == req

      reqstr = "GET ../../../../etc/passwd HTTP/1.1\r\n" *
               "Host: test\r\n\r\n"

      @test_throws HTTP.ParseError HTTP.URI(parse(Request, reqstr).target)

      reqstr = "GET  HTTP/1.1\r\n" *
               "Host: test\r\n\r\n"

      @test_throws HTTP.ParseError parse(Request, reqstr)

      reqstr = "POST / HTTP/1.1\r\n" *
               "Host: foo.com\r\n" *
               "Transfer-Encoding: chunked\r\n\r\n" *
               "3\r\nfoo\r\n" *
               "3\r\nbar\r\n" *
               "0\r\n" *
               "Trailer-Key: Trailer-Value\r\n" *
               "\r\n"

      req = Request("POST", "/",
                    ["Host"=>"foo.com", "Transfer-Encoding"=>"chunked", "Trailer-Key"=>"Trailer-Value"])
      req.body = HTTP.bytes("foobar")

      @test parse(Request, reqstr) == req

#= FIXME
      reqstr = "POST / HTTP/1.1\r\n" *
               "Host: foo.com\r\n" *
               "Transfer-Encoding: chunked\r\n" *
               "Content-Length: 9999\r\n\r\n" * # to be removed.
               "3\r\nfoo\r\n" *
               "3\r\nbar\r\n" *
               "0\r\n" *
               "\r\n"

      @test_throws HTTP.ParseError parse(Request, reqstr)
=#

      reqstr = "CONNECT www.google.com:443 HTTP/1.1\r\n\r\n"

      req = Request("CONNECT", "www.google.com:443")

      @test parse(Request, reqstr) == req

      reqstr = "CONNECT 127.0.0.1:6060 HTTP/1.1\r\n\r\n"

      req = Request("CONNECT", "127.0.0.1:6060")

      @test parse(Request, reqstr) == req

      reqstr = "CONNECT /_goRPC_ HTTP/1.1\r\n\r\n"
      req = HTTP.Request("CONNECT", "/_goRPC_")

      @test parse(Request, reqstr) == req

      reqstr = "NOTIFY * HTTP/1.1\r\nServer: foo\r\n\r\n"

      req = Request("NOTIFY", "*", ["Server"=>"foo"])

      @test parse(Request, reqstr) == req

      reqstr = "OPTIONS * HTTP/1.1\r\nServer: foo\r\n\r\n"

      req = Request("OPTIONS", "*", ["Server"=>"foo"])

      @test parse(Request, reqstr) == req

      reqstr = "GET / HTTP/1.1\r\nHost: issue8261.com\r\nConnection: close\r\n\r\n"

      req = Request("GET", "/", ["Host"=>"issue8261.com", "Connection"=>"close"])

      @test parse(Request, reqstr) == req

      reqstr = "HEAD / HTTP/1.1\r\nHost: issue8261.com\r\nConnection: close\r\nContent-Length: 0\r\n\r\n"

      req = Request("HEAD", "/", ["Host"=>"issue8261.com", "Connection"=>"close", "Content-Length"=>"0"])

      @test parse(Request, reqstr) == req

      reqstr = "POST /cgi-bin/process.cgi HTTP/1.1\r\n" *
               "User-Agent: Mozilla/4.0 (compatible; MSIE5.01; Windows NT)\r\n" *
               "Host: www.tutorialspoint.com\r\n" *
               "Content-Type: text/xml; charset=utf-8\r\n" *
               "Content-Length: 19\r\n" *
               "Accept-Language: en-us\r\n" *
               "Accept-Encoding: gzip, deflate\r\n" *
               "Connection: Keep-Alive\r\n\r\n" *
               "first=Zara&last=Ali\r\n\r\n"

      req = Request("POST", "/cgi-bin/process.cgi",
                    ["User-Agent"=>"Mozilla/4.0 (compatible; MSIE5.01; Windows NT)",
                     "Host"=>"www.tutorialspoint.com",
                     "Content-Type"=>"text/xml; charset=utf-8",
                     "Content-Length"=>"19",
                     "Accept-Language"=>"en-us",
                     "Accept-Encoding"=>"gzip, deflate",
                     "Connection"=>"Keep-Alive"])
      req.body = HTTP.bytes("first=Zara&last=Ali")

      @test parse(Request, reqstr) == req
  end

  @testset "Response(str)" begin
      for resp in responses
          println("TEST - parser.jl - Response $(resp.name)")
          r = parse(Response, resp.raw)
          @test r.version.major == resp.http_major
          @test r.version.minor == resp.http_minor
          @test r.status == resp.status_code
          @test HTTP.Messages.statustext(r.status) == resp.response_status
          @test length(r.headers) == resp.num_headers
          r_headers = [tocameldash(n) => String(v) for (n,v) in r.headers]
          @test r_headers == resp.headers
          @test String(r.body) == resp.body
# FIXME              @test HTTP.http_should_keep_alive(HTTP.DEFAULT_PARSER) == resp.should_keep_alive
      end
  end

  @testset "HTTP.parse errors" begin
      reqstr = "GET / HTTP/1.1\r\n" * "Foo: F\01ailure\r\n\r\n"
      strict && @test_throws HTTP.ParseError parse(Request,reqstr)
      if !strict
        r = HTTP.parse(HTTP.Messages.Request, reqstr)
        @test r.method == "GET"
        @test r.target == "/"
        @test length(r.headers) == 1
      end

      reqstr = "GET / HTTP/1.1\r\n" * "Foo: B\02ar\r\n\r\n"
      strict && @test_throws HTTP.ParseError parse(Request, reqstr)
      if !strict
          r = parse(HTTP.Messages.Request, reqstr)
          @test r.method == "GET"
          @test r.target == "/"
          @test length(r.headers) == 1
      end

      respstr = "HTTP/1.1 200 OK\r\n" * "Foo: F\01ailure\r\n\r\n"
      strict && @test_throws HTTP.ParseError parse(Response,respstr)
      if !strict
          r = parse(HTTP.Messages.Response, respstr)
          @test r.status == 200
          @test length(r.headers) == 1
      end

      respstr = "HTTP/1.1 200 OK\r\n" * "Foo: B\02ar\r\n\r\n"
      strict && @test_throws HTTP.ParseError parse(Response,respstr)
      if !strict
          r = parse(HTTP.Messages.Response, respstr)
          @test r.status == 200
          @test length(r.headers) == 1
      end

      reqstr = "GET / HTTP/1.1\r\n" * "Fo@: Failure\r\n\r\n"
      @test_throws HTTP.ParseError parse(Request, reqstr)

      reqstr = "GET / HTTP/1.1\r\n" * "Foo\01\test: Bar\r\n\r\n"
      @test_throws HTTP.ParseError parse(Request, reqstr)

      respstr = "HTTP/1.1 200 OK\r\n" * "Fo@: Failure\r\n\r\n"
      @test_throws HTTP.ParseError parse(Response, respstr)
      @test occursin(r"INVALID_HEADER_FIELD", @errmsg(parse(Response, respstr)))

      respstr = "HTTP/1.1 200 OK\r\n" * "Foo\01\test: Bar\r\n\r\n"
      @test_throws HTTP.ParseError parse(Response, respstr)

      reqstr = "GET / HTTP/1.1\r\n" * "Content-Length: 0\r\nContent-Length: 1\r\n\r\n"
      strict && @test_throws HTTP.ParseError parse(Request, reqstr)
      respstr = "HTTP/1.1 200 OK\r\n" * "Content-Length: 0\r\nContent-Length: 1\r\n\r\n"
      strict && @test_throws HTTP.ParseError parse(Response, respstr)

      reqstr = "GET / HTTP/1.1\r\n" * "Transfer-Encoding: chunked\r\nContent-Length: 1\r\n\r\n"
      strict && @test_throws HTTP.ParseError parse(Request, reqstr)
      respstr = "HTTP/1.1 200 OK\r\n" * "Transfer-Encoding: chunked\r\nContent-Length: 1\r\n\r\n"
      strict && @test_throws HTTP.ParseError parse(Response, respstr)

      reqstr = "GET / HTTP/1.1\r\n" * "Foo: 1\rBar: 1\r\n\r\n"
      @test_throws HTTP.ParseError parse(Request, reqstr)
      respstr = "HTTP/1.1 200 OK\r\n" * "Foo: 1\rBar: 1\r\n\r\n"
      @test_throws HTTP.ParseError parse(Response, respstr)

      buf = "GET / HTTP/1.1\r\nheader: value\nhdr: value\r\n"
      @test_throws EOFError r = parse(Request, buf)

      respstr = "HTTP/1.1 200 OK\r\n" * "Content-Length: " * "1844674407370955160" * "\r\n\r\n"
      r = Response()
      readheaders(IOBuffer(respstr), r)
      @test r.status == 200
      @test [r.headers...] == ["Content-Length"=>"1844674407370955160"]

#=
      respstr = "HTTP/1.1 200 OK\r\n" * "Content-Length: " * "18446744073709551615" * "\r\n\r\n"
      e = try parse(Response,respstr) catch e e end
      @test isa(e, HTTP.ParseError) && e.code == Parsers.HPE_INVALID_CONTENT_LENGTH

      respstr = "HTTP/1.1 200 OK\r\n" * "Content-Length: " * "18446744073709551616" * "\r\n\r\n"
      e = try parse(Response,respstr) catch e e end
      @test isa(e, HTTP.ParseError) && e.code == Parsers.HPE_INVALID_CONTENT_LENGTH
=#

      respstr = "HTTP/1.1 200 OK\r\n" * "Transfer-Encoding: chunked\r\n\r\n" * "FFFFFFFFFFFFFFE" * "\r\n..."
      r = Response()
      readheaders(IOBuffer(respstr), r)
      @test r.status == 200
      @test [r.headers...] == ["Transfer-Encoding"=>"chunked"]

      respstr = "HTTP/1.1 200 OK\r\n" * "Transfer-Encoding: chunked\r\n\r\n" * "FFFFFFFFFFFFFFF" * "\r\n..."
      e = try parse(Response,respstr) catch e e end
      @test isa(e, HTTP.ParseError) && e.code == :CHUNK_SIZE_EXCEEDS_LIMIT
      respstr = "HTTP/1.1 200 OK\r\n" * "Transfer-Encoding: chunked\r\n\r\n" * "10000000000000000" * "\r\n..."
      e = try parse(Response,respstr) catch e e end
      @test isa(e, HTTP.ParseError) && e.code == :CHUNK_SIZE_EXCEEDS_LIMIT

      @test_throws HTTP.ParseError parse(Request,"GET / HTP/1.1\r\n\r\n")

      r = parse(Request,"GET / HTTP/1.1\r\n" * "Test: Düsseldorf\r\n\r\n")
      @test Pair{String,String}[r.headers...] == ["Test" => "Düsseldorf"]

      for m in ["GET", "PUT", "M-SEARCH", "FOOMETHOD"]
          r = parse(Request,"$m / HTTP/1.1\r\n\r\n")
          @test r.method == string(m)
      end

      for m in ("HTTP/1.1", "hello world")
          @test_throws HTTP.ParseError parse(Request,"$m / HTTP/1.1\r\n\r\n")
      end
      for m in ("ASDF","C******","COLA","GEM","GETA","M****","MKCOLA","PROPPATCHA","PUN","PX","SA")
          @test parse(Request,"$m / HTTP/1.1\r\n\r\n").method == m
      end

      @test_throws HTTP.ParseError parse(Request,"GET / HTTP/1.1\r\n" * "name\r\n" * " : value\r\n\r\n")

      reqstr = "GET / HTTP/1.1\r\n" *
      "X-SSL-FoooBarr:   -----BEGIN CERTIFICATE-----\r\n" *
      "\tMIIFbTCCBFWgAwIBAgICH4cwDQYJKoZIhvcNAQEFBQAwcDELMAkGA1UEBhMCVUsx\r\n" *
      "\tETAPBgNVBAoTCGVTY2llbmNlMRIwEAYDVQQLEwlBdXRob3JpdHkxCzAJBgNVBAMT\r\n" *
      "\tAkNBMS0wKwYJKoZIhvcNAQkBFh5jYS1vcGVyYXRvckBncmlkLXN1cHBvcnQuYWMu\r\n" *
      "\tdWswHhcNMDYwNzI3MTQxMzI4WhcNMDcwNzI3MTQxMzI4WjBbMQswCQYDVQQGEwJV\r\n" *
      "\tSzERMA8GA1UEChMIZVNjaWVuY2UxEzARBgNVBAsTCk1hbmNoZXN0ZXIxCzAJBgNV\r\n" *
      "\tBAcTmrsogriqMWLAk1DMRcwFQYDVQQDEw5taWNoYWVsIHBhcmQYJKoZIhvcNAQEB\r\n" *
      "\tBQADggEPADCCAQoCggEBANPEQBgl1IaKdSS1TbhF3hEXSl72G9J+WC/1R64fAcEF\r\n" *
      "\tW51rEyFYiIeZGx/BVzwXbeBoNUK41OK65sxGuflMo5gLflbwJtHBRIEKAfVVp3YR\r\n" *
      "\tgW7cMA/s/XKgL1GEC7rQw8lIZT8RApukCGqOVHSi/F1SiFlPDxuDfmdiNzL31+sL\r\n" *
      "\t0iwHDdNkGjy5pyBSB8Y79dsSJtCW/iaLB0/n8Sj7HgvvZJ7x0fr+RQjYOUUfrePP\r\n" *
      "\tu2MSpFyf+9BbC/aXgaZuiCvSR+8Snv3xApQY+fULK/xY8h8Ua51iXoQ5jrgu2SqR\r\n" *
      "\twgA7BUi3G8LFzMBl8FRCDYGUDy7M6QaHXx1ZWIPWNKsCAwEAAaOCAiQwggIgMAwG\r\n" *
      "\tA1UdEwEB/wQCMAAwEQYJYIZIAYb4QgHTTPAQDAgWgMA4GA1UdDwEB/wQEAwID6DAs\r\n" *
      "\tBglghkgBhvhCAQ0EHxYdVUsgZS1TY2llbmNlIFVzZXIgQ2VydGlmaWNhdGUwHQYD\r\n" *
      "\tVR0OBBYEFDTt/sf9PeMaZDHkUIldrDYMNTBZMIGaBgNVHSMEgZIwgY+AFAI4qxGj\r\n" *
      "\tloCLDdMVKwiljjDastqooXSkcjBwMQswCQYDVQQGEwJVSzERMA8GA1UEChMIZVNj\r\n" *
      "\taWVuY2UxEjAQBgNVBAsTCUF1dGhvcml0eTELMAkGA1UEAxMCQ0ExLTArBgkqhkiG\r\n" *
      "\t9w0BCQEWHmNhLW9wZXJhdG9yQGdyaWQtc3VwcG9ydC5hYy51a4IBADApBgNVHRIE\r\n" *
      "\tIjAggR5jYS1vcGVyYXRvckBncmlkLXN1cHBvcnQuYWMudWswGQYDVR0gBBIwEDAO\r\n" *
      "\tBgwrBgEEAdkvAQEBAQYwPQYJYIZIAYb4QgEEBDAWLmh0dHA6Ly9jYS5ncmlkLXN1\r\n" *
      "\tcHBvcnQuYWMudmT4sopwqlBWsvcHViL2NybC9jYWNybC5jcmwwPQYJYIZIAYb4QgEDBDAWLmh0\r\n" *
      "\tdHA6Ly9jYS5ncmlkLXN1cHBvcnQuYWMudWsvcHViL2NybC9jYWNybC5jcmwwPwYD\r\n" *
      "\tVR0fBDgwNjA0oDKgMIYuaHR0cDovL2NhLmdyaWQt5hYy51ay9wdWIv\r\n" *
      "\tY3JsL2NhY3JsLmNybDANBgkqhkiG9w0BAQUFAAOCAQEAS/U4iiooBENGW/Hwmmd3\r\n" *
      "\tXCy6Zrt08YjKCzGNjorT98g8uGsqYjSxv/hmi0qlnlHs+k/3Iobc3LjS5AMYr5L8\r\n" *
      "\tUO7OSkgFFlLHQyC9JzPfmLCAugvzEbyv4Olnsr8hbxF1MbKZoQxUZtMVu29wjfXk\r\n" *
      "\thTeApBv7eaKCWpSp7MCbvgzm74izKhu3vlDk9w6qVrxePfGgpKPqfHiOoGhFnbTK\r\n" *
      "\twTC6o2xq5y0qZ03JonF7OJspEd3I5zKY3E+ov7/ZhW6DqT8UFvsAdjvQbXyhV8Eu\r\n" *
      "\tYhixw1aKEPzNjNowuIseVogKOLXxWI5vAi5HgXdS0/ES5gDGsABo4fqovUKlgop3\r\n" *
      "\tRA==\r\n" *
      "\t-----END CERTIFICATE-----\r\n" *
      "\r\n"

      r = parse(Request, reqstr)
      @test r.method == "GET"

      @test "GET / HTTP/1.1X-SSL-FoooBarr:   $(header(r, "X-SSL-FoooBarr"))" == replace(reqstr, "\r\n" => "")

      # @test_throws HTTP.HTTP.ParseError HTTP.parse(HTTP.Request, "GET / HTTP/1.1\r\nHost: www.example.com\r\nConnection\r\033\065\325eep-Alive\r\nAccept-Encoding: gzip\r\n\r\n")

      r = parse(Request,"GET /bad_get_no_headers_no_body/world HTTP/1.1\r\nAccept: */*\r\n\r\nHELLO")
      @test String(r.body) == ""
  end
end # @testset HTTP.parse

end # module ParserTest


text:
module ModuleProjection
using MultipleViewGeometry.ModuleTypes, MultipleViewGeometry.ModuleOperators, MultipleViewGeometry.ModuleMathAliases
using StaticArrays, LinearAlgebra
export project, epipole
include("project.jl")
include("epipole.jl")
end

ext: jl


text:
function MOI.set(
    model::MOI.ModelLike,
    attr::ForwardInConstraint,
    bridge::MOI.Bridges.Constraint.VectorizeBridge{T},
    value,
) where {T}
    MOI.set(model, attr, bridge.vector_constraint, MOI.Utilities.operate(vcat, T, value))
end
function MOI.get(
    model::MOI.ModelLike,
    attr::DiffOpt.BackwardOutConstraint,
    bridge::MOI.Bridges.Constraint.AbstractFunctionConversionBridge,
)
    return MOI.get(model, attr, bridge.constraint)
end
function MOI.set(
    model::MOI.ModelLike,
    attr::DiffOpt.ForwardInConstraint,
    bridge::MOI.Bridges.Constraint.SetMapBridge,
    func,
)
    mapped_func = MOI.Bridges.map_function(typeof(bridge), func)
    MOI.set(model, attr, bridge.constraint, mapped_func)
end
function MOI.get(
    model::MOI.ModelLike,
    attr::DiffOpt.BackwardOutConstraint,
    bridge::MOI.Bridges.Constraint.SetMapBridge,
)
    func = MOI.get(model, attr, bridge.constraint)
    return MOI.Bridges.adjoint_map_function(typeof(bridge), func)
end


text:
module ModelObjectsLH

using DocStringExtensions

export SingleId, has_index, make_string, make_single_id
export ObjectId, make_object_id, make_child_id, own_name, n_parents, description
export ModelSwitches
export ModelObject, is_model_object, get_object_id,
    collect_model_objects, collect_object_ids, get_child_objects, find_object, find_only_object, get_value
export object_structure, show_object_structure

const ObjIdSeparator = " > ";

"""
    ModelObject

Abstract model object
Must have field `objId :: ObjectId` that uniquely identifies it
May contain a ParamVector, but need not.

Child objects may be vectors. Then the vector must have a fixed element type that is
a subtype of `ModelObject`
"""
abstract type ModelObject end

"""
	$(SIGNATURES)

Switches from which `ModelObject` is constructed.
"""
abstract type ModelSwitches end

include("single_id.jl");
include("object_id.jl");
# include("model_switches.jl");
include("m_objects.jl");

end # module


text:
using Documenter
using Escape

makedocs(
    sitename = "Escape.jl",
    format = Documenter.HTML(
        canonical="https://HAMdetector.github.io/MyPkg2.jl",
    ),
    modules = [Escape],
    repo = "github.com/HAMdetector/Escape.jl.git",
    pages = [
        "quick_start.md",
        "advanced_usage.md"
    ],
)

# Documenter can also automatically deploy documentation to gh-pages.
# See "Hosting Documentation" and deploydocs() in the Documenter manual
# for more information.
deploydocs(
    repo = "github.com/HAMdetector/Escape.jl.git"
)


text:
struct Vec{T<:Number} <: AbstractVec{T}
    x::T
    y::T
    z::T
end

@inline Base.@propagate_inbounds Base.getindex(a::AbstractVec,I::Integer) = getfield(a,I)
Base.IndexStyle(a::Type{<:AbstractVec}) = Base.IndexLinear()

@inline xpos(a::AbstractVec) = a.x
@inline ypos(a::AbstractVec) = a.y
@inline zpos(a::AbstractVec) = a.z

@inline LinearAlgebra.norm(a::Vec{<:Real}) = @fastmath sqrt(muladd(xpos(a), xpos(a), muladd(ypos(a), ypos(a), zpos(a)^2)))
@inline LinearAlgebra.norm(a::Vec) = @fastmath sqrt(abs2(a.x)+abs2(a.y)+abs2(a.z))

@inline Base.:+(a::AbstractVec,b::AbstractVec) = Vec(xpos(a)+xpos(b), ypos(a)+ypos(b), zpos(a)+zpos(b))
@inline Base.:-(a::AbstractVec,b::AbstractVec) = Vec(xpos(a)-xpos(b), ypos(a)-ypos(b), zpos(a)-zpos(b))
@inline Base.:-(a::AbstractVec) = Vec(-xpos(a),-ypos(a),-zpos(a))
@inline Base.:*(a::Number,b::AbstractVec) = Vec(a*xpos(b), a*ypos(b), a*zpos(b))
@inline Base.:*(b::AbstractVec,a::Number) = Vec(a*xpos(b), a*ypos(b), a*zpos(b))
@inline Base.:/(b::AbstractVec,a::Number) = inv(a)*b

@inline LinearAlgebra.dot(a::AbstractVec,b::AbstractVec) = muladd(xpos(a), xpos(b), muladd(ypos(a), ypos(b), zpos(a)*zpos(b)))

@inline LinearAlgebra.cross(a::AbstractVec,b::AbstractVec) = Vec(ypos(a)*zpos(b) - zpos(a)*ypos(b), zpos(a)*xpos(b) - xpos(a)*zpos(b), xpos(a)*ypos(b) - ypos(a)*xpos(b))

@inline anglecos(a::Vec,b::Vec) = LinearAlgebra.dot(a,b)/(LinearAlgebra.norm(a)*LinearAlgebra.norm(b))

@inline Base.size(a::Vec) = (3,)
@inline Base.length(a::Vec) = 3
@inline Base.zero(a::Type{Vec{T}}) where {T} = Vec{T}(zero(T),zero(T),zero(T))
@inline Vec(x::T,y::T,z::T) where T = Vec{T}(x,y,z)
@inline Vec(x,y,z) = Vec(promote(x,y,z)...)

abstract type AbstractVecArray{T,N} <: AbstractArray{Vec{T},N} end

@inline Base.@propagate_inbounds function Base.getindex(v::AbstractVecArray{T,N},i::Int) where {T,N}
    x = xvec(v)
    y = yvec(v)
    z = zvec(v)
    xv = x[i]
    yv = y[i]
    zv = z[i]
    return Vec{T}(xv,yv,zv)
end

@inline Base.@propagate_inbounds function Base.getindex(v::AbstractVecArray{T,N},I::Vararg{Int,N}) where {T,N}
    x = xvec(v)
    y = yvec(v)
    z = zvec(v)
    xv = x[I...]
    yv = y[I...]
    zv = z[I...]
    return Vec{T}(xv,yv,zv)
end

@inline Base.@propagate_inbounds function Base.setindex!(v::AbstractVecArray,vec,i::Int)
    x = xvec(v)
    y = yvec(v)
    z = zvec(v)
    setindex!(x,getfield(vec,1),i)
    setindex!(y,getfield(vec,2),i)
    setindex!(z,getfield(vec,3),i)
    return v
end

@inline Base.@propagate_inbounds function Base.setindex!(v::AbstractVecArray{T,N},vec,I::Vararg{Int,N}) where {T,N}
    x = xvec(v)
    y = yvec(v)
    z = zvec(v)
    setindex!(x,getfield(vec,1),I...)
    setindex!(y,getfield(vec,2),I...)
    setindex!(z,getfield(vec,3),I...)
    return v
end

@inline Base.size(v::AbstractVecArray) =
    size(xvec(v))

struct VecArray{T,N,A<:AbstractArray{T,N},B<:AbstractArray{T,N},C<:AbstractArray{T,N}} <: AbstractVecArray{T,N}
    x::A
    y::B
    z::C
    function VecArray{T,N,A,B,C}(x::A,y::B,z::C) where {T,N,A,B,C}
        @assert (size(x) == size(y) == size(z))
        return new{T,N,A,B,C}(x,y,z)
    end
end

@generated function Base.IndexStyle(::Type{<:VecArray{T,N,A,B,C}}) where {T,N,A,B,C}
    indexstyle = all(x->(IndexStyle(x)===IndexLinear()),(A,B,C)) ? IndexLinear() : IndexCartesian()
    return :($indexstyle)
end

@inline VecArray(x::AbstractArray{T,N},y::B,z::C) where{T,N,B<:AbstractArray{T,N},C<:AbstractArray{T,N}} = VecArray{T,N,typeof(x),B,C}(x,y,z)
@inline VecArray{T}(dims::Vararg{Int,N}) where {T,N} = VecArray(zeros(T,dims...),zeros(T,dims...),zeros(T,dims...))
@inline VecArray(dims::Vararg{Int,N}) where {N} = VecArray{Float64}(dims...)

Base.read!(io::NTuple{3,A},a::VecArray) where{A<:Union{<:IO,<:AbstractString}} = (read!(io[1],a.x); read!(io[2],a.y); read!(io[3],a.z))
Base.write(io::NTuple{3,A},a::VecArray) where{A<:Union{<:IO,<:AbstractString}} = (write(io[1],a.x); write(io[2],a.y); write(io[3],a.z))

@inline xvec(v::VecArray) =
    v.x
@inline yvec(v::VecArray) =
    v.y
@inline zvec(v::VecArray) =
    v.z

Base.similar(a::VecArray) = VecArray(similar(a.x),similar(a.y),similar(a.z))


text:
# Declare a new `isfinite` function to avoid type piracy. This new function works with
# `Char` and `Period` as well as other types.
isfinite(x) = iszero(x - x)
isfinite(x::Real) = Base.isfinite(x)


text:
module Laby

##########################################################################

module ExternalInput
export extract_image_from_file, extract_text_from_file
using FileIO, ColorTypes

function missing_file(fileName::String)
	println("""The file \""""*fileName*"""\" is missing.""")
	exit()
end

function extract_image_from_file(fileName::String)
	if !isfile(fileName)
		missing_file(fileName)
	else
		image = RGBA.(load(fileName))
		(r, g, b, a) = (red.(image), green.(image), blue.(image), alpha.(image))
		image = permutedims(cat(r, g, b, a; dims = 3), [3, 1, 2])
		image = round.(UInt8, 255*image)
		return image::Array{UInt8, 3}
	end
end

function extract_text_from_file(fileName::String)
	if !isfile(fileName)
		missing_file(fileName)
	else
		return read(fileName, String)::String
	end
end

end # ExternalInput

##########################################################################
##########################################################################

module LabyCore
export act!
using ..ExternalInput

struct Graphics
	static::Dict{String, Array{UInt8, 3}}
	animationFrames::Dict{String, Array{UInt8, 3}}
	animation::Dict{String, Array{String, 1}}
end

struct Cosmos
	cellSize::Int
	width::Int
	height::Int
end

mutable struct Knuffis
	x::Int
	y::Int
	orientation::String
	finished::Bool
end

mutable struct Cell
	x::Int
	y::Int
	nature::String
	id::String
	passable::Bool
	top::Array{String, 1}
	middle::Array{String, 1}
	bottom::Array{String, 1}
	topAniStack::Array{String, 1}
	middleAniStack::Array{String, 1}
	bottomAniStack::Array{String, 1}
	changed::Bool
end

mutable struct World
	knuffis::Knuffis
	cells::Array{Cell, 2}
	addresses::Dict{String, NamedTuple}
	completed::Bool
end

function load_graphics()

	statics = [

	"knuffis_north";
	"knuffis_east";
	"knuffis_south";
	"knuffis_west";

	"wall";

	"corridor_0000";
	"corridor_0001";
	"corridor_0010";
	"corridor_0011";
	"corridor_0100";
	"corridor_0101";
	"corridor_0110";
	"corridor_0111";
	"corridor_1000";
	"corridor_1001";
	"corridor_1010";
	"corridor_1011";
	"corridor_1100";
	"corridor_1101";
	"corridor_1110";

	"port_NS_closed";
	"port_NS_open";
	"port_EW_closed";
	"port_EW_open";

	"closer_unpressed";
	"closer_pressed";
	"opener_unpressed";
	"opener_pressed";

	"blue";
	"cyan";
	"green";
	"yellow";
	"red";
	"purple";

	"goal"

	]

	imgStatics = extract_image_from_file.("graphics/static/".*statics.*".png")

	animationFrames = [

	"knuffis_north_a1f1"; # 1
	"knuffis_north_a1f2"; # 2
	"knuffis_north_a1f3"; # 3

	"knuffis_east_a1f1"; # 4
	"knuffis_east_a1f2"; # 5
	"knuffis_east_a1f3"; # 6

	"knuffis_south_a1f1"; # 7
	"knuffis_south_a1f2"; # 8
	"knuffis_south_a1f3"; # 9

	"knuffis_west_a1f1"; # 10
	"knuffis_west_a1f2"; # 11
	"knuffis_west_a1f3"; # 12

	"port_NS_a1f1"; # 13
	"port_NS_a1f2"; # 14
	"port_NS_a1f3"; # 15
	"port_NS_a1f4"; # 16
	"port_NS_a1f5"; # 17
	"port_NS_a1f6"; # 18
	"port_NS_a1f7"; # 19

	"port_EW_a1f1"; # 20
	"port_EW_a1f2"; # 21
	"port_EW_a1f3"; # 22
	"port_EW_a1f4"; # 23
	"port_EW_a1f5"; # 24
	"port_EW_a1f6"; # 25
	"port_EW_a1f7"; # 26

	]

	imgAnimationFrames = extract_image_from_file.("graphics/animation/".*animationFrames.*".png")

	animation = Dict(
	"knuffis_north_finished" => animationFrames[[2; 3; 2; 1; 2; 3; 2]],
	"knuffis_east_finished" => animationFrames[[5; 6; 5; 4; 5; 6; 5]],
	"knuffis_south_finished" => animationFrames[[8; 9; 8; 7; 8; 9; 8]],
	"knuffis_west_finished" => animationFrames[[11; 12; 11; 10; 11; 12; 11]],
	"port_NS_opening" => animationFrames[13:19],
	"port_EW_opening" => animationFrames[20:26],
	"port_NS_closing" => reverse(animationFrames[13:19]),
	"port_EW_closing" => reverse(animationFrames[20:26]))

	graphics = Graphics(Dict(statics .=> imgStatics), Dict(animationFrames .=> imgAnimationFrames), animation)

	return graphics::Graphics

end

function big_bang(fileName::String)

	function create_cosmos(scripture::Array{String, 1})
		cellSize = 50
		width = parse(Int, split(scripture[1], ",")[2])
		height = parse(Int, split(scripture[1], ",")[1])
		return Cosmos(cellSize, width, height)::Cosmos
	end

	function create_world(scripture::Array{String, 1})

		scripture = scripture[2:end]

		knuffis = Knuffis(0, 0, "", false)
		cells = Array{Cell}(undef, cosmos.height, cosmos.width)
		addresses = Dict{String, NamedTuple}()
		completed = false
		world = World(knuffis, cells, addresses, completed)

		for s in scripture

			y = parse(Int, split(split(s, ":")[1], ",")[1])
			x = parse(Int, split(split(s, ":")[1], ",")[2])

			seen = split(split(split(s, ":")[2], "#")[1], ",")
			unseen = split(split(s, "#")[2], "_")

			if split(seen[2], "_")[1] == "knuffis"
				world.knuffis.x = x
				world.knuffis.y = y
				world.knuffis.orientation = split(seen[2], "_")[2]
			end

			nature = unseen[1]
			id = unseen[2]
			if nature in ["corridor" "opener" "closer" "goal"]
				passable = true
			else
				passable = false
			end
			top = split(seen[1], "+")
			middle = split(seen[2], "+")
			bottom = split(seen[3], "+")
			if nature == "port" && split(bottom[1], "_")[3] == "open"
				passable = true
			end
			topAniStack = String[]
			middleAniStack = String[]
			bottomAniStack = String[]
			changed = true
			world.cells[y, x] = Cell(x, y, nature, id, passable, top, middle, bottom, topAniStack, middleAniStack, bottomAniStack, changed)

			if !isempty(world.cells[y, x].id)
				world.addresses[world.cells[y, x].nature*"."*world.cells[y, x].id] = (x = x, y = y)
			end

		end

		return world::World

	end

	scripture = extract_text_from_file(fileName)

	scripture = replace(scripture, "\r\n" => "\n")
	scripture = replace(scripture, "\r" => "\n")
	scripture = replace(scripture, "\t" => "")
	scripture = replace(scripture, " " => "")
	scripture = String.(split(scripture, "\n"; keepempty = false))

	cosmos = create_cosmos(scripture)
	world = create_world(scripture)

	return cosmos::Cosmos, world::World

end

function physics(world::World, action::String, nextFrame::Bool)

	function next_frame(aniStack::Array{String, 1})

		if isempty(aniStack) || length(aniStack) == 1
			return String[]::Array{String, 1}
		end

		if length(aniStack) > 1
			return aniStack[2:end]::Array{String, 1}
		end

	end

	world = deepcopy(world)

	action = split(action, "_");

	if action[1] == "moveKnuffis"

		world.completed = world.knuffis.finished

		cell = world.cells[world.knuffis.y, world.knuffis.x]
		cell.middle = [""]
		cell.changed = true

		cell.bottom = replace.(cell.bottom, "pressed" => "unpressed")

		world.knuffis.orientation = action[2]

		if action[2] == "north" && world.knuffis.y > 1 && world.cells[world.knuffis.y-1, world.knuffis.x].passable
			world.knuffis.y += -1
		elseif action[2] == "east" && world.knuffis.x < cosmos.width && world.cells[world.knuffis.y, world.knuffis.x+1].passable
			world.knuffis.x += 1
		elseif action[2] == "south" && world.knuffis.y < cosmos.height && world.cells[world.knuffis.y+1, world.knuffis.x].passable
			world.knuffis.y += 1
		elseif action[2] == "west" && world.knuffis.x > 1 && world.cells[world.knuffis.y, world.knuffis.x-1].passable
			world.knuffis.x += -1
		end

		cell = world.cells[world.knuffis.y, world.knuffis.x]
		cell.middle = ["knuffis_"*world.knuffis.orientation]
		cell.changed = true

		cell.bottom = replace.(cell.bottom, "unpressed" => "pressed")

		if cell.nature in ["opener" "closer"]

			x = world.addresses["port."*cell.id].x
			y = world.addresses["port."*cell.id].y

			response = Dict(
			"opener" => (true, "closed" => "opening", "closed" => "open"),
			"closer" => (false, "open" => "closing", "open" => "closed"))

			if response[cell.nature][1] != world.cells[y, x].passable
				world.cells[y, x].passable = response[cell.nature][1]
				world.cells[y, x].bottomAniStack = GRAPHICS.animation[replace(world.cells[y, x].bottom[1], response[cell.nature][2])]
				world.cells[y, x].bottom = replace.(world.cells[y, x].bottom, response[cell.nature][3])
				world.cells[y, x].changed = true
			end

		elseif cell.nature == "goal"
			cell.middleAniStack = GRAPHICS.animation["knuffis_"*world.knuffis.orientation*"_finished"]
			world.knuffis.finished = true
		end

	end

	if nextFrame
		for cell in world.cells
			cell.bottomAniStack = next_frame(cell.bottomAniStack)
			cell.middleAniStack = next_frame(cell.middleAniStack)
			cell.topAniStack = next_frame(cell.topAniStack)
		end
	end

	return world::World

end

function render(world::World, image::Array{UInt8, 3})

	function overlay(images::Array{Array{UInt8, 3}, 1})
		subBuffer = images[1][1:3, :, :]
		for i in 2:length(images)
			subBuffer[1, :, :] = round.(UInt8, subBuffer[1, :, :] + (Int16.(images[i][1, :, :]) - subBuffer[1, :, :]) .* (images[i][4, :, :] / 255))
			subBuffer[2, :, :] = round.(UInt8, subBuffer[2, :, :] + (Int16.(images[i][2, :, :]) - subBuffer[2, :, :]) .* (images[i][4, :, :] / 255))
			subBuffer[3, :, :] = round.(UInt8, subBuffer[3, :, :] + (Int16.(images[i][3, :, :]) - subBuffer[3, :, :]) .* (images[i][4, :, :] / 255))
		end
		return subBuffer::Array{UInt8, 3}
	end

	world = deepcopy(world)
	image = deepcopy(image)

	animationStackSize = 0

	for cell in world.cells

		animationStackSize = maximum([animationStackSize; length.([cell.bottomAniStack, cell.middleAniStack, cell.topAniStack])])

		if cell.changed

			cell.changed = false

			xStart = (cell.x-1)*cosmos.cellSize+1
			xEnd = cell.x*cosmos.cellSize
			yStart = (cell.y-1)*cosmos.cellSize+1
			yEnd = cell.y*cosmos.cellSize

			cellImageStack = Array{Array{UInt8, 3}, 1}(undef, 0)
			strsA = [cell.bottomAniStack, cell.middleAniStack, cell.topAniStack]
			strsS = [cell.bottom, cell.middle, cell.top]
			for i in 1:3
				if !isempty(strsA[i])
					cellImageStack = [cellImageStack; [GRAPHICS.animationFrames[strsA[i][1]]]]
				elseif strsS[i] != [""]
					for str in strsS[i]
						cellImageStack = [cellImageStack; [GRAPHICS.static[str]]]
					end
				end
			end

			cellImage = overlay(cellImageStack)

			image[:, yStart:yEnd, xStart:xEnd] = cellImage

		end

	end

	return image::Array{UInt8, 3}, animationStackSize::Int

end

function act!(action::String, nextFrame::Bool)

	if split(action, "_")[1] == "bigBang"
		global cosmos, world = big_bang(String(split(action, "_")[2]))
		global image = UInt8.(zeros(3, cosmos.height*cosmos.cellSize, cosmos.width*cosmos.cellSize))
		action = ""
	end

	global world = physics(world, action, nextFrame)
	animationStackSize = 0
	let (i, a) = render(world, image)
		global image = i
		animationStackSize = a
	end

	return image::Array{UInt8, 3}, animationStackSize::Int, world.completed::Bool

end

global const GRAPHICS = load_graphics()

global cosmos
global world
global image

end # LabyCore

##########################################################################
##########################################################################

module UserInterface
using ..LabyCore, Gtk

mutable struct Level
	n::Int
	complete::Bool
	animationStackSize::Int
	animating::Bool
end

function make_gtk_image(imgRGB::Array{UInt8, 3})
	data = Array{Gtk.RGB}(undef, size(imgRGB)[3], size(imgRGB)[2])
	for y in 1:size(imgRGB)[2], x in 1:size(imgRGB)[3]
		data[x, y] = Gtk.RGB((imgRGB[1, y, x]), (imgRGB[2, y, x]), (imgRGB[3, y, x]))
	end
	pixbuf = GdkPixbuf(data = data, has_alpha = false)
	return GtkImage(pixbuf)::GtkImageLeaf
end

function initialize_level!(resetting::Bool)

	if level.n == 0
		global level.n = 1
	elseif resetting
		println("Resetting level "*string(level.n)*"!")
		delete!(window, window[1])
	else
		println("Level "*string(level.n)*" completed!")
		delete!(window, window[1])
		global level.n += 1
	end

	filePath = "levels/"*string(level.n, pad = 3)*".world"
	if isfile(filePath)
		global image, level.animationStackSize, level.complete = act!("bigBang_"*filePath, false)
	else
		print("Done!\n")
		exit()
	end

	global image = make_gtk_image(image)
	push!(window, image)
	showall(window)

	return nothing

end

function key_pressed!(window::GtkWindowLeaf, event::Gtk.GdkEventKey)

	if !blocked

		global blocked = true

		if haskey(ACTIONS, event.keyval)
			action = ACTIONS[event.keyval]
		elseif event.keyval == 114 # r
			initialize_level!(true)
		elseif event.keyval == 65307 # escape
			exit()
		end

		global image, level.animationStackSize, level.complete = act!(action, false)
		if level.complete
			initialize_level!(false)
		end
		global image = make_gtk_image(image)
		redraw!()

		run_animation! = @task begin
			global level.animating = true
			sleep(0.1)
			while level.animationStackSize > 0
				animation_action! = @task begin
					global image, level.animationStackSize, level.complete = act!("", true)
					global image = make_gtk_image(image)
					redraw!()
				end
				schedule(animation_action!)
				sleep(0.1)
				wait(animation_action!)
			end
			global level.animating = false
		end

		if !level.animating
			schedule(run_animation!)
		end

	end

	return nothing

end

function key_released!(window::GtkWindowLeaf, event::Gtk.GdkEventKey)
	global blocked = false
	return nothing
end

function escaped(window::GtkWindowLeaf)
	exit()
end

function redraw!()
	newPixbuf = get_gtk_property(image, "pixbuf", GdkPixbuf)
	set_gtk_property!(window[1], "pixbuf", newPixbuf)
	reveal(window[1], true)
	return nothing
end

global window = GtkWindow("Laby")
set_gtk_property!(window, "resizable", false)
global image

global level = Level(0, false, 0, false)

initialize_level!(false)

global blocked = false
signal_connect(key_pressed!, window, "key-press-event")
signal_connect(key_released!, window, "key-release-event")
global const ACTIONS = Dict(
65362 => "moveKnuffis_north", # up-arrow
65363 => "moveKnuffis_east", # right-arrow
65364 => "moveKnuffis_south", # down-arrow
65361 => "moveKnuffis_west") # left-arrow

signal_connect(escaped, window, "destroy")

println("\nUse arrow keys to move around.\nPress r to restart level.\nPress escape to quit.\n")

Gtk.gtk_main()

end # UserInterface

##########################################################################

end # Laby

max_stars_repo_name: JonPallbo/Laby

max_issues_repo_name: JonPallbo/Laby

max_forks_repo_name: JonPallbo/Laby


text:
module LsqFit

    export curve_fit,
           margin_error,
           confidence_interval,
           estimate_covar,
           make_hessian,
           Avv,
           # StatsBase reexports
           dof, coef, nobs, mse, rss,
           stderror, weights, residuals

    using Distributions
    using OptimBase
    using LinearAlgebra
    using ForwardDiff
    import NLSolversBase: value, jacobian
    import StatsBase
    import StatsBase: coef, dof, nobs, rss, stderror, weights, residuals

    import Base.summary

    include("geodesic.jl")
    include("levenberg_marquardt.jl")
    include("curve_fit.jl")

end


text:
# Data entry point functions
read_data = nothing

@datafunc function main1(x::Blob=>String, t::BlobTree=>BlobTree)
    csv_data = open(IO, t["1.csv"]) do io
        read(io,String)
    end
    global read_data = (x_string=x, csv_data=csv_data)
end

@datafunc function main1(x::Blob=>IO)
    x_data = read(x, String)
    global read_data = (x_data=x_data,)
end

@testset "@datafunc and @datarun" begin
    proj = DataSets.load_project("Data.toml")

    @datarun proj main1("a_text_file", "a_tree_example")

    @test read_data == (x_string="Hello world!\n",
                        csv_data="Name,Age\n\"Aaron\",23\n\"Harry\",42\n")

    @datarun proj main1("a_text_file")
    @test read_data == (x_data="Hello world!\n",)

    # No match for a single tree
    @test_throws ArgumentError @datarun proj main1("a_tree_example")
end

@testset "@datarun with DataSet.PROJECT" begin
    empty!(DataSets.PROJECT)
    DataSets.load_project!("Data.toml")

    @test dataset("a_text_file").uuid == UUID("b498f769-a7f6-4f67-8d74-40b770398f26")

    global read_data = nothing
    @datarun main1("a_text_file")
    @test read_data == (x_data="Hello world!\n",)
end


text:
module test_jive_skip_modules

using Jive # Skipped @skip
using Test # @test

empty!(Jive.Skipped.expressions)

@skip module want_to_skip
sleep(3)
print(:dont_print_it)
end

@test !isdefined(@__MODULE__, :want_to_skip)
@test Jive.Skipped.expressions == [:module=>:want_to_skip]

ENV["JIVE_SKIP"] = "0"

@skip module non_skip
end

@test isdefined(@__MODULE__, :non_skip)
@test Jive.Skipped.expressions == [:module=>:want_to_skip]

ENV["JIVE_SKIP"] = "1"

end # module test_jive_skip_modules