Skip to content

Commit

Permalink
v0.22
Browse files Browse the repository at this point in the history
  • Loading branch information
Adrian committed Nov 24, 2019
1 parent e2d3720 commit 4637f36
Show file tree
Hide file tree
Showing 10 changed files with 244 additions and 60 deletions.
2 changes: 1 addition & 1 deletion Project.toml
Original file line number Diff line number Diff line change
@@ -1,7 +1,7 @@
name = "Genie"
uuid = "c43c736e-a2d1-11e8-161f-af95117fbd1e"
authors = ["Adrian Salceanu <[email protected]>"]
version = "0.21.0"
version = "0.22.0"

[deps]
ArgParse = "c7e460c6-2fb9-53a9-8c5b-16f535851c63"
Expand Down
2 changes: 1 addition & 1 deletion docs/guides/Working_With_Genie_Apps.md
Original file line number Diff line number Diff line change
@@ -1,7 +1,7 @@
# Working with Genie apps (projects)

Working with Genie in an interactive environment can be useful – but usually we want to persist the application and reuse it between sessions.
One way to achieve that is to save it as an IJulia notebook and rerun the cells. However, you can get the best of Genie by working with Genie apps.
One way to achieve this is to save it as an IJulia notebook and rerun the cells. However, you can get the best of Genie by working with Genie apps.
A Genie app is an MVC web application which promotes the convention-over-configuration principle. By working with a few predefined files, within the Genie app structure, the framework can lift a lot of weight and massively improve development productivity. But following Genie's workflow, one instantly gets, out of the box, features like automatic module loading and reloading, dedicated configuration files, logging, support for environments, code generators, caching, support for Genie plugins, and more.

In order to create a new Genie app, all we need to do is run `Genie.newapp($app_name)`:
Expand Down
2 changes: 1 addition & 1 deletion src/Configuration.jl
Original file line number Diff line number Diff line change
Expand Up @@ -3,7 +3,7 @@ Core genie configuration / settings functionality.
"""
module Configuration

const GENIE_VERSION = v"0.21.0"
const GENIE_VERSION = v"0.22.0"

import Logging
import Genie
Expand Down
50 changes: 26 additions & 24 deletions src/Flax.jl
Original file line number Diff line number Diff line change
Expand Up @@ -23,6 +23,9 @@ using .HTMLRenderer
include("JSONRenderer.jl")
using .JSONRenderer

include("JSRenderer.jl")
using .JSRenderer

const Html = @__MODULE__

const BUILD_NAME = "FlaxViews"
Expand Down Expand Up @@ -53,30 +56,6 @@ function partial(path::String; context::Module = @__MODULE__, vars...) :: String
end


"""
parseview(data::String; partial = false, context::Module = @__MODULE__) :: Function
Parses a view file, returning a rendering function. If necessary, the function is JIT-compiled, persisted and loaded into memory.
"""
function parseview(data::String; partial = false, context::Module = @__MODULE__) :: Function
data_hash = hash(data)
path = "Flax_" * string(data_hash)

func_name = function_name(string(data_hash, partial)) |> Symbol
mod_name = m_name(string(path, partial)) * ".jl"
f_path = joinpath(Genie.config.path_build, BUILD_NAME, mod_name)
f_stale = build_is_stale(f_path, f_path)

if f_stale || ! isdefined(context, func_name)
f_stale && build_module(string_to_flax(data, partial = partial), path, mod_name)

return Base.include(context, joinpath(Genie.config.path_build, BUILD_NAME, mod_name))
end

getfield(context, func_name)
end


"""
template(path::String; partial::Bool = true, context::Module = @__MODULE__) :: String
Expand Down Expand Up @@ -442,4 +421,27 @@ function changebuilds(subfolder = BUILD_NAME) :: Bool
preparebuilds()
end


"""
view_file_info(path::String, supported_extensions = SUPPORTED_HTML_OUTPUT_FILE_FORMATS) :: Tuple{String,String}
Extracts path and extension info about a file
"""
function view_file_info(path::String, supported_extensions::Vector{String} = HTMLRenderer.SUPPORTED_HTML_OUTPUT_FILE_FORMATS) :: Tuple{String,String}
_path, _extension = "", ""

if isfile(path)
_path, _extension = relpath(path), "." * split(path, ".", limit = 2)[end]
else
for file_extension in supported_extensions
if isfile(path * file_extension)
_path, _extension = path * file_extension, file_extension
break
end
end
end

_path, _extension
end

