creating extensions
There are both Connection and Server extensions. Here is how to make them.
creating connection extensions
Toolips.AbstractConnection
— Typeabstract type AbstractConnection
Connections are passed through function routes and can have Servables written to it.
Consistencies
- routes::Dict - A {String, Function} dictionary that the server references to
direct incoming connections.
- http::Any - Usually an HTTP.Stream, however can be anything that is binded to
the Base.write method.
- extensions::Dict - A {Symbol, ServerExtension} dictionary that can be used to
access ServerExtensions.
Abstract Connections must have the extensions Dict, the routing Dict, and some sort of writable stream called http. This needs to be binded to Base.write. A good example of this is Toolips.SpoofStream and Toolips.SpoofConnection, which can be used to write connection output to a string.
mutable struct SpoofStream
text::String
SpoofStream() = new("")
end
The http value can be anything, so in this case it will be a SpoofStream. The SpoofStream contains only a string, text. This is then binded to the write method:
write(s::SpoofStream, e::Any) = s.text = s.text * string(e)
write(c::SpoofStream, s::Servable) = s.f(c)
Finally, we make our connection, using SpoofStream as HTTP.
mutable struct SpoofConnection <: AbstractConnection
routes::Dict
http::SpoofStream
extensions::Dict
function SpoofConnection(r::Dict, http::SpoofStream, extensions::Dict)
new(r, SpoofStream(), extensions)
end
SpoofConnection() = new(Dict(), SpoofStream(), Dict())
end
creating server extensions
Toolips.ServerExtension
— Typeabstract type ServerExtension
Server extensions are loaded into the server on startup, and can have a few different abilities according to their type field's value. This value can be either a Symbol or a Vector of Symbols.
Consistencies
- type::T where T == Vector{Symbol} || T == Symbol. The type can be :routing,
:func, :connection, or any combination inside of a Vector{Symbol}. :routing ServerExtensions must have an f() function that takes two dictionaries; e.g. f(r::Dict{String, Function}, e::Dict{Symbol, ServerExtension}) The first Dict is the dictionary of routes, the second is the dictionary of server extensions. :func server extensions will be ran everytime the server is routed. They will need to have the same f function, but taking a single argument as a connection. Lastly, :connection extensions are simply pushed to the connection.
Server extensions are a little bit more intense. There are three types of server extensions, :func, :routing, and :connection. The type field can be either a Vector{Symbol}, or a single symbol – and a combination of each of these can be written. A :func extension is one that holds a function that is ran every time a Connection is routed. A :func extension requires that the function f(::AbstractConnection) or f(::Connection) exists inside of it. Here is an example:
import Toolips: ServerExtension
mutable struct MyExtension <: ServerExtension
f::Function
function MyExtension()
f(c::Connection) = begin
write!(c, "Hello!")
end
end
end
Each time the server is routed, there will now be "Hello!" written to the top of the page. A :routing extension is similar, but we will want to have the f function instead take two dictionaries. The dictionaries are specifically of type Dict{String, Function}, and Dict{Symbol, ServerExtension}. A great example of this is the Toolips.Files extension:
mutable struct Files <: ServerExtension
type::Symbol
directory::String
f::Function
function Files(directory::String = "public")
f(r::Dict, e::Dict) = begin
l = length(directory) + 1
for path in route_from_dir(directory)
push!(r, path[l:length(path)] => c::Connection -> write!(c, File(path)))
end
end
new(:routing, directory, f)
end
end
Finally, there is also a :connection extension. These are extensions that are to be pushed into the Connection's extensions field. Nothing extra needs to be done to these types of extensions. A great example of this is the Toolips.Logger:
mutable struct Logger <: ServerExtension
type::Symbol
out::String
levels::Dict
log::Function
prefix::String
timeformat::String
writeat::Int64
function Logger(levels::Dict{Any, Crayon} = Dict(
1 => Crayon(foreground = :light_cyan),
2 => Crayon(foreground = :light_yellow),
3 => Crayon(foreground = :yellow, bold = true),
4 => Crayon(foreground = :red, bold = true),
:time_crayon => Crayon(foreground = :magenta, bold = true),
:message_crayon => Crayon(foreground = :light_blue, bold = true)
);
out::String = pwd() * "/logs/log.txt", prefix::String = "🌷 toolips> ",
timeformat::String = "YYYY:mm:dd:HH:MM", writeat::Int64 = 2)
log(level::Int64, message::String) = _log(level, message, levels, out,
prefix, timeformat, writeat)
log(message::String) = _log(1, message, levels, out, prefix, timeformat,
writeat)
log(c::Connection, message::String) = _log(c, message)
# These bindings are left open-ended for extending via
# import Toolips._log
log(level::Int64, message::Any) = _log(level, a, levels, out, prefix,
timeformat)
new(:connection, out::String, levels::Dict, log::Function,
prefix::String, timeformat::String, writeat::Int64)::Logger
end
end
toolips internals
If you're looking at the internals, you are probably good enough at reading documentation... Here are the doc-strings, my friend. Thank you for contributing.
Base.write
— MethodInternals
write(s::SpoofStream, e::Any) -> _
A binding to Base.write that allows one to write to SpoofStream.text.
example
s = SpoofStream()
write(s, "hi")
println(s.text)
hi
Base.write
— MethodInternals
write(s::SpoofStream, e::Servable) -> _
A binding to Base.write that allows one to write a Servable to SpoofStream.text.
example
s = SpoofStream()
write(s, p("hello"))
println(s.text)
<p id = "hello"></p>
Toolips.create_serverdeps
— FunctionInternals
create_serverdeps(name::String, inc::String) -> _
Creates the essential portions of the webapp file structure, where name is the project's name and inc is any extensions or strings to incorporate at the top of the file.
example
create_serverdeps("ToolipsApp")
Toolips.serverfuncdefs
— FunctionCore
serverfuncdefs(::AbstractVector, ::String, ::Integer,
::Dict) -> (::Function, ::Function, ::Function)
This method is used internally by a constructor to generate the functions add, start, and remove for the ServerTemplate.
example
Toolips._start
— FunctionCore - Internals
_start(routes::AbstractVector, ip::String, port::Integer,
extensions::Dict, c::Type) -> ::WebServer
This is an internal function for the ServerTemplate. This function is binded to the ServerTemplate.start field.
example
st = ServerTemplate()
st.start()
Toolips.generate_router
— FunctionCore - Internals
generate_router(routes::AbstractVector, server::Any, extensions::Dict,
conn::Type)
This method is used internally by the _start method. It returns a closure function that both routes and calls functions.
example
server = Sockets.listen(Sockets.InetAddr(parse(IPAddr, ip), port))
if has_extension(extensions, Logger)
extensions[Logger].log(1,
"Toolips Server starting on port " * string(port))
end
routefunc, rdct, extensions = generate_router(routes, server, extensions,
Connection)
@async HTTP.listen(routefunc, ip, port, server = server)
Toolips._log
— FunctionExtensions
_log(level::Int64, message::String, levels::Dict, out::String) -> _
Binded call for the field log() inside of Logger(). See ?(Logger) for more details on the field log. All arguments are fields of that type. Return is a printout into the REPL as well as an append to the log file, provided by the out URI. –––––––––
example (Closure from Logger)
log(level::Int64, message::String) = _log(level, message, levels, out)
log(message::String) = _log(1, message, levels, out)
Extensions
_log(http::HTTP.Stream, message::String) -> _
Binded call for the field log() inside of Logger(). This will log both to the JavaScript/HTML console. –––––––––
example (Closure from Logger)
log(http::HTTP.Stream, message::String) = _log(http, message)
Base.string
— FunctionInternals
string(r::Vector{UInt8}) -> ::String
Turns a vector of UInt8s into a string.
Interface
string(c::Component) -> ::String
Shows c as a string representation of itself.
example
c = divider("example", align = "center")
string(c)
"divider: align = center"
Toolips.SpoofConnection
— TypeSpoofConnection <: AbstractConnection
- routes::Dict
- http::SpoofStream
- extensions::Dict Builds a fake connection with a SpoofStream. Useful if you want to write
a Servable without a server.
example
fakec = SpoofConnection()
servable = Component()
# write!(::AbstractConnection, ::Servable):
write!(fakec, servable)
field info
- routes::Dict - A dictionary of routes, usually left empty.
- http::SpoofStream - A fake http stream that instead writes output to a string.
- extensions::Dict - A dictionary of extensions, usually empty.
constructors
- SpoofStream(r::Dict, http::SpoofStream, extensions::Dict)
- SpoofStream()
Toolips.SpoofStream
— TypeSpoofStream
- text::String The SpoofStream allows us to fake a connection by building a SpoofConnection
which will write to the SpoofStream.text field whenever write! is called. This is useful for testing, or just writing servables into a string.
example
stream = SpoofStream()
write(stream, "hello!")
println(stream.text)
hello!
conn = SpoofConnection()
servab = Component()
write!(conn, servab)
field info
- text::String - The text written to the stream.
constructors
- SpoofStream()
Toolips.route_from_dir
— FunctionExtensions
routefromdir(dir::String) -> ::Vector{String}
Recursively appends filenames for a directory AND all subsequent directories.
example
x::Vector{String} = route_from_dir("mypath")
Base.show
— MethodInterface
show(t::Base.TTY, x::Component) -> _
Shows a component as markdown in a terminal.
example
# In the terminal, elsewhere the component will show as HTML.
show(x)
Base.show
— MethodInterface
show(x::Component) -> _
Shows a component as HTML.
example
show(x)
Toolips.show_log
— FunctionExtensions
show_log(level::Int64, message::String, levels::Dict{Any, Crayon},
prefix::String, time::Any)
Prints a log to the screen.
example
show_log(1, "hello!", levels, "toolips> ", now()
[2022:05:23:22:01] toolips> hello!
Toolips.@L_str
— MacroInterface
L_str(s::String) -> ::String
Creates a literal string
example
x = 5
L"dollar_signx" # pretend dollar_sign is a dollar sign.
Toolips.has_extension
— MethodInternals
has_extension(d::Dict, t::Type) -> ::Bool
Checks if d has an extension of type t.
example
if has_extension(d, Logger)
d[:Logger].log("it has a logger, I think.")
end
Toolips.argsplit
— FunctionInternals
argsplit(args::Vector{AbstractString}) -> ::Dict{Symbol, Any}
Used by the getargs method to parse GET arguments into a Dict.
example
argsplit(["c=5", "b=8"])
Dict(:c => 5, :b => 8)
Base.string
— MethodInternals
string(r::Vector{UInt8}) -> ::String
Turns a vector of UInt8s into a string.
Toolips.showchildren
— FunctionInternals
showchildren(x::Component) -> ::String
Get the children of x as a markdown string.
example
c = divider("example")
child = p("mychild")
push!(c, child)
s = showchildren(c)
println(s)
"##### children
|-- mychild