Creating Custom Extensions in Toolips.jl: A Comprehensive Guide
Written on
Introduction to Toolips.jl Extensions
This guide provides an insight into the development of extensions for Toolips.jl. As of version 0.1.0, Toolips is in the Unstable phase, awaiting finalization of documentation and server tests. Toolips has seen significant advancements since its inception, largely due to its focus on extensibility. At its foundation, Toolips embraces a functional design pattern. For a deeper understanding of this approach, refer to the article discussing a functional core in JavaScript.
The functional core allows for high mutability because it revolves around functions instead of data. Functions can modify data without directly altering it, presenting a unique and intriguing method for a web-development framework, particularly within Julia's closure paradigm. This strategy has proven to be effective. In Toolips, every server operation, from logging to serving public files, can be added as an optional extension. If your project doesn't require a logger, there's no need to include that extension. Similarly, if serving a directory of files isn't necessary, you can skip that too. This flexibility is crucial since web projects in languages like Julia can differ vastly, as seen in Python's varied frameworks.
Comparing Frameworks: Python Examples
In Python, two prominent frameworks dominate the web-development landscape: Flask and Django. Flask is a lightweight option suitable for web applications, though it can be complex for extensive projects. Django, on the other hand, is typically used for building full websites rather than APIs, leading to a split in learning for Python developers who wish to do both. This dichotomy can create issues with dependencies and functionality, leaving developers with either excess or insufficient tools tailored to their specific project needs.
The closure-based architecture of Toolips.jl addresses these challenges by making web-development capabilities easily extendable. Toolips can serve simple tasks like providing an API for a model's predictions or support full-stack applications, as demonstrated by the Pasta.jl text editor example.
Creating Your Own Extensions
Today, we’ll delve into the process of building custom extensions for Toolips. Surprisingly, it's quite straightforward to implement a lot of functionality with minimal code! If you're eager to explore Toolips further, visit our GitHub repository: Our Project.
We will start by creating a project named ToolipsDefaults.jl. This will offer a default set of indexable styles and the ability to generate a stylesheet automatically on selected routes. First, we’ll generate the project using the command:
using Pkg; Pkg.generate("ToolipsDefaults")
Our new ToolipsDefaults/src/ToolipsDefaults.jl file will now serve as a basic module:
module ToolipsDefaults
greet() = print("Hello World!")
end
Next, we need to handle our imports. We'll directly import the Servable abstract type since we're developing a Servable extension, along with the Toolips module:
using Toolips
import Toolips: Servable
Structuring the Stylesheet
Before we begin creating our stylesheet structure, we must determine the required fields. To investigate any abstract type in Toolips, we can access its documentation:
help?> Servable
The essential consistency for a Servable is its f() function. While there exists a lower hierarchical step called StyleComponent, it does not align with our new StyleSheet Servable. Therefore, we will focus on the f consistency in our constructor:
mutable struct StyleSheet
f::Function
end
What additional components should our StyleSheet include? The beauty of Servable extensions lies in how f functions resemble typical routing functions. For instance:
rt = route("/") do c::Connection
write!(c, "hello world!")
end
An f function, however, is structured like this:
f(c::Connection) = begin
write!(c, "hello world!")
end
Given that both functions accept similar arguments, we can create Servable extensions as preset compositions of components. Thus, we will include a vector of Servables in our type:
mutable struct StyleSheet
f::Function
comps::Vector{Servable}
end
Next, we’ll build an inner constructor to initialize our StyleSheet:
mutable struct StyleSheet
f::Function
comps::Vector{Servable}
function StyleSheet()
end
end
Now, let's define our f function using the syntax discussed earlier:
mutable struct StyleSheet
f::Function
comps::Vector{Servable}
function StyleSheet(comps::Vector{Servable})
f(c::AbstractConnection) = begin
write!(c, comps)end
end
end
Setting Default Styles
Our next step involves establishing default styles for our StyleSheet. We can create individual methods for each style, returning a single Servable:
function da_div()
end
function ds_div()
end
function ds_p()
end
function ds_a()
end
function ds_button()
end
function ds_li()
end
function ds_ul()
end
function ds_h1()
end
function ds_h2()
end
function ds_h3()
end
While the complete implementation of these functions isn’t necessary here, their outputs will be single Servables, which we will incorporate into the StyleSheet constructor:
mutable struct StyleSheet
f::Function
comps::Vector{Servable}
function StyleSheet(comps::Vector{Servable} = [
ds_div(), ds_p(), ds_a(), ds_button(), ds_li(), ds_ul(), ds_h1(),
ds_h2(), ds_h3()
])
f(c::AbstractConnection) = begin
write!(c, comps)end
end
end
Sub-typing as a Servable
Next, we will sub-type this as a Servable and return our StyleSheet using the inner constructor:
mutable struct StyleSheet <: Servable
f::Function
comps::Vector{Servable}
function StyleSheet(comps::Vector{Servable} = [
ds_div(), ds_p(), ds_a(), ds_button(), ds_li(), ds_ul(), ds_h1(),
ds_h2(), ds_h3()
])
f(c::AbstractConnection) = begin
write!(c, comps)end
new(f, comps)::StyleSheet
end
end
Implementing a ServerExtension
Now, we’ll create a simple ServerExtension that will automatically write the stylesheet to predefined routes. Let's start by examining the consistencies of the ServerExtension abstract type:
help?> ServerExtension
Server extensions are loaded at server startup and can possess different capabilities based on the value of their type field, which can be a Symbol or a Vector of Symbols. For our needs, we want a function that runs before each routing, so we will use :func:
mutable struct StyleSheetLoader
type::Symbol
f::Function
stylesheet::StyleSheet
active_routes::Vector{String}
function StyleSheetLoader(active_routes::Vector{String} = ["/"],
stylesheet = StyleSheet())new(:func, f, stylesheet, active_routes)
end
end
This leads us to write an f function similar to the previous example, where we utilize the write! function to render our stylesheet:
mutable struct StyleSheetLoader <: ServerExtension
type::Symbol
f::Function
stylesheet::StyleSheet
active_routes::Vector{String}
function StyleSheetLoader(active_routes::Vector{String} = ["/"],
stylesheet = StyleSheet())f(c::Connection) = begin
write!(c, stylesheet)end
new(:func, f, stylesheet, active_routes)
end
end
Conclusion: Embracing Minimalism in Development
We have now successfully created a new StyleSheet servable that can be utilized with Toolips, along with a straightforward extension that applies our stylesheet to predetermined routes. This guide serves as a resource for those looking to develop customized structures and Servables within Toolips. Such techniques can greatly enhance your projects, especially when certain functionalities may be lacking.
I appreciate your reading this guide, and I hope it motivates you to explore creating your own extensions in Toolips!