end
41 changes: 19 additions & 22 deletions src/HTMLRenderer.jl
Original file line number Diff line number Diff line change
Expand Up @@ -106,10 +106,6 @@ function attributes(attrs::Vector{Pair{Symbol,Any}} = Vector{Pair{Symbol,Any}}()
a = IOBuffer()

for (k,v) in attrs
sk = string(k)
# startswith(sk, "_") && (k = sk = sk[2:end])
# k = replace(sk, "_"=>"-")

print(a, "$(k)=\"$(v)\" ")
end

Expand Down Expand Up @@ -192,7 +188,7 @@ Resolves the inclusion and rendering of a template file
function get_template(path::String; partial::Bool = true, context::Module = @__MODULE__) :: Function
orig_path = path

path, extension = view_file_info(path)
path, extension = Flax.view_file_info(path)

isfile(path) || error("Template file \"$orig_path\" with extensions $SUPPORTED_HTML_OUTPUT_FILE_FORMATS does not exist")

Expand Down Expand Up @@ -240,25 +236,26 @@ end


"""
view_file_info(path::String) :: Tuple{String,String}
parseview(data::String; partial = false, context::Module = @__MODULE__) :: Function
Extracts path and extension info about a file
Parses a view file, returning a rendering function. If necessary, the function is JIT-compiled, persisted and loaded into memory.
"""
function view_file_info(path::String) :: Tuple{String,String}
_path, _extension = "", ""
function parseview(data::String; partial = false, context::Module = @__MODULE__) :: Function
data_hash = hash(data)
path = "Flax_" * string(data_hash)

if isfile(path)
_path, _extension = relpath(path), "." * split(path, ".", limit = 2)[end]
else
for file_extension in SUPPORTED_HTML_OUTPUT_FILE_FORMATS
if isfile(path * file_extension)
_path, _extension = path * file_extension, file_extension
break
end
end
func_name = Flax.function_name(string(data_hash, partial)) |> Symbol
mod_name = Flax.m_name(string(path, partial)) * ".jl"
f_path = joinpath(Genie.config.path_build, Flax.BUILD_NAME, mod_name)
f_stale = Flax.build_is_stale(f_path, f_path)

if f_stale || ! isdefined(context, func_name)
f_stale && Flax.build_module(Flax.string_to_flax(data, partial = partial), path, mod_name)

return Base.include(context, joinpath(Genie.config.path_build, Flax.BUILD_NAME, mod_name))
end

_path, _extension
getfield(context, func_name)
end


Expand All @@ -268,10 +265,10 @@ function render(data::String; context::Module = @__MODULE__, layout::Union{Strin
Flax.registervars(vars...)

if layout !== nothing
task_local_storage(:__yield, Flax.parseview(data, partial = true, context = context))
Flax.parseview(layout, partial = false, context = context)
task_local_storage(:__yield, parseview(data, partial = true, context = context))
parseview(layout, partial = false, context = context)
else
Flax.parseview(data, partial = false, context = context)
parseview(data, partial = false, context = context)
end
end

Expand Down
89 changes: 89 additions & 0 deletions src/JSRenderer.jl
Original file line number Diff line number Diff line change
@@ -0,0 +1,89 @@
module JSRenderer


import Revise
import Logging, FilePaths
using Genie, Genie.Flax


const JS_FILE_EXT = ["js.jl"]
const TEMPLATE_EXT = [".flax.js", ".jl.js"]

const SUPPORTED_JS_OUTPUT_FILE_FORMATS = TEMPLATE_EXT

const JSString = String

const NBSP_REPLACEMENT = ("&nbsp;"=>"!!nbsp;;")

export JSString


"""
"""
function get_template(path::String; context::Module = @__MODULE__) :: Function
orig_path = path

path, extension = Flax.view_file_info(path, SUPPORTED_JS_OUTPUT_FILE_FORMATS)

isfile(path) || error("JS file \"$orig_path\" with extensions $SUPPORTED_JS_OUTPUT_FILE_FORMATS does not exist")

extension in JS_FILE_EXT && return (() -> Base.include(context, path))

f_name = Flax.function_name(path) |> Symbol
mod_name = Flax.m_name(path) * ".jl"
f_path = joinpath(Genie.config.path_build, Flax.BUILD_NAME, mod_name)
f_stale = Flax.build_is_stale(path, f_path)

if f_stale || ! isdefined(context, func_name)
f_stale && Flax.build_module(Flax.to_js(data), path, mod_name)

return Base.include(context, joinpath(Genie.config.path_build, Flax.BUILD_NAME, mod_name))
end

getfield(context, f_name)
end


"""
"""
@inline function to_js(data::String; prepend = "\n") :: String
string("function $(Flax.function_name(data))() \n",
Flax.injectvars(),
prepend,
"\"\"\"$data\"\"\"",
"\nend \n")
end


"""
"""
function render(data::String; context::Module = @__MODULE__, vars...) :: Function
Flax.registervars(vars...)

data_hash = hash(data)
path = "Flax_" * string(data_hash)

func_name = Flax.function_name(string(data_hash)) |> Symbol
mod_name = Flax.m_name(path) * ".jl"
f_path = joinpath(Genie.config.path_build, Flax.BUILD_NAME, mod_name)
f_stale = Flax.build_is_stale(f_path, f_path)

if f_stale || ! isdefined(context, func_name)
f_stale && Flax.build_module(to_js(data), path, mod_name)

return Base.include(context, joinpath(Genie.config.path_build, Flax.BUILD_NAME, mod_name))
end

getfield(context, func_name)
end


"""
"""
function render(viewfile::FilePaths.PosixPath; context::Module = @__MODULE__, vars...) :: Function
Flax.registervars(vars...)

get_template(string(viewfile), partial = false, context = context)
end

end
61 changes: 55 additions & 6 deletions src/Renderer.jl
Original file line number Diff line number Diff line change
@@ -1,6 +1,6 @@
module Renderer

export respond, html, json, redirect
export respond, html, json, redirect, js

import Revise
import HTTP, Reexport, Markdown, Logging, FilePaths
Expand Down Expand Up @@ -42,8 +42,9 @@ Collection of renderers associated to each supported mime time. Other mime-rende
or current ones can be replaced by custom ones, to be used by Genie.
"""
const RENDERERS = Dict(
MIME"text/html" => Flax.HTMLRenderer,
MIME"application/json" => Flax.JSONRenderer
MIME"text/html" => Flax.HTMLRenderer,
MIME"application/json" => Flax.JSONRenderer,
MIME"application/javascript" => Flax.JSRenderer
)

const ResourcePath = Union{String,Symbol}
Expand Down Expand Up @@ -135,6 +136,15 @@ function WebRenderable(wr::WebRenderable, status::Int, headers::HTTPHeaders)
end


function WebRenderable(wr::WebRenderable, content_type::Symbol, status::Int, headers::HTTPHeaders)
wr.content_type = content_type
wr.status = status
wr.headers = headers

wr
end


function render(::Type{MIME"text/html"}, data::String;
context::Module = @__MODULE__, layout::Union{String,Nothing} = nothing, vars...) :: WebRenderable
try
Expand All @@ -144,6 +154,8 @@ function render(::Type{MIME"text/html"}, data::String;
rethrow(ex)
end
end


function render(::Type{MIME"text/html"}, viewfile::FilePath; layout::Union{Nothing,FilePath} = nothing,
context::Module = @__MODULE__, vars...) :: WebRenderable
try
Expand Down Expand Up @@ -242,22 +254,59 @@ end
"""
function json(datafile::FilePath; context::Module = @__MODULE__,
status::Int = 200, headers::HTTPHeaders = HTTPHeaders(), vars...) :: HTTP.Response
WebRenderable(render(MIME"application/json", datafile; context = context, vars...), status, headers) |> respond
WebRenderable(render(MIME"application/json", datafile; context = context, vars...), :json, status, headers) |> respond
end


"""
"""
function json(data::String; context::Module = @__MODULE__,
status::Int = 200, headers::HTTPHeaders = HTTPHeaders(), vars...) :: HTTP.Response
WebRenderable(render(MIME"application/json", data; context = context, vars...), status, headers) |> respond
WebRenderable(render(MIME"application/json", data; context = context, vars...), :json, status, headers) |> respond
end


"""
"""
function json(data; status::Int = 200, headers::HTTPHeaders = HTTPHeaders()) :: HTTP.Response
WebRenderable(render(MIME"application/json", data), status, headers) |> respond
WebRenderable(render(MIME"application/json", data), :json, status, headers) |> respond
end


### JS RENDERING


function render(::Type{MIME"application/javascript"}, data::String; context::Module = @__MODULE__, vars...) :: WebRenderable
try
WebRenderable(RENDERERS[MIME"application/javascript"].render(data; context = context, vars...) |> Base.invokelatest, :js)
catch ex
isa(ex, KeyError) && Flax.changebuilds() # it's a view error so don't reuse them
rethrow(ex)
end
end


function render(::Type{MIME"application/javascript"}, viewfile::FilePath; context::Module = @__MODULE__, vars...) :: WebRenderable
try
WebRenderable(RENDERERS[MIME"application/javascript"].render(viewfile; context = context, vars...) |> Base.invokelatest, :js)
catch ex
isa(ex, KeyError) && Flax.changebuilds() # it's a view error so don't reuse them
rethrow(ex)
end
end


"""
"""
function js(data::String; context::Module = @__MODULE__, status::Int = 200, headers::HTTPHeaders = HTTPHeaders(), vars...) :: HTTP.Response
WebRenderable(render(MIME"application/javascript", data; context = context, vars...), :js, status, headers) |> respond
end


"""
"""
function js(viewfile::FilePath; context::Module = @__MODULE__, status::Int = 200, headers::HTTPHeaders = HTTPHeaders(), vars...) :: HTTP.Response
WebRenderable(render(MIME"application/javascript", viewfile; context = context, vars...), :js, status, headers) |> respond
end

### REDIRECT RESPONSES ###
Expand Down
5 changes: 0 additions & 5 deletions test/tests_advanced_html_rendering.jl
Original file line number Diff line number Diff line change
@@ -1,8 +1,3 @@
# TODO - rendering of html elements with properties, slashes, quotes, no quotes, empty, etc
# TODO - multiple tags in if/else
# TODO @foreach, if, etc


@safetestset "Advanced rendering" begin

@safetestset "@foreach macro renders local variables" begin
Expand Down
Loading

0 comments on commit 4637f36

Please sign in to comment